summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2018-01-08 22:22:59 +0530
committerJonas Smedegaard <dr@jones.dk>2018-01-08 22:22:59 +0530
commit766bb4acdda738e450630c92d9c37e6cb42d9423 (patch)
tree8277c899ed928c69dfd1251bf889af6c78400f84
Import baresip_0.5.7.orig.tar.gz
[dgit import orig baresip_0.5.7.orig.tar.gz]
-rw-r--r--.gitignore25
-rw-r--r--.travis.yml30
-rw-r--r--Makefile305
-rw-r--r--README.md441
-rw-r--r--debian/changelog192
-rw-r--r--debian/compat1
-rw-r--r--debian/control34
-rw-r--r--debian/copyright37
-rw-r--r--debian/dirs3
-rw-r--r--debian/docs2
-rw-r--r--debian/libbaresip-dev.dirs3
-rw-r--r--debian/libbaresip-dev.files2
-rw-r--r--debian/libbaresip.dirs1
-rw-r--r--debian/libbaresip.files1
-rwxr-xr-xdebian/rules90
-rw-r--r--docs/COPYING31
-rw-r--r--docs/ChangeLog1611
-rw-r--r--docs/THANKS47
-rw-r--r--docs/TODO11
-rw-r--r--docs/examples/accounts58
-rw-r--r--docs/examples/config177
-rw-r--r--docs/examples/contacts11
-rw-r--r--include/baresip.h1211
-rw-r--r--mk/Doxyfile238
-rw-r--r--mk/mod.mk105
-rw-r--r--mk/modules.mk467
-rw-r--r--mk/win32/baresip.sln39
-rw-r--r--mk/win32/baresip.vcxproj204
-rw-r--r--mk/win32/baresip.vcxproj.filters366
-rw-r--r--mk/win32/static.c38
-rw-r--r--modules/account/account.c213
-rw-r--r--modules/account/module.mk10
-rw-r--r--modules/alsa/alsa.c184
-rw-r--r--modules/alsa/alsa.h20
-rw-r--r--modules/alsa/alsa_play.c169
-rw-r--r--modules/alsa/alsa_src.c174
-rw-r--r--modules/alsa/module.mk11
-rw-r--r--modules/amr/amr.c344
-rw-r--r--modules/amr/amr.h10
-rw-r--r--modules/amr/module.mk64
-rw-r--r--modules/amr/sdp.c56
-rw-r--r--modules/aubridge/aubridge.c67
-rw-r--r--modules/aubridge/aubridge.h41
-rw-r--r--modules/aubridge/device.c187
-rw-r--r--modules/aubridge/module.mk11
-rw-r--r--modules/aubridge/play.c58
-rw-r--r--modules/aubridge/src.c61
-rw-r--r--modules/audiounit/audiounit.c97
-rw-r--r--modules/audiounit/audiounit.h27
-rw-r--r--modules/audiounit/module.mk14
-rw-r--r--modules/audiounit/player.c212
-rw-r--r--modules/audiounit/recorder.c263
-rw-r--r--modules/audiounit/sess.c174
-rw-r--r--modules/aufile/aufile.c265
-rw-r--r--modules/aufile/module.mk11
-rw-r--r--modules/auloop/auloop.c413
-rw-r--r--modules/auloop/module.mk10
-rw-r--r--modules/av1/av1.c51
-rw-r--r--modules/av1/av1.h20
-rw-r--r--modules/av1/decode.c293
-rw-r--r--modules/av1/encode.c259
-rw-r--r--modules/av1/module.mk13
-rw-r--r--modules/avahi/avahi.c389
-rw-r--r--modules/avahi/module.mk12
-rw-r--r--modules/avcapture/avcapture.m398
-rw-r--r--modules/avcapture/module.mk11
-rw-r--r--modules/avcodec/avcodec.c258
-rw-r--r--modules/avcodec/avcodec.h60
-rw-r--r--modules/avcodec/decode.c556
-rw-r--r--modules/avcodec/encode.c805
-rw-r--r--modules/avcodec/h263.c188
-rw-r--r--modules/avcodec/h26x.h104
-rw-r--r--modules/avcodec/module.mk21
-rw-r--r--modules/avformat/avformat.c448
-rw-r--r--modules/avformat/module.mk12
-rw-r--r--modules/b2bua/b2bua.c246
-rw-r--r--modules/b2bua/module.mk10
-rw-r--r--modules/bv32/bv32.c180
-rw-r--r--modules/bv32/module.mk11
-rw-r--r--modules/cairo/cairo.c345
-rw-r--r--modules/cairo/module.mk12
-rw-r--r--modules/codec2/codec2.c202
-rw-r--r--modules/codec2/module.mk11
-rw-r--r--modules/cons/cons.c274
-rw-r--r--modules/cons/module.mk10
-rw-r--r--modules/contact/contact.c255
-rw-r--r--modules/contact/module.mk10
-rw-r--r--modules/coreaudio/coreaudio.c111
-rw-r--r--modules/coreaudio/coreaudio.h18
-rw-r--r--modules/coreaudio/module.mk13
-rw-r--r--modules/coreaudio/player.c165
-rw-r--r--modules/coreaudio/recorder.c176
-rw-r--r--modules/daala/daala.c63
-rw-r--r--modules/daala/daala.h20
-rw-r--r--modules/daala/decode.c181
-rw-r--r--modules/daala/encode.c292
-rw-r--r--modules/daala/module.mk13
-rw-r--r--modules/debug_cmd/debug_cmd.c164
-rw-r--r--modules/debug_cmd/module.mk10
-rw-r--r--modules/directfb/directfb.c190
-rw-r--r--modules/directfb/module.mk14
-rw-r--r--modules/dshow/dshow.cpp536
-rw-r--r--modules/dshow/module.mk11
-rw-r--r--modules/dtls_srtp/dtls.c51
-rw-r--r--modules/dtls_srtp/dtls_srtp.c507
-rw-r--r--modules/dtls_srtp/dtls_srtp.h33
-rw-r--r--modules/dtls_srtp/module.mk11
-rw-r--r--modules/dtls_srtp/srtp.c150
-rw-r--r--modules/dtmfio/dtmfio.c149
-rw-r--r--modules/dtmfio/module.mk12
-rw-r--r--modules/echo/echo.c158
-rw-r--r--modules/echo/module.mk9
-rw-r--r--modules/evdev/evdev.c348
-rw-r--r--modules/evdev/module.mk12
-rw-r--r--modules/evdev/print.c513
-rw-r--r--modules/evdev/print.h11
-rw-r--r--modules/fakevideo/fakevideo.c199
-rw-r--r--modules/fakevideo/module.mk11
-rw-r--r--modules/g711/g711.c133
-rw-r--r--modules/g711/module.mk10
-rw-r--r--modules/g722/g722.c191
-rw-r--r--modules/g722/module.mk11
-rw-r--r--modules/g7221/decode.c70
-rw-r--r--modules/g7221/encode.c71
-rw-r--r--modules/g7221/g7221.c50
-rw-r--r--modules/g7221/g7221.h29
-rw-r--r--modules/g7221/module.mk14
-rw-r--r--modules/g7221/sdp.c54
-rw-r--r--modules/g726/g726.c208
-rw-r--r--modules/g726/module.mk11
-rw-r--r--modules/gsm/gsm.c178
-rw-r--r--modules/gsm/module.mk12
-rw-r--r--modules/gst/README34
-rw-r--r--modules/gst/dump.c65
-rw-r--r--modules/gst/gst.c454
-rw-r--r--modules/gst/gst.h9
-rw-r--r--modules/gst/module.mk12
-rw-r--r--modules/gst1/gst.c464
-rw-r--r--modules/gst1/module.mk13
-rw-r--r--modules/gst_video/encode.c540
-rw-r--r--modules/gst_video/gst_video.c64
-rw-r--r--modules/gst_video/gst_video.h24
-rw-r--r--modules/gst_video/module.mk13
-rw-r--r--modules/gst_video/sdp.c56
-rw-r--r--modules/gst_video1/encode.c613
-rw-r--r--modules/gst_video1/gst_video.c66
-rw-r--r--modules/gst_video1/gst_video.h24
-rw-r--r--modules/gst_video1/module.mk13
-rw-r--r--modules/gst_video1/sdp.c57
-rw-r--r--modules/gtk/call_window.c522
-rw-r--r--modules/gtk/dial_dialog.c96
-rw-r--r--modules/gtk/gtk_mod.c1053
-rw-r--r--modules/gtk/gtk_mod.h51
-rw-r--r--modules/gtk/module.mk22
-rw-r--r--modules/gtk/transfer_dialog.c134
-rw-r--r--modules/gtk/uri_entry.c45
-rw-r--r--modules/gzrtp/gzrtp.cpp220
-rw-r--r--modules/gzrtp/messages.cpp256
-rw-r--r--modules/gzrtp/module.mk38
-rw-r--r--modules/gzrtp/session.cpp214
-rw-r--r--modules/gzrtp/session.h50
-rw-r--r--modules/gzrtp/srtp.cpp327
-rw-r--r--modules/gzrtp/srtp.h46
-rw-r--r--modules/gzrtp/stream.cpp707
-rw-r--r--modules/gzrtp/stream.h138
-rw-r--r--modules/h265/README29
-rw-r--r--modules/h265/TODO5
-rw-r--r--modules/h265/decode.c337
-rw-r--r--modules/h265/encode.c292
-rw-r--r--modules/h265/fmt.c165
-rw-r--r--modules/h265/h265.c67
-rw-r--r--modules/h265/h265.h70
-rw-r--r--modules/h265/module.mk11
-rw-r--r--modules/h265/notes75
-rw-r--r--modules/httpd/httpd.c181
-rw-r--r--modules/httpd/module.mk10
-rw-r--r--modules/ice/ice.c989
-rw-r--r--modules/ice/module.mk10
-rw-r--r--modules/ilbc/ilbc.c357
-rw-r--r--modules/ilbc/module.mk11
-rw-r--r--modules/isac/isac.c226
-rw-r--r--modules/isac/module.mk11
-rw-r--r--modules/jack/jack.c43
-rw-r--r--modules/jack/jack_play.c239
-rw-r--r--modules/jack/jack_src.c234
-rw-r--r--modules/jack/mod_jack.h14
-rw-r--r--modules/jack/module.mk12
-rw-r--r--modules/l16/l16.c104
-rw-r--r--modules/l16/module.mk10
-rw-r--r--modules/libsrtp/module.mk11
-rw-r--r--modules/libsrtp/sdes.c46
-rw-r--r--modules/libsrtp/sdes.h22
-rw-r--r--modules/libsrtp/srtp.c474
-rw-r--r--modules/menu/menu.c1162
-rw-r--r--modules/menu/module.mk10
-rw-r--r--modules/mpa/decode.c217
-rw-r--r--modules/mpa/encode.c196
-rw-r--r--modules/mpa/module.mk14
-rw-r--r--modules/mpa/mpa.c205
-rw-r--r--modules/mpa/mpa.h37
-rw-r--r--modules/mpa/sdp.c55
-rw-r--r--modules/mqtt/README.md59
-rw-r--r--modules/mqtt/module.mk14
-rw-r--r--modules/mqtt/mqtt.c157
-rw-r--r--modules/mqtt/mqtt.h26
-rw-r--r--modules/mqtt/publish.c104
-rw-r--r--modules/mqtt/subscribe.c146
-rw-r--r--modules/mwi/module.mk10
-rw-r--r--modules/mwi/mwi.c225
-rw-r--r--modules/natbd/module.mk10
-rw-r--r--modules/natbd/natbd.c509
-rw-r--r--modules/natpmp/libnatpmp.c235
-rw-r--r--modules/natpmp/libnatpmp.h53
-rw-r--r--modules/natpmp/module.mk10
-rw-r--r--modules/natpmp/natpmp.c387
-rw-r--r--modules/omx/README17
-rw-r--r--modules/omx/module.c137
-rw-r--r--modules/omx/module.mk23
-rw-r--r--modules/omx/omx.c349
-rw-r--r--modules/omx/omx.h46
-rw-r--r--modules/opengl/module.mk11
-rw-r--r--modules/opengl/opengl.m534
-rw-r--r--modules/opengles/context.m115
-rw-r--r--modules/opengles/module.mk16
-rw-r--r--modules/opengles/opengles.c297
-rw-r--r--modules/opengles/opengles.h28
-rw-r--r--modules/opensles/module.mk13
-rw-r--r--modules/opensles/opensles.c79
-rw-r--r--modules/opensles/opensles.h18
-rw-r--r--modules/opensles/player.c203
-rw-r--r--modules/opensles/recorder.c214
-rw-r--r--modules/opus/decode.c101
-rw-r--r--modules/opus/encode.c187
-rw-r--r--modules/opus/module.mk14
-rw-r--r--modules/opus/opus.c173
-rw-r--r--modules/opus/opus.h37
-rw-r--r--modules/opus/sdp.c51
-rw-r--r--modules/oss/module.mk18
-rw-r--r--modules/oss/oss.c364
-rw-r--r--modules/pcp/listener.c124
-rw-r--r--modules/pcp/module.mk12
-rw-r--r--modules/pcp/pcp.c365
-rw-r--r--modules/pcp/pcp.h15
-rw-r--r--modules/plc/module.mk11
-rw-r--r--modules/plc/plc.c120
-rw-r--r--modules/portaudio/module.mk11
-rw-r--r--modules/portaudio/portaudio.c346
-rw-r--r--modules/presence/module.mk11
-rw-r--r--modules/presence/notifier.c219
-rw-r--r--modules/presence/presence.c123
-rw-r--r--modules/presence/presence.h19
-rw-r--r--modules/presence/publisher.c309
-rw-r--r--modules/presence/subscriber.c382
-rw-r--r--modules/pulse/module.mk14
-rw-r--r--modules/pulse/player.c153
-rw-r--r--modules/pulse/pulse.c54
-rw-r--r--modules/pulse/pulse.h14
-rw-r--r--modules/pulse/recorder.c193
-rw-r--r--modules/qtcapture/module.mk11
-rw-r--r--modules/qtcapture/qtcapture.m402
-rw-r--r--modules/rst/audio.c273
-rw-r--r--modules/rst/module.mk14
-rw-r--r--modules/rst/rst.c423
-rw-r--r--modules/rst/rst.h26
-rw-r--r--modules/rst/video.c280
-rw-r--r--modules/sdl/module.mk18
-rw-r--r--modules/sdl/sdl.c327
-rw-r--r--modules/sdl/sdl.h9
-rw-r--r--modules/sdl/util.c54
-rw-r--r--modules/sdl2/module.mk11
-rw-r--r--modules/sdl2/sdl.c343
-rw-r--r--modules/selfview/module.mk11
-rw-r--r--modules/selfview/selfview.c280
-rw-r--r--modules/silk/module.mk11
-rw-r--r--modules/silk/silk.c262
-rw-r--r--modules/snapshot/module.mk11
-rw-r--r--modules/snapshot/png_vf.c189
-rw-r--r--modules/snapshot/png_vf.h6
-rw-r--r--modules/snapshot/snapshot.c104
-rw-r--r--modules/sndfile/module.mk11
-rw-r--r--modules/sndfile/sndfile.c200
-rw-r--r--modules/sndio/module.mk11
-rw-r--r--modules/sndio/sndio.c319
-rw-r--r--modules/speex/module.mk12
-rw-r--r--modules/speex/speex.c519
-rw-r--r--modules/speex_aec/module.mk15
-rw-r--r--modules/speex_aec/speex_aec.c229
-rw-r--r--modules/speex_pp/module.mk15
-rw-r--r--modules/speex_pp/speex_pp.c161
-rw-r--r--modules/srtp/module.mk11
-rw-r--r--modules/srtp/sdes.c45
-rw-r--r--modules/srtp/sdes.h22
-rw-r--r--modules/srtp/srtp.c420
-rw-r--r--modules/stdio/module.mk11
-rw-r--r--modules/stdio/stdio.c193
-rw-r--r--modules/stun/module.mk10
-rw-r--r--modules/stun/stun.c252
-rw-r--r--modules/swscale/module.mk11
-rw-r--r--modules/swscale/swscale.c197
-rw-r--r--modules/syslog/module.mk10
-rw-r--r--modules/syslog/syslog.c76
-rw-r--r--modules/turn/module.mk10
-rw-r--r--modules/turn/turn.c301
-rw-r--r--modules/uuid/module.mk10
-rw-r--r--modules/uuid/uuid.c117
-rw-r--r--modules/v4l/module.mk11
-rw-r--r--modules/v4l/v4l.c270
-rw-r--r--modules/v4l2/module.mk14
-rw-r--r--modules/v4l2/v4l2.c513
-rw-r--r--modules/v4l2_codec/README41
-rw-r--r--modules/v4l2_codec/module.mk11
-rw-r--r--modules/v4l2_codec/v4l2_codec.c603
-rw-r--r--modules/vidbridge/disp.c94
-rw-r--r--modules/vidbridge/module.mk11
-rw-r--r--modules/vidbridge/src.c93
-rw-r--r--modules/vidbridge/vidbridge.c78
-rw-r--r--modules/vidbridge/vidbridge.h47
-rw-r--r--modules/vidinfo/module.mk12
-rw-r--r--modules/vidinfo/panel.c289
-rw-r--r--modules/vidinfo/vidinfo.c177
-rw-r--r--modules/vidinfo/vidinfo.h42
-rw-r--r--modules/vidloop/module.mk10
-rw-r--r--modules/vidloop/vidloop.c505
-rw-r--r--modules/vp8/decode.c290
-rw-r--r--modules/vp8/encode.c272
-rw-r--r--modules/vp8/module.mk14
-rw-r--r--modules/vp8/sdp.c39
-rw-r--r--modules/vp8/vp8.c62
-rw-r--r--modules/vp8/vp8.h30
-rw-r--r--modules/vp9/decode.c287
-rw-r--r--modules/vp9/encode.c317
-rw-r--r--modules/vp9/module.mk14
-rw-r--r--modules/vp9/sdp.c39
-rw-r--r--modules/vp9/vp9.c64
-rw-r--r--modules/vp9/vp9.h30
-rw-r--r--modules/vumeter/module.mk11
-rw-r--r--modules/vumeter/vumeter.c203
-rw-r--r--modules/wincons/module.mk11
-rw-r--r--modules/wincons/wincons.c217
-rw-r--r--modules/winwave/module.mk11
-rw-r--r--modules/winwave/play.c236
-rw-r--r--modules/winwave/src.c226
-rw-r--r--modules/winwave/winwave.c60
-rw-r--r--modules/winwave/winwave.h20
-rw-r--r--modules/x11/module.mk12
-rw-r--r--modules/x11/x11.c452
-rw-r--r--modules/x11grab/module.mk12
-rw-r--r--modules/x11grab/x11grab.c223
-rw-r--r--modules/zrtp/module.mk13
-rw-r--r--modules/zrtp/zrtp.c661
-rw-r--r--rpm/baresip.spec51
-rw-r--r--share/busy.wavbin0 -> 64320 bytes
-rw-r--r--share/callwaiting.wavbin0 -> 80418 bytes
-rw-r--r--share/error.wavbin0 -> 16776 bytes
-rw-r--r--share/logo.pngbin0 -> 49325 bytes
-rw-r--r--share/message.wavbin0 -> 6356 bytes
-rw-r--r--share/notfound.wavbin0 -> 16568 bytes
-rw-r--r--share/ring.wavbin0 -> 12180 bytes
-rw-r--r--share/ringback.wavbin0 -> 68084 bytes
-rw-r--r--src/account.c703
-rw-r--r--src/aucodec.c66
-rw-r--r--src/audio.c2081
-rw-r--r--src/aufilt.c28
-rw-r--r--src/aulevel.c85
-rw-r--r--src/auplay.c109
-rw-r--r--src/ausrc.c108
-rw-r--r--src/baresip.c248
-rw-r--r--src/bfcp.c199
-rw-r--r--src/call.c1877
-rw-r--r--src/cmd.c768
-rw-r--r--src/conf.c386
-rw-r--r--src/config.c925
-rw-r--r--src/contact.c338
-rw-r--r--src/core.h541
-rw-r--r--src/event.c171
-rw-r--r--src/h264.c182
-rw-r--r--src/log.c153
-rw-r--r--src/magic.h38
-rw-r--r--src/main.c265
-rw-r--r--src/mctrl.c44
-rw-r--r--src/menc.c65
-rw-r--r--src/message.c192
-rw-r--r--src/metric.c90
-rw-r--r--src/mnat.c90
-rw-r--r--src/module.c258
-rw-r--r--src/mos.c63
-rw-r--r--src/net.c561
-rw-r--r--src/play.c352
-rw-r--r--src/realtime.c100
-rw-r--r--src/reg.c265
-rw-r--r--src/rtpext.c112
-rw-r--r--src/rtpkeep.c165
-rw-r--r--src/sdp.c191
-rw-r--r--src/sipreq.c150
-rw-r--r--src/srcs.mk55
-rw-r--r--src/stream.c733
-rw-r--r--src/ua.c1906
-rw-r--r--src/ui.c195
-rw-r--r--src/vidcodec.c126
-rw-r--r--src/video.c1421
-rw-r--r--src/vidfilt.c103
-rw-r--r--src/vidisp.c132
-rw-r--r--src/vidsrc.c125
-rw-r--r--test/account.c69
-rw-r--r--test/aulevel.c61
-rw-r--r--test/call.c906
-rw-r--r--test/cmd.c161
-rw-r--r--test/contact.c55
-rw-r--r--test/cplusplus.cpp22
-rw-r--r--test/main.c271
-rw-r--r--test/message.c232
-rw-r--r--test/mock/cert.c82
-rw-r--r--test/mock/dnssrv.c245
-rw-r--r--test/mock/mock_aucodec.c94
-rw-r--r--test/mock/mock_auplay.c102
-rw-r--r--test/mock/mock_ausrc.c91
-rw-r--r--test/mock/mock_vidcodec.c210
-rw-r--r--test/mock/mock_vidisp.c97
-rw-r--r--test/mock/mock_vidsrc.c91
-rw-r--r--test/mos.c53
-rw-r--r--test/net.c46
-rw-r--r--test/play.c99
-rw-r--r--test/sip/aor.c98
-rw-r--r--test/sip/auth.c91
-rw-r--r--test/sip/domain.c187
-rw-r--r--test/sip/location.c188
-rw-r--r--test/sip/sipsrv.c330
-rw-r--r--test/sip/sipsrv.h122
-rw-r--r--test/sip/user.c73
-rw-r--r--test/srcs.mk54
-rw-r--r--test/test.c109
-rw-r--r--test/test.h224
-rw-r--r--test/ua.c714
-rw-r--r--test/video.c38
434 files changed, 74411 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..392a2cc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+# Object files
+*.o
+*.ko
+
+# Libraries
+*.lib
+*.a
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+baresip
+selftest
+
+# Generated files
+src/static.c
+build*
+*.pc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..093f30f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,30 @@
+language: c
+
+os:
+ - linux
+ - osx
+
+compiler:
+ - clang
+ - gcc
+
+env:
+ - LIBRE=re LIBREM=rem
+
+sudo: require
+
+addons:
+ apt:
+ packages:
+ libssl-dev
+
+install:
+ - git clone https://github.com/creytiv/re.git
+ - git clone https://github.com/creytiv/rem.git
+ - curl -OL 'https://github.com/alfredh/pytools/raw/master/ccheck.py'
+ - for p in ${LIBRE} ${LIBREM}; do cd $p && sudo PATH="$PATH" make install && cd - && sudo rm -Rf $p; done
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo ldconfig; fi
+
+script:
+ - make V=1 CCACHE= EXTRA_CFLAGS=-Werror info test modules
+ - python2 ccheck.py
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f5b4c25
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,305 @@
+#
+# Makefile
+#
+# Copyright (C) 2010 Creytiv.com
+#
+#
+# Internal features:
+#
+# USE_TLS Enable SIP over TLS transport
+# USE_VIDEO Enable Video-support
+#
+
+USE_VIDEO := 1
+
+PROJECT := baresip
+VERSION := 0.5.7
+DESCR := "Baresip is a modular SIP User-Agent with audio and video support"
+
+# Verbose and silent build modes
+ifeq ($(V),)
+HIDE=@
+endif
+
+ifndef LIBRE_MK
+LIBRE_MK := $(shell [ -f ../re/mk/re.mk ] && \
+ echo "../re/mk/re.mk")
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f ../re-$(VERSION)/mk/re.mk ] && \
+ echo "../re-$(VERSION)/mk/re.mk")
+endif
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f /usr/share/re/re.mk ] && \
+ echo "/usr/share/re/re.mk")
+endif
+ifeq ($(LIBRE_MK),)
+LIBRE_MK := $(shell [ -f /usr/local/share/re/re.mk ] && \
+ echo "/usr/local/share/re/re.mk")
+endif
+endif
+
+include $(LIBRE_MK)
+include mk/modules.mk
+
+ifndef LIBREM_PATH
+LIBREM_PATH := $(shell [ -d ../rem ] && echo "../rem")
+endif
+
+
+CFLAGS += -I. -Iinclude -I$(LIBRE_INC) -I$(SYSROOT)/include
+CFLAGS += -I$(LIBREM_PATH)/include
+CFLAGS += -I$(SYSROOT)/local/include/rem -I$(SYSROOT)/include/rem
+
+CXXFLAGS += -I. -Iinclude -I$(LIBRE_INC)
+CXXFLAGS += -I$(LIBREM_PATH)/include
+CXXFLAGS += -I$(SYSROOT)/local/include/rem -I$(SYSROOT)/include/rem
+CXXFLAGS += $(EXTRA_CXXFLAGS)
+
+# XXX: common for C/C++
+CPPFLAGS += -DHAVE_INTTYPES_H
+
+ifneq ($(LIBREM_PATH),)
+CLANG_OPTIONS += -I$(LIBREM_PATH)/include
+endif
+
+ifeq ($(OS),win32)
+STATIC := yes
+endif
+
+ifeq ($(OS),freebsd)
+ifneq ($(SYSROOT),)
+CFLAGS += -I$(SYSROOT)/local/include
+endif
+endif
+
+
+# Optional dependencies
+ifneq ($(USE_VIDEO),)
+CFLAGS += -DUSE_VIDEO=1
+endif
+ifneq ($(STATIC),)
+CFLAGS += -DSTATIC=1
+CXXFLAGS += -DSTATIC=1
+endif
+CFLAGS += -DMODULE_CONF
+
+INSTALL := install
+ifeq ($(DESTDIR),)
+PREFIX := /usr/local
+else
+PREFIX := /usr
+endif
+BINDIR := $(PREFIX)/bin
+INCDIR := $(PREFIX)/include
+BIN := $(PROJECT)$(BIN_SUFFIX)
+TEST_BIN := selftest$(BIN_SUFFIX)
+SHARED := lib$(PROJECT)$(LIB_SUFFIX)
+STATICLIB := libbaresip.a
+ifeq ($(STATIC),)
+MOD_BINS:= $(patsubst %,%$(MOD_SUFFIX),$(MODULES))
+endif
+APP_MK := src/srcs.mk
+TEST_MK := test/srcs.mk
+MOD_MK := $(patsubst %,modules/%/module.mk,$(MODULES))
+MOD_BLD := $(patsubst %,$(BUILD)/modules/%,$(MODULES))
+LIBDIR := $(PREFIX)/lib
+MOD_PATH := $(LIBDIR)/$(PROJECT)/modules
+SHARE_PATH := $(PREFIX)/share/$(PROJECT)
+CFLAGS += -DPREFIX=\"$(PREFIX)\"
+
+
+all: sanity $(MOD_BINS) $(BIN)
+
+.PHONY: modules
+modules: $(MOD_BINS)
+
+include $(APP_MK)
+include $(TEST_MK)
+include $(MOD_MK)
+
+OBJS := $(patsubst %.c,$(BUILD)/src/%.o,$(filter %.c,$(SRCS)))
+OBJS += $(patsubst %.m,$(BUILD)/src/%.o,$(filter %.m,$(SRCS)))
+OBJS += $(patsubst %.S,$(BUILD)/src/%.o,$(filter %.S,$(SRCS)))
+
+APP_OBJS := $(OBJS) $(patsubst %.c,$(BUILD)/src/%.o,$(APP_SRCS)) $(MOD_OBJS)
+
+LIB_OBJS := $(OBJS) $(MOD_OBJS)
+
+TEST_OBJS := $(patsubst %.c,$(BUILD)/test/%.o,$(filter %.c,$(TEST_SRCS)))
+TEST_OBJS += $(patsubst %.cpp,$(BUILD)/test/%.o,$(filter %.cpp,$(TEST_SRCS)))
+
+ifneq ($(LIBREM_PATH),)
+LIBS += -L$(LIBREM_PATH)
+endif
+
+# Static build: include module linker-flags in binary
+ifneq ($(STATIC),)
+LIBS += $(MOD_LFLAGS)
+else
+LIBS += -L$(SYSROOT)/local/lib
+MOD_LFLAGS += -L$(SYSROOT)/local/lib
+endif
+
+LIBS += -lrem -lm
+LIBS += -L$(SYSROOT)/lib
+
+ifeq ($(OS),win32)
+TEST_LIBS += -static-libgcc
+endif
+
+
+-include $(APP_OBJS:.o=.d)
+
+-include $(TEST_OBJS:.o=.d)
+
+
+sanity:
+ifeq ($(LIBRE_MK),)
+ @echo "ERROR: Missing common makefile for libre. Check LIBRE_MK"
+ @exit 2
+endif
+ifeq ($(LIBRE_INC),)
+ @echo "ERROR: Missing header files for libre. Check LIBRE_INC"
+ @exit 2
+endif
+ifeq ($(LIBRE_SO),)
+ @echo "ERROR: Missing library files for libre. Check LIBRE_SO"
+ @exit 2
+endif
+
+Makefile: mk/*.mk $(MOD_MK) $(LIBRE_MK)
+
+
+$(SHARED): $(LIB_OBJS)
+ @echo " LD $@"
+ $(HIDE)$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -L$(LIBRE_SO) -lre $(LIBS) -o $@
+
+$(STATICLIB): $(LIB_OBJS)
+ @echo " AR $@"
+ @rm -f $@; $(AR) $(AFLAGS) $@ $^
+ifneq ($(RANLIB),)
+ @echo " RANLIB $@"
+ $(HIDE)$(RANLIB) $@
+endif
+
+libbaresip.pc:
+ @echo 'prefix='$(PREFIX) > libbaresip.pc
+ @echo 'exec_prefix=$${prefix}' >> libbaresip.pc
+ @echo 'libdir=$${prefix}/lib' >> libbaresip.pc
+ @echo 'includedir=$${prefix}/include' >> libbaresip.pc
+ @echo '' >> libbaresip.pc
+ @echo 'Name: libbaresip' >> libbaresip.pc
+ @echo 'Description: $(DESCR)' >> libbaresip.pc
+ @echo 'Version: '$(VERSION) >> libbaresip.pc
+ @echo 'URL: http://www.creytiv.com/baresip.html' >> libbaresip.pc
+ @echo 'Libs: -L$${libdir} -lbaresip' >> libbaresip.pc
+ @echo 'Cflags: -I$${includedir}' >> libbaresip.pc
+
+# GPROF requires static linking
+$(BIN): $(APP_OBJS)
+ @echo " LD $@"
+ifneq ($(GPROF),)
+ $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ ../re/libre.a $(LIBS) -o $@
+else
+ $(HIDE)$(LD) $(LFLAGS) $(APP_LFLAGS) $^ \
+ -L$(LIBRE_SO) -lre $(LIBS) -o $@
+endif
+
+
+.PHONY: test
+test: $(TEST_BIN)
+ ./$(TEST_BIN)
+
+$(TEST_BIN): $(STATICLIB) $(TEST_OBJS)
+ @echo " LD $@"
+ $(HIDE)$(CXX) $(LFLAGS) $(TEST_OBJS) \
+ -L$(LIBRE_SO) -L. \
+ -l$(PROJECT) -lre $(LIBS) $(TEST_LIBS) -o $@
+
+$(BUILD)/%.o: %.c $(BUILD) Makefile $(APP_MK)
+ @echo " CC $@"
+ $(HIDE)$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/%.o: %.cpp $(BUILD) Makefile $(APP_MK)
+ @echo " CXX $@"
+ $(HIDE)$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/%.o: %.m $(BUILD) Makefile $(APP_MK)
+ @echo " OC $@"
+ $(HIDE)$(CC) $(CFLAGS) $(OBJCFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/%.o: %.S $(BUILD) Makefile $(APP_MK)
+ @echo " AS $@"
+ $(HIDE)$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+$(BUILD): Makefile
+ @mkdir -p $(BUILD)/src $(MOD_BLD) $(BUILD)/test/mock $(BUILD)/test/sip
+ @touch $@
+
+install: $(BIN) $(MOD_BINS)
+ @mkdir -p $(DESTDIR)$(BINDIR)
+ $(INSTALL) -m 0755 $(BIN) $(DESTDIR)$(BINDIR)
+ @mkdir -p $(DESTDIR)$(MOD_PATH)
+ $(INSTALL) -m 0644 $(MOD_BINS) $(DESTDIR)$(MOD_PATH)
+ @mkdir -p $(DESTDIR)$(SHARE_PATH)
+ $(INSTALL) -m 0644 share/* $(DESTDIR)$(SHARE_PATH)
+
+install-dev: install-shared install-static
+
+install-shared: $(SHARED) libbaresip.pc
+ @mkdir -p $(DESTDIR)$(INCDIR)
+ $(INSTALL) -Cm 0644 include/baresip.h $(DESTDIR)$(INCDIR)
+ @mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(LIBDIR)/pkgconfig
+ $(INSTALL) -m 0644 $(SHARED) $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0644 libbaresip.pc $(DESTDIR)$(LIBDIR)/pkgconfig
+
+install-static: $(STATICLIB)
+ @mkdir -p $(DESTDIR)$(INCDIR)
+ $(INSTALL) -Cm 0644 include/baresip.h $(DESTDIR)$(INCDIR)
+ @mkdir -p $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0644 $(STATICLIB) $(DESTDIR)$(LIBDIR)
+
+uninstall:
+ @rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
+ @rm -rf $(DESTDIR)$(MOD_PATH)
+ @rm -f $(DESTDIR)$(PREFIX)/lib/$(SHARED)
+ @rm -f $(DESTDIR)$(PREFIX)/lib/$(STATICLIB)
+ @rm -f $(DESTDIR)$(PREFIX)/lib/pkgconfig/libbaresip.pc
+
+.PHONY: clean
+clean:
+ @rm -rf $(BIN) $(MOD_BINS) $(SHARED) $(BUILD) $(TEST_BIN) \
+ $(STATICLIB) libbaresip.pc
+ @rm -f *stamp \
+ `find . -name "*.[od]"` \
+ `find . -name "*~"` \
+ `find . -name "\.\#*"`
+
+.PHONY: ccheck
+ccheck:
+ @ccheck.pl > /dev/null
+
+version:
+ @perl -pi -e 's/BARESIP_VERSION.*/BARESIP_VERSION \"$(VERSION)"/' \
+ include/baresip.h
+ @perl -pi -e "s/PROJECT_NUMBER = .*/\
+PROJECT_NUMBER = $(VERSION)/" \
+ mk/Doxyfile
+ @echo "updating version number to $(VERSION)"
+
+src/static.c: $(BUILD) Makefile $(APP_MK) $(MOD_MK)
+ @echo " SH $@"
+ @echo "/* static.c - autogenerated by makefile */" > $@
+ @echo "#include <re_types.h>" >> $@
+ @echo "#include <re_mod.h>" >> $@
+ @echo "" >> $@
+ @for n in $(MODULES); do \
+ echo "extern const struct mod_export exports_$${n};" >> $@ ; \
+ done
+ @echo "" >> $@
+ @echo "const struct mod_export *mod_table[] = {" >> $@
+ @for n in $(MODULES); do \
+ echo " &exports_$${n}," >> $@ ; \
+ done
+ @echo " NULL" >> $@
+ @echo "};" >> $@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8edddee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,441 @@
+baresip README
+==============
+
+
+![Baresip Logo](https://raw.githubusercontent.com/alfredh/baresip/master/share/logo.png)
+
+
+Baresip is a portable and modular SIP User-Agent with audio and video support.
+Copyright (c) 2010 - 2017 Creytiv.com
+Distributed under BSD license
+
+
+[![Build Status](https://travis-ci.org/alfredh/baresip.svg?branch=master)](https://travis-ci.org/alfredh/baresip)
+
+
+## Features:
+
+* Call features:
+ - Unlimited number of SIP accounts
+ - Unlimited number of calls
+ - Unattended call transfer
+ - Auto answer
+ - Call hold and resume
+ - Microphone mute
+ - Call waiting
+ - Call recording
+ - Peer to peer calls
+ - Video calls
+ - Instant Messaging
+ - Custom ring tones
+ - Repeat last call (redial)
+ - Message Waiting Indication (MWI)
+ - Address book with presence
+
+* Signaling:
+ - SIP protocol support
+ - SIP outbound protocol for NAT-traversal
+ - SIP Re-invite
+ - SIP Routes
+ - SIP early media support
+ - DNS NAPTR/SRV support
+ - Multiple accounts support
+ - DTMF support (RTP, SIP INFO)
+
+* Security:
+ - Signalling encryption (TLS)
+ - Audio and video encryption (Secure RTP)
+ - DTLS-SRTP key exchange protocol
+ - ZRTP key exchange protocol
+ - SDES key exchange protocol
+
+* Audio:
+ - Low latency audio pipeline
+ - High definition audio codecs
+ - Audio device configuration
+ - Audio filter plugins
+ - Internal audio resampler for fixed sampling rates
+ - Linear 16 bit wave format support for ringtones
+ - Packet loss concealment (PLC)
+ - Configurable ringtone playback device
+ - Automatic gain control (AGC) and Noise reducation
+ - Acoustic echo control (AEC)
+
+* Audio-codecs:
+ - AMR narrowband, AMR wideband
+ - BroadVoice32 BV32
+ - Codec2
+ - G.711
+ - G.722
+ - G.726
+ - GSM
+ - iLBC
+ - iSAC
+ - L16
+ - MPA
+ - Opus
+ - Silk
+ - Speex
+
+* Audio-drivers:
+ - Advanced Linux Sound Architecture (ALSA) audio-driver
+ - Android OpenSLES audio-driver
+ - Gstreamer playbin input audio-driver
+ - JACK Audio Connection Kit audio-driver
+ - MacOSX/iOS coreaudio/audiounit audio-driver
+ - Open Sound System (OSS) audio-driver
+ - Portaudio audio-driver
+ - Windows winwave audio-driver
+
+* Video:
+ - Support for H.265, H.264, H.263, VP8, VP9, MPEG-4 Video
+ - Configurable resolution/framerate/bitrate
+ - Configurable video input/output
+ - Support for asymmetric video
+
+* Video-codecs:
+ - H.265
+ - H.264
+ - H.263
+ - VP8
+ - VP9
+ - MPEG-4
+
+* Video-drivers:
+ - iOS avcapture video-source
+ - FFmpeg/libav libavformat/avdevice input
+ - Cairo video-source test module
+ - Direct Show video-source
+ - MacOSX QTcapture/AVCapture video-source
+ - RST media player
+ - Linux V4L/V4L2 video-source
+ - X11 grabber video-source
+ - DirectFB video-output
+ - OpenGL/OpenGLES video-output
+ - SDL/SDL2 video-output
+ - X11 video-output
+
+* NAT-traversal:
+ - STUN support
+ - TURN server support
+ - ICE and ICE-lite support
+ - NATPMP support
+
+* Networking:
+ - multihoming, IPv4/IPv6
+ - automatic network roaming
+
+* Management:
+ - Embedded web-server with HTTP interface
+ - Command-line console over UDP/TCP
+ - Command line interface (CLI)
+ - Simple configuration files
+
+
+## Building
+
+baresip is using GNU makefiles, and the following packages must be
+installed before building:
+
+* [libre](https://github.com/creytiv/re)
+* [librem](https://github.com/creytiv/rem)
+* [openssl](https://www.openssl.org/)
+
+
+### Build with debug enabled
+
+```
+$ make
+$ sudo make install
+```
+
+### Build with release
+
+```
+$ make RELEASE=1
+$ sudo make RELEASE=1 install
+```
+
+### Build with clang compiler
+
+```
+$ make CC=clang
+$ sudo make CC=clang install
+```
+
+Modules will be built if external dependencies are installed.
+After building you can start baresip like this:
+
+```
+$ baresip
+```
+
+The config files in $HOME/.baresip are automatically generated
+the first time you run baresip.
+
+
+## Documentation
+
+The online documentation generated with doxygen is available in
+the main [website](http://creytiv.com/doxygen/baresip-dox/html/)
+
+
+### Examples
+
+Configuration examples are available from the
+[examples](https://github.com/alfredh/baresip/tree/master/docs/examples)
+directory.
+
+
+## License
+
+The baresip project is using the BSD license.
+
+
+## Contributing
+
+Patches can sent via Github
+[Pull-Requests](https://github.com/creytiv/baresip/pulls) or to the RE devel
+[mailing-list](http://lists.creytiv.com/mailman/listinfo/re-devel).
+
+
+## Design goals:
+
+* Minimalistic and modular VoIP client
+* SIP, SDP, RTP/RTCP, STUN/TURN/ICE
+* IPv4 and IPv6 support
+* RFC-compliancy
+* Robust, fast, low footprint
+* Portable C89 and C99 source code
+
+
+## Modular Plugin Architecture:
+```
+account Account loader
+alsa ALSA audio driver
+amr Adaptive Multi-Rate (AMR) audio codec
+aubridge Audio bridge module
+audiounit AudioUnit audio driver for MacOSX/iOS
+aufile Audio module for using a WAV-file as audio input
+auloop Audio-loop test module
+avahi Avahi Zeroconf Module
+avcapture Video source using iOS AVFoundation video capture
+avcodec Video codec using FFmpeg/libav libavcodec
+avformat Video source using FFmpeg/libav libavformat
+b2bua Back-to-Back User-Agent (B2BUA) module
+bv32 BroadVoice32 audio codec
+cairo Cairo video source
+codec2 Codec2 low bit rate speech codec
+cons UDP/TCP console UI driver
+contact Contacts module
+coreaudio Apple Coreaudio driver
+debug_cmd Debug commands
+directfb DirectFB video display module
+dshow Windows DirectShow video source
+dtls_srtp DTLS-SRTP end-to-end encryption
+echo Echo server module
+evdev Linux input driver
+fakevideo Fake video input/output driver
+g711 G.711 audio codec
+g722 G.722 audio codec
+g7221 G.722.1 audio codec
+g726 G.726 audio codec
+gsm GSM audio codec
+gst Gstreamer audio source
+gst1 Gstreamer 1.0 audio source
+gst_video Gstreamer video codec
+gst_video1 Gstreamer 1.0 video codec
+gtk GTK+ 2.0 UI
+gzrtp ZRTP module using GNU ZRTP C++ library
+h265 H.265 video codec
+httpd HTTP webserver UI-module
+ice ICE protocol for NAT Traversal
+ilbc iLBC audio codec
+isac iSAC audio codec
+jack JACK Audio Connection Kit audio-driver
+l16 L16 audio codec
+libsrtp Secure RTP encryption using libsrtp
+menu Interactive menu
+mpa MPA Speech and Audio Codec
+mqtt MQTT (Message Queue Telemetry Transport) module
+mwi Message Waiting Indication
+natbd NAT Behavior Discovery Module
+natpmp NAT Port Mapping Protocol (NAT-PMP) module
+omx OpenMAX IL video display module
+opengl OpenGL video output
+opengles OpenGLES video output
+opensles OpenSLES audio driver
+opus OPUS Interactive audio codec
+oss Open Sound System (OSS) audio driver
+pcp Port Control Protocol (PCP) module
+plc Packet Loss Concealment (PLC) using spandsp
+portaudio Portaudio driver
+pulse Pulseaudio driver
+presence Presence module
+qtcapture Apple QTCapture video source driver
+rst Radio streamer using mpg123
+sdl Simple DirectMedia Layer (SDL) video output driver
+sdl2 Simple DirectMedia Layer v2 (SDL2) video output driver
+selfview Video selfview module
+silk SILK audio codec
+snapshot Save video-stream as PNG images
+sndfile Audio dumper using libsndfile
+sndio Audio driver for OpenBSD
+speex Speex audio codec
+speex_aec Acoustic Echo Cancellation (AEC) using libspeexdsp
+speex_pp Audio pre-processor using libspeexdsp
+srtp Secure RTP encryption (SDES) using libre SRTP-stack
+stdio Standard input/output UI driver
+stun Session Traversal Utilities for NAT (STUN) module
+swscale Video scaling using libswscale
+syslog Syslog module
+turn Obtaining Relay Addresses from STUN (TURN) module
+uuid UUID generator and loader
+v4l Video4Linux video source
+v4l2 Video4Linux2 video source
+v4l2_codec Video4Linux2 video codec module (H264 hardware encoding)
+vidbridge Video bridge module
+vidinfo Video info overlay module
+vidloop Video-loop test module
+vp8 VP8 video codec
+vp9 VP9 video codec
+vumeter Display audio levels in console
+wincons Console input driver for Windows
+winwave Audio driver for Windows
+x11 X11 video output driver
+x11grab X11 grabber video source
+zrtp ZRTP media encryption module
+```
+
+
+## IETF RFC/I-Ds:
+
+* RFC 2190 RTP Payload Format for H.263 Video Streams (Historic)
+* RFC 2250 RTP Payload Format for the mpa Speech and Audio Codec
+* RFC 2429 RTP Payload Format for 1998 ver of ITU-T Rec. H.263 Video (H.263+)
+* RFC 3016 RTP Payload Format for MPEG-4 Audio/Visual Streams
+* RFC 3428 SIP Extension for Instant Messaging
+* RFC 3711 The Secure Real-time Transport Protocol (SRTP)
+* RFC 3856 A Presence Event Package for SIP
+* RFC 3863 Presence Information Data Format (PIDF)
+* RFC 3951 Internet Low Bit Rate Codec (iLBC)
+* RFC 3952 RTP Payload Format for iLBC Speech
+* RFC 3984 RTP Payload Format for H.264 Video
+* RFC 4145 TCP-Based Media Transport in SDP
+* RFC 4240 Basic Network Media Services with SIP (partly)
+* RFC 4298 Broadvoice Speech Codecs
+* RFC 4347 Datagram Transport Layer Security
+* RFC 4568 SDP Security Descriptions for Media Streams
+* RFC 4572 Connection-Oriented Media Transport over TLS Protocol in SDP
+* RFC 4574 The SDP Label Attribute
+* RFC 4585 Extended RTP Profile for RTCP-Based Feedback (RTP/AVPF)
+* RFC 4587 RTP Payload Format for H.261 Video Streams
+* RFC 4629 RTP Payload Format for ITU-T Rec. H.263 Video
+* RFC 4796 The SDP Content Attribute
+* RFC 4867 RTP Payload Format for the AMR and AMR-WB Audio Codecs
+* RFC 4961 Symmetric RTP / RTP Control Protocol (RTCP)
+* RFC 5168 XML Schema for Media Control
+* RFC 5285 A General Mechanism for RTP Header Extensions
+* RFC 5506 Support for Reduced-Size RTCP
+* RFC 5574 RTP Payload Format for the Speex Codec
+* RFC 5576 Source-Specific Media Attributes in SDP
+* RFC 5577 RTP Payload Format for ITU-T Recommendation G.722.1
+* RFC 5626 Managing Client-Initiated Connections in SIP
+* RFC 5627 Obtaining and Using GRUUs in SIP
+* RFC 5761 Multiplexing RTP Data and Control Packets on a Single Port
+* RFC 5763 Framework for Establishing a SRTP Security Context Using DTLS
+* RFC 5764 DTLS Extension to Establish Keys for SRTP
+* RFC 5780 NAT Behaviour Discovery Using STUN
+* RFC 6263 App. Mechanism for Keeping Alive NAT Associated with RTP / RTCP
+* RFC 6464 A RTP Header Extension for Client-to-Mixer Audio Level Indication
+* RFC 6716 Definition of the Opus Audio Codec
+* RFC 6886 NAT Port Mapping Protocol (NAT-PMP)
+* RFC 7587 RTP Payload Format for the Opus Speech and Audio Codec
+* RFC 7741 RTP Payload Format for VP8 Video
+* RFC 7798 RTP Payload Format for High Efficiency Video Coding (HEVC)
+
+* draft-ietf-avt-rtp-isac-04
+
+
+## Architecture:
+(note: out of date, needs updating)
+
+```
+ .------.
+ |Video |
+ _ |Stream|\
+ /|'------' \ 1
+ / \
+ / _\|
+ .--. N .----. M .------. 1 .-------. 1 .-----.
+ |UA|--->|Call|--->|Audio |--->|Generic|--->|Media|
+ '--' '----' |Stream| |Stream | | NAT |
+ |1 '------' '-------' '-----'
+ | C| 1| |
+ \|/ .-----. .----. |
+ .-------. |Codec| |Jbuf| |1
+ | SIP | '-----' '----' |
+ |Session| 1| /|\ |
+ '-------' .---. | \|/
+ |DSP| .--------.
+ '---' |RTP/RTCP|
+ '--------'
+ | SRTP |
+ '--------'
+```
+
+ A User-Agent (UA) has 0-N SIP Calls
+ A SIP Call has 0-M Media Streams
+
+
+## Supported platforms:
+
+* Android
+* Apple Mac OS X and iOS
+* FreeBSD
+* Linux
+* NetBSD
+* OpenBSD
+* Solaris
+* Windows (mingw and VS2015)
+
+
+### Supported versions of C Standard library
+
+* Android bionic
+* BSD libc
+* GNU C Library (glibc)
+* Windows C Run-Time Libraries (CRT)
+* uClibc
+
+
+### Supported compilers:
+
+* gcc 3.x
+* gcc 4.x
+* gcc 5.x
+* gcc 6.x
+* ms vc2003 compiler
+* clang
+
+### Supported versions of OpenSSL
+
+* OpenSSL version 1.0.1
+* OpenSSL version 1.0.2
+* OpenSSL version 1.1.0
+* LibreSSL version 2.x
+
+
+## Related projects
+
+* [libre](https://github.com/creytiv/re)
+* [librem](https://github.com/creytiv/rem)
+* [retest](https://github.com/creytiv/retest)
+* [restund](http://creytiv.com/restund.html)
+
+
+## References
+
+* Project homepage: http://www.creytiv.com/baresip.html
+* Github: https://github.com/alfredh/baresip
+* Mailing-list: http://lists.creytiv.com/mailman/listinfo/re-devel
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..364d0ae
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,192 @@
+baresip (0.5.7) unstable; urgency=medium
+
+ * version 0.5.7
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Mon, 25 Des 2017 10:00:00 +0100
+
+baresip (0.5.6) unstable; urgency=medium
+
+ * version 0.5.6
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 14 Oct 2017 10:00:00 +0200
+
+baresip (0.5.5) unstable; urgency=medium
+
+ * version 0.5.5
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Thu, 7 Sep 2017 16:00:00 +0200
+
+baresip (0.5.4) unstable; urgency=medium
+
+ * version 0.5.4
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 24 Jun 2017 12:00:00 +0200
+
+baresip (0.5.3) unstable; urgency=medium
+
+ * version 0.5.3
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sun, 14 May 2017 07:00:00 +0200
+
+baresip (0.5.2) unstable; urgency=medium
+
+ * version 0.5.2
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 7 Apr 2017 19:00:00 +0200
+
+baresip (0.5.1) unstable; urgency=medium
+
+ * version 0.5.1
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 4 Mar 2017 10:00:00 +0100
+
+baresip (0.5.0) unstable; urgency=medium
+
+ * version 0.5.0
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 23 Dec 2016 18:00:00 +0100
+
+baresip (0.4.20) unstable; urgency=medium
+
+ * version 0.4.20
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 22 July 2016 20:00:00 +0100
+
+baresip (0.4.19) unstable; urgency=medium
+
+ * version 0.4.19
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 20 May 2016 20:00:00 +0100
+
+baresip (0.4.18) unstable; urgency=medium
+
+ * version 0.4.18
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 12 Mar 2016 18:00:00 +0100
+
+baresip (0.4.17) unstable; urgency=low
+
+ * version 0.4.17
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 17 Jan 2016 12:00:00 +0100
+
+baresip (0.4.16) unstable; urgency=low
+
+ * version 0.4.16
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 1 Dec 2015 12:00:00 +0100
+
+baresip (0.4.15) unstable; urgency=low
+
+ * version 0.4.15
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 26 Sep 2015 12:00:00 +0100
+
+baresip (0.4.14) unstable; urgency=low
+
+ * version 0.4.14
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 8 Aug 2015 12:00:00 +0100
+
+
+baresip (0.4.13) unstable; urgency=low
+
+ * version 0.4.13
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 20 Jun 2015 20:00:00 +0100
+
+baresip (0.4.12) unstable; urgency=low
+
+ * version 0.4.12
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 24 Dec 2014 14:00:00 +0100
+
+baresip (0.4.11) unstable; urgency=low
+
+ * version 0.4.11
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 21 Jun 2014 14:00:00 +0100
+
+baresip (0.4.10) unstable; urgency=low
+
+ * version 0.4.10
+
+ -- Alfred E. Heggestad <aeh@db.org> Thu, 23 Jan 2014 16:00:00 +0100
+
+baresip (0.4.9) unstable; urgency=low
+
+ * version 0.4.9
+
+ -- Alfred E. Heggestad <aeh@db.org> Mon, 6 Jan 2014 16:00:00 +0100
+
+baresip (0.4.8) unstable; urgency=low
+
+ * version 0.4.8
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 6 Dec 2013 23:00:00 +0100
+
+baresip (0.4.7) unstable; urgency=low
+
+ * version 0.4.7
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 12 Nov 2013 22:00:00 +0100
+
+baresip (0.4.6) unstable; urgency=low
+
+ * version 0.4.6
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 11 Oct 2013 20:00:00 +0100
+
+baresip (0.4.5) unstable; urgency=low
+
+ * version 0.4.5
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 31 Aug 2013 18:00:00 +0100
+
+baresip (0.4.4) unstable; urgency=low
+
+ * version 0.4.4
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 18 May 2013 10:00:00 +0100
+
+baresip (0.4.3) unstable; urgency=low
+
+ * version 0.4.3
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 1 Jan 2013 01:01:00 +0100
+
+baresip (0.4.2) unstable; urgency=low
+
+ * version 0.4.2
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 9 Sept 2012 09:09:00 +0100
+
+baresip (0.4.1) unstable; urgency=low
+
+ * version 0.4.1
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 21 Apr 2012 21:04:00 +0100
+
+baresip (0.4.0) unstable; urgency=low
+
+ * version 0.4.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 25 Dec 2011 12:25:00 +0100
+
+baresip (0.3.0) unstable; urgency=low
+
+ * version 0.3.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 7 Sept 2011 07:11:00 +0100
+
+baresip (0.2.0) unstable; urgency=low
+
+ * version 0.2.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 20 May 2011 20:05:00 +0100
+
+baresip (0.1.0) unstable; urgency=low
+
+ * version 0.1.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 5 Nov 2010 05:11:10 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..fa34192
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,34 @@
+Source: baresip
+Section: comm
+Priority: optional
+Maintainer: Alfred E. Heggestad <aeh@db.org>
+Standards-Version: 3.9.5
+Build-Depends: debhelper (>= 9.20120311), librem-dev (>= 0.5.0), libre-dev (>= 0.5.4), libasound2-dev, libavformat-dev, libavdevice-dev, libswscale-dev
+Homepage: http://www.creytiv.com/
+
+Package: baresip
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, librem (>= 0.5.0), libre (>= 0.5.4)
+Description: Modular SIP User-Agent with audio and video support
+ Design goals:
+ .
+ Minimalistic and modular VoIP client
+ SIP, SDP, RTP/RTCP, STUN/TURN/ICE
+ IPv4 and IPv6 support
+ RFC compliant
+ Robust, fast, low footprint
+ Portable C89 and C99 source code
+
+Package: libbaresip
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends}, ${misc:Depends}, librem (>= 0.5.0), libre (>= 0.5.4)
+Description: Baresip library
+
+Package: libbaresip-dev
+Architecture: any
+Section: libdevel
+Depends: libbaresip (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
+Description: Baresip library development files
+ See https://github.com/alfredh/baresip/wiki/Using-baresip-as-a-library
+ for an example.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..f56a8be
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,37 @@
+This package was debianized by Alfred E. Heggestad <aeh@db.org>
+
+It was downloaded from www.creytiv.com
+
+
+Copyright (c) 2010 - 2014, Alfred E. Heggestad
+Copyright (c) 2010 - 2014, Richard Aas
+Copyright (c) 2010 - 2014, Creytiv.com
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the Creytiv.com nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..217c62a
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,3 @@
+usr/bin
+usr/lib/baresip/modules
+usr/share/baresip
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b468964
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README.md
+docs/TODO
diff --git a/debian/libbaresip-dev.dirs b/debian/libbaresip-dev.dirs
new file mode 100644
index 0000000..3199aad
--- /dev/null
+++ b/debian/libbaresip-dev.dirs
@@ -0,0 +1,3 @@
+usr/include
+usr/lib
+usr/share
diff --git a/debian/libbaresip-dev.files b/debian/libbaresip-dev.files
new file mode 100644
index 0000000..c3124ae
--- /dev/null
+++ b/debian/libbaresip-dev.files
@@ -0,0 +1,2 @@
+usr/include
+usr/lib/libbaresip.a
diff --git a/debian/libbaresip.dirs b/debian/libbaresip.dirs
new file mode 100644
index 0000000..6845771
--- /dev/null
+++ b/debian/libbaresip.dirs
@@ -0,0 +1 @@
+usr/lib
diff --git a/debian/libbaresip.files b/debian/libbaresip.files
new file mode 100644
index 0000000..0e8a305
--- /dev/null
+++ b/debian/libbaresip.files
@@ -0,0 +1 @@
+/usr/lib/libbaresip.so
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..45a39d1
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,90 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+BARESIP_FLAGS := MOD_AUTODETECT=1
+
+EXTRA_CFLAGS:="$(shell dpkg-buildflags --get CFLAGS)"
+EXTRA_LFLAGS:="$(shell dpkg-buildflags --get LDFLAGS)"
+
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+ dh_testdir
+ $(MAKE) RELEASE=1 $(BARESIP_FLAGS) DESTDIR=$(CURDIR)/debian/baresip \
+ EXTRA_CFLAGS=$(EXTRA_CFLAGS) \
+ EXTRA_LFLAGS=$(EXTRA_LFLAGS)
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ $(MAKE) clean
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
+ mkdir $(CURDIR)/debian/tmp
+ $(MAKE) RELEASE=1 $(BARESIP_FLAGS) install DESTDIR=$(CURDIR)/debian/baresip
+ $(MAKE) RELEASE=1 $(BARESIP_FLAGS) install-dev DESTDIR=$(CURDIR)/debian/tmp
+ dh_movefiles
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+# dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_installinit
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_python
+ dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+build-arch: build
+
+build-indep: build
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/docs/COPYING b/docs/COPYING
new file mode 100644
index 0000000..938d6cc
--- /dev/null
+++ b/docs/COPYING
@@ -0,0 +1,31 @@
+Copyright (c) 2010 - 2017, Alfred E. Heggestad
+Copyright (c) 2010 - 2017, Richard Aas
+Copyright (c) 2010 - 2017, Creytiv.com
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/docs/ChangeLog b/docs/ChangeLog
new file mode 100644
index 0000000..d79818e
--- /dev/null
+++ b/docs/ChangeLog
@@ -0,0 +1,1611 @@
+2017-12-25 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.7
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.7
+ * NOTE: Requires libre v0.5.5 or later
+ Requires librem v0.5.0 or later
+
+ * Credits: Thanks to Swedish Radio who sponsored many new
+ features in this release.
+
+ * new commands:
+ - 'conf_reload' -- Reload config file
+
+ * new modules:
+ - gzrtp ZRTP module using GNU ZRTP C++ library
+ (thanks glenvt18)
+
+ - mqtt MQTT (Message Queue Telemetry Transport) module
+ (sponsored by Swedish Radio)
+
+ * config:
+ - audio_txmode poll|thread Set audio transmit mode
+ - auplay_format s16|float|s24_3le Set playback sample format
+ - ausrc_format s16|float|s24_3le Set source sample format
+ - sdp_ebuacip yes|no Enable EBU-ACIP parameters
+ - zrtp_hash yes|no Enable/disable ZRTP hash
+
+ * baresip-core:
+ - audio: add sample format conversion
+ - audio: add sample format for source/playback
+ - audio: check timestamps on incoming RTP packets
+ - audio: pace outgoing packets in txmode=thread
+ - audio: remove txmode with realtime thread
+ - audio: remove txmode with timer
+ - audio: set EBUACIP parameters in SDP
+ - auplay: add sample format to auplay_prm
+ - auplay: change write handler to any sample format
+ - ausrc: add sample format to ausrc_prm
+ - ausrc: change read handler to any sample format
+ - event.c: new file for generic event handling
+ - event: add event_encode_dict to encode event to a dictionary
+ - event: added UA_EVENT_CALL_RTCP for received RTCP
+ - log: print to stdout (ref #320)
+
+ * selftest:
+ - add test for different audio tx-modes
+ - add test for float audio sample format
+
+ * Modules:
+
+ * alsa: add support for multiple sample formats
+
+ * audiounit: add support for FLOAT sample format
+
+ * auloop: add support for multiple sample formats
+
+ * avahi: Bugfix: Destroy resolver after callback (#318)
+ (thanks Jonathan Sieber)
+
+ * avcodec: change x264 rate control mode to ABR (#334)
+ (thanks Jonathan Sieber)
+
+ * debug_cmd: add command 'conf_reload' to reload config file
+
+ * gzrtp: ZRTP module using GNU ZRTP C++ library
+ (thanks glenvt18)
+
+ * menu: add config 'ringback_disabled' to disable playing
+ of ringback tone.
+
+ * mqtt: MQTT (Message Queue Telemetry Transport) module
+ new module using libmosquitto as the backend.
+
+ * opus: fix encoder bitrate, ref #305
+ add opus_stereo config parameter (thanks Ola Palm)
+ add config param opus_sprop_stereo (thanks Ola Palm)
+
+ * portaudio: add support for FLOAT sample format
+
+ * pulse: add support for FLOAT sample format
+ remove garbage at the beginning of a recording (#323)
+
+ * quicktime: module was removed
+
+ * rst: add support for multiple sample formats
+
+ * zrtp: add signaling hash support (#311)
+
+
+
+
+2017-10-14 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.6
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.6
+ * NOTE: Requires libre v0.5.5 or later
+ Requires librem v0.5.0 or later
+
+ * New Baresip logo (thanks Ernst and community)
+
+ * baresip-core:
+ - log: rename error to error_msg due to GNU extension clash
+ - ua: remove ua_sipfd()
+
+ * Modules:
+
+ * avahi: Avahi Zeroconf Module (thanks Jonathan Sieber)
+
+ * avcodec: handle fragment packet loss
+
+ * cairo: draw a dancing logo
+
+ * ice: set ICE role correctly
+ set retransmit count (RC) to 4
+
+ * opensles: fix recorder speaker setup (thanks Juha Heinanen)
+
+ * opus: fix encoder bitrate, ref #305
+
+ * zrtp: encrypt/decrypt RTCP packets (thanks @glenvt18)
+
+
+2017-09-07 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.5
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.5
+ * NOTE: Requires libre v0.5.5 or later
+ Requires librem v0.5.0 or later
+
+ * new commands:
+ - insmod module.so -- Load a module
+ - rmmod module.so -- Unload a module
+
+ * config:
+ - fullscreen yes|no Enable fullscreen display
+
+ * baresip-core:
+ - account: optional param 'auth_pass' for password
+ add account_set_auth_pass()
+ add account_aor()
+ add account_auth_pass()
+ - contact: add update handler (thanks Jonathan Sieber)
+ - h264: add rtp_ts RTP Timestamp
+ - module: add module_load/unload
+ remove list of application modules
+ - stream: reset timer on incoming RTCP packets (fixes #271)
+ - ui: make the API re-entrant
+ - video: add RTP timestamp to videnc packet handler
+ add video_calc_rtp_timestamp()
+ add video_calc_seconds()
+ - video: use RTP timestamp from video encoder
+
+ * selftest:
+ - add test for video timestamps
+
+ * Modules:
+
+ * account: move password prompt here
+
+ * av1: use encoder PTS to calculate RTP timestamp
+
+ * avcodec: use encoder PTS to calculate RTP timestamp
+ use level_idc=0x1f for x264
+
+ * cons: updated UI api
+
+ * evdev: updated UI api
+
+ * gst_video: use encoder PTS to calculate RTP timestamp
+
+ * gst_video1: use encoder PTS to calculate RTP timestamp
+
+ * h265: use encoder PTS to calculate RTP timestamp
+ fix FU decoder bug
+
+ * httpd: updated UI api
+
+ * ice: move gathering from lib to app
+ (requires libre v0.5.5 or later)
+
+ * menu: updated UI api
+
+ * mwi: updated UI api
+
+ * presence: Handle contacts added at run-time
+ (thanks Jonathan Sieber)
+
+ * sdl: updated UI api
+
+ * sdl2: add support for fullscreen video
+
+ * stdio: updated UI api
+
+ * v4l: add support for more pixel-formats
+
+ * v4l2_codec: use encoder PTS to calculate RTP timestamp
+
+ * vp8: use encoder PTS to calculate RTP timestamp
+
+ * vp9: use encoder PTS to calculate RTP timestamp
+
+ * wincons: updated UI api
+
+
+2017-06-24 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.4
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.4
+ * NOTE: Requires libre v0.5.4 or later
+ Requires librem v0.5.0 or later
+
+ * config:
+ - audio_level yes|no Enable audio level RTP extension
+
+ * baresip-core:
+ - add support for Client-to-Mixer Audio Level Indication (RFC 6464)
+ - add support for RTP Header Extensions (RFC 5285)
+ - module: dont load same static module twice
+ - ua: add ua_progress()
+ - ua: check for Accept header in incoming OPTIONS request
+ - use a dummy RTP port for incoming OPTIONS (ref #265)
+ - vidcodec: make the API re-entrant
+ - vidfilt: make the API re-entrant
+ - vidisp: make the API re-entrant
+ - vidsrc: make the API re-entrant
+
+ * selftest:
+ - add test for audio level indication in call
+ - add test for call progress
+
+ * Modules:
+
+ * (all video modules updated with API-changes)
+
+ * zrtp: check for RTP packet in send handler (ref #262)
+ (thanks to MobiSciLab for reporting the bug)
+
+ - registered zrtp_log function with zrtp engine
+ - improved info message on how to verify remote peer
+ - improved setting and printing of zrtp cache file
+ (thanks Juha Heinanen)
+
+
+2017-05-14 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.3
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.3
+ * NOTE: Requires libre v0.5.3 or later
+ Requires librem v0.5.0 or later
+
+ * config:
+ - (no changes)
+
+ * build:
+ - detect jack module (thanks Tony Langley)
+ - Updated MSVS projects to vs2015 (thanks Mikhail Barg)
+
+ * baresip-core:
+ - aulevel: add aulevel_calc_dbov()
+ - audio: Set correct clock rate for telephone events
+ (thanks Jan Hoffmann)
+ - play: Add gapless repeat for tone playback (thanks Jan Hoffmann)
+
+ * selftest:
+ - add tests for aulevel
+ - add tests for audio player
+ - add mock aucodec/auplay
+
+ * Modules:
+
+ * gst_video1: Tune x264enc for low latency (thanks Jonathan Sieber)
+
+ * httpd: fix a crash
+
+ * ice: update to latest libre ICE-api
+
+ * omx: Fixed some problems on OMX/RaspberryPi (thanks Jonathan Sieber)
+
+ * srtp: fix SRTP for early-media (thanks Jan Hoffmann)
+
+ * vumeter: use aulevel_calc_dbov to calculate signal energy
+
+ * zrtp: update to latest libzrtp from freeswitch (thanks Juha Heinanen)
+
+
+2017-04-07 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.2
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.2
+ * NOTE: Requires libre v0.5.0 or later
+ Requires librem v0.5.0 or later
+
+ * new modules:
+ - omx OpenMAX IL video display module (thanks Jonathan Sieber)
+
+ * config:
+ - (no changes)
+
+ * baresip-core:
+ - aucodec: make the API re-entrant
+ - aufilt: make the API re-entrant
+ - auplay: make the API re-entrant
+ - ausrc: make the API re-entrant
+ - video: using a video-source is now optional
+
+ * Modules:
+
+ * avformat: add pixelformat AV_PIX_FMT_YUVJ420P (Thanks Gary Metalle)
+
+ * cairo: print picture info, use grey background
+
+ * dtmfio: check fd before calling fclose (thanks Richard Perez)
+
+ * h265: enable YUV444P pixelformat
+
+ * oss: fix build for Solaris 11
+
+ * speex: mark the module as deprecated, see speex.org
+
+
+2017-03-04 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.1
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.1
+ * NOTE: Requires libre v0.5.0 or later
+ Requires librem v0.5.0 or later
+
+ * new modules:
+
+ * config:
+ - stunuser STUN username for STUN/TURN/ICE
+ - stunpass STUN password for STUN/TURN/ICE
+ - snd_path Path to sndfile audio dump files
+
+ * baresip-core:
+ - account: add more accessor functions
+ - account: add 'stunuser' and 'stunpass'
+ - commands: make the struct commands opaque
+ - message: make the API re-entrant, multiple listeners
+ - menc: make the API re-entrant
+ - mnat: make the API re-entrant
+
+ * selftest:
+ - add tests for account
+ - add tests for message
+
+ * Modules:
+
+ * amr: use MOD-CFLAGS instead of global CFLAGS
+
+ * avcodec: added optional config 'avcodec_h264dec' to specify hardware
+ accellerated FFmpeg decoder (thanks Harald Gutmann)
+
+ * avformat: remove blocking sleep, use packet timestamp to
+ pace video stream (thanks Harald Gutmann)
+
+ * debug_cmd: add OpenSSL version to systems info
+
+ * gtk: fix build where USE_NOTIFICATIONS is not defined
+ get rid of system header warnings by using -isystem
+
+ * httpd: add support for un-escaping of URL parameters
+ (thanks to elektm93)
+
+ * menu: add new command 'ausrc' to switch audio source
+ add new command 'auplay' to switch audio player
+
+ * sdl2: add more pixelformats (ref #202)
+ (thanks Harald Gutmann)
+
+ * sndfile: add config to specify path for dump files (thanks Elektm93)
+ add test for sndfile on *BSD. (#194) (thanks jungle-boogie)
+
+ * swscale: get dst-size from config (ref #203)
+
+ * v4l2_codec: Video device selection bug (#218)
+ (thanks Richard Perez)
+
+
+2016-12-23 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.0
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.5.0
+ * NOTE: Requires libre v0.5.0 or later
+ Requires librem v0.5.0 or later
+
+ * new modules:
+ - av1 Experimental AV1 video codec
+ - debug_cmd Debug commands for advanced users
+ - pcp Port Control Protocol (PCP) for NAT traversal
+ - swscale Video scaling using FFmpeg's libswscale
+
+ * config:
+ - call_max_calls Maximum number of calls per account
+
+ * baresip-core:
+ - call: add multiple lines
+ - call: start video on reinvite (thanks Gary Metalle)
+ - cmd: add support for long commands
+ - cmd: make it re-entrant
+ - config: add some modules to template (thanks Dmitrij D. Czarkoff)
+ - contact: make it re-entrant
+ - play: make it re-entrant
+ - vidcodec: add a intraframe-flag to api
+ - video: resend FIR until Intra frame received
+
+ * selftest:
+ - add test for DTMF in call
+ - add test for contacts
+ - add test for long commands
+ - add test for maximum calls
+ - add test for multiple calls
+ - add test for video call
+ - add audio-source mock
+ - add video-codec mock
+ - add video-display mock
+ - add video-source mock
+
+ * Modules:
+
+ * aufile: convert samples from little-endian to host-endian
+
+ * auloop: use long commands /auloop and /auloop_stop
+
+ * av1: new module for Experimental AV1 video codec
+
+ * avcodec: add config option 'avcodec_h264enc' to set encoder name
+ (thanks to @hargut)
+
+ * avformat: fix init and warnings (thanks Maciej Koman)
+
+ * b2bua: use long command /b2bua
+
+ * contact: use long commands
+
+ * debug_cmd: new module for advanced debug commands
+
+ * g7221: expose spandsp api (thanks to Steve Underwood)
+
+ * gtk: use long command /gtk
+
+ * h265: add 'profile-id=1' to SDP
+
+ * menu: add long commands
+ add command 'line' or '@' to set current call
+
+ * opengl: fix deprecated warnings on OSX 10.12
+
+ * opensles: add support for stereo
+ (thanks to Juha Heinanen and Vijay Pratap Singh)
+
+ * opus: add support for SDP parameter mirroring
+ (thanks to Sveriges Radio)
+
+ * pcp: new module for Port Control Protocol (PCP) NAT traversal
+ requires librew (https://github.com/alfredh/rew)
+
+ * plc: expose spandsp api (thanks to Steve Underwood)
+
+ * presence: add long commands /presence_{on,off}line
+
+ * snapshot: use long commands (thanks Dmitrij D. Czarkoff)
+
+ * sndio: use driver-suggested buffer size (thanks Dmitrij D. Czarkoff)
+
+ * swscale: new module for video filter using libswscale
+
+ * v4l2: pick up VID_FMT_NV12 and VID_FMT_NV21 formats as well (#176)
+ don't check for native/emulated format (#179)
+ (thanks Dmitrij D. Czarkoff)
+
+ * vidloop: use long commands
+
+ * vp8: add 'intra' parameter to decoder api
+ fix building with old versions of libvpx
+
+ * wincons: graceful closing of thread (fixes #151)
+ (thanks to @GGGO)
+
+ * zrtp: use long command
+
+
+2016-07-22 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.20
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.4.20
+ * NOTE: Requires libre v0.4.17 or later
+ Requires librem v0.4.7 or later
+
+ * new modules:
+ - pulse Pulseaudio driver
+ - vp9 VP9 video codec
+
+ * config:
+ - audio_path Path to audio files
+ - call_local_timeout Timeout for incoming calls
+ - redial_attempts Number of redial attempts
+ - redial_delay Redial delay in seconds
+
+ * baresip-core:
+ - baresip: added a global baresip instance (WIP)
+ - call: add RTP timeout (thanks to Sveriges Radio)
+ - config: added call_local_timeout for incoming call timeout
+ - config: added compile-time configureable CONFIG_PATH
+ - config: added 'audio_path' config variable (thanks Juha Heinanen)
+ - net: made it re-entrant with struct network
+ - ua: added uag_set_exit_handler
+ - ua: fix bug with reg_uri limited to 64-chars
+ - video: vidfilters should not modify decoded image
+
+ * selftest:
+ - add test for network
+ - add test for sending SIP OPTIONS
+ - add test for RTP timeout
+
+ * Modules:
+
+ * avcodec: fix usage of deprecated API
+
+ * avformat: remove support for scaling
+ fix usage of deprecated API
+
+ * cons: relay log-messages to active UDP/TCP connections
+ https://github.com/alfredh/baresip/issues/144
+
+ * h265: fix usage of deprecated API
+
+ * menu: added support for re-dial on failure
+ (thanks to Sveriges Radio)
+
+ * mpa: Bug with reinit of codec structs (thanks Christian Hoene)
+
+ * natpmp: added support for RTCP
+
+ * presence: use correct struct in deref handler
+
+ * pulse: new module for Pulseaudio driver
+ (thanks to Matthias Apitz for testing)
+
+ * vidloop: vidfilters should not modify decoded image
+
+ * vp8: module renamed from vpx.so to vp8.so
+
+ * vp9: new module implementing VP9 video codec
+
+ * wincons: use ReadConsoleInput, thanks to GGGO (fixes #139)
+ https://github.com/alfredh/baresip/issues/139
+
+
+2016-05-20 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.19
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.4.19
+ * NOTE: Requires libre v0.4.14 or later
+ Requires librem v0.4.7 or later
+
+ * new modules:
+ - mpa MPA Speech and Audio Codec (thanks Christian Hoene)
+
+ * baresip-core:
+ - audio: remove is_g722 exception
+ use aucodec's rtp clockrate for calculating RTP timestamp
+ plc: make sure sampc is exactly one ptime frame
+ - aucodec: split srate into DSP srate and RTP clockrate
+ (these are different for e.g. G.722 and MDA)
+ - mos: add mos_calculate() (thanks Lorenzo Mangani)
+ - net: use configured dns servers only, if specified
+ - ua: fix potential NULL-pointer crash for uag.cfg
+
+ * selftest:
+ - add test for SIP registration with DNS
+ - add test for SIP registration with authentication
+ - add test for MOS calculations
+ - added a mock DNS Server
+ - added a mock SIP Server
+
+ * Modules:
+
+ * aucodec: add support for NV12 and YUVJ420P pixel formats
+
+ * daala: update to libdaala version 0.0-1564-g79787c7
+
+ * gtk: fix autodetection of libgtk+ 2.0 (thanks Charles Lehner)
+
+ * h265: remove call to x265_cleanup, caused crash on OpenBSD
+
+ * mpa: new module that implements MPA Speech and Audio Codec
+ (this module was contributed by Christian Hoene)
+
+ * opus: added new configuration parameters:
+ opus_cbr {yes,no} # Constant Bitrate (inverse of VBR)
+ opus_inbandfec {yes,no} # Enable inband FEC
+ opus_dtx {yes,no} # Enable DTX
+
+ * presence: improved interoperability, allow white space before
+ xml element closing tags (thanks Juha Heinanen)
+
+ * x11: added borderless window (thanks Doug Blewett)
+
+
+2016-03-12 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.18
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.4.18
+ * NOTE: Requires libre v0.4.14 or later
+ Requires librem v0.4.7 or later
+
+ * baresip-core:
+ - call: fix SIP INFO with dtmf-relay (thanks Gary Metalle)
+ - ua: add event UA_EVENT_CALL_CLOSED for ua_hangup()
+
+ * selftest:
+ - add tests for answer a call and hangup
+
+ * Modules:
+
+ * alsa: fix potential crash (thanks Gary Metalle)
+
+ * audiounit: fix compilation for iOS (issue #91)
+
+ * avcodec: fix compilation for FFmpeg 3.0
+
+ * avformat: fix compilation for FFmpeg 3.0
+
+ * gtk: always handle incoming calls (thanks Charles Lehner)
+
+ * h265: fix compilation for FFmpeg 3.0
+
+ * menu: add config 'menu_bell off/on' to enable Bell alert
+ add command 'A' for switch audio device (thanks AlexMarlo)
+
+ * v4l2_codec: add list of encoders (fixes #99)
+
+
+2016-01-17 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.17
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT tag: v0.4.17
+ * NOTE: Requires libre v0.4.14 or later
+ Requires librem v0.4.7 or later
+
+ * new modules:
+ - echo Echo server module
+ - jack JACK Audio Connection Kit audio-driver
+
+ * baresip-core:
+ - config: keep config object in memory
+ - ua: moved playing of ringtones out of core, to "menu" module
+ (let's keep the core nice and slim..)
+ - ui: added ui_password_prompt()
+
+ * selftest:
+ - silence debug/info log by default, only print warnings
+ (use -v to see verbose logging)
+
+ * Modules:
+
+ * alsa: added config option to specify the sample format
+ "alsa_sample_format {s16,float,s24_3le}"
+ thanks to Ola Palm for valuable feedback
+
+ * audiounit: fix recording on OSX (thanks Sebastian Reimers)
+ print hardware samplerate in debug mode
+
+ * auloop: add support for 44100 Hz samplerate
+
+ * daala: update to latest libdaala API (thanks Dmitrij D. Czarkoff)
+
+ * echo: new module which implements a simple Echo-server, to
+ be used in combination with the aubridge.so module.
+ contributed by Sebastian Reimers
+
+ * gtk: fixes to support C89 compiler (thanks Dmitrij D. Czarkoff)
+
+ * jack: new module which implements audio-driver for JACK
+
+ * menu: playing of ringtones moved here, from ua.c
+
+ * sndio: fix crash when device open fails (thanks Dmitrij D. Czarkoff)
+
+
+2015-12-01 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.16
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT commit bed2241da3261e472f09b21958f0cc1324a94f27
+ * GIT tag: v0.4.16
+ * NOTE: Requires libre v0.4.14 or later
+
+ * new modules:
+ - v4l2_codec Video4Linux2 video codec (H264 hardware encoding)
+ - vidinfo Video info overlay module
+
+ * baresip-core:
+ - audio: add audio_set_source() and audio_set_player()
+ - audio: flush tx-buffer for all modes (thanks Thibault Gueslin)
+ - call: add call_is_outgoing()
+ - call: check address-family of incoming SDP offer (thanks Olle)
+ - h264: move H.264 packetization code to core
+ - main: add -u option to append extra global UA parameters
+ - main: pre-load modules after all arguments are parsed
+ - ua: add events UA_EVENT_SHUTDOWN,UA_EXIT
+ - ua: add ua_hold_answer()
+ - ua: add ua_set_media_af()
+ - ua: delay mod-unloading if mods has a ref to struct ua
+
+ * build:
+ - add verbose build with V=1 (thanks Dmitrij D. Czarkoff)
+ - add pkg-config file (thanks William King)
+ - add travis.yml file for Github build-system
+
+ * Modules:
+
+ * alsa: fix memory leaks
+
+ * avcodec: move common H.264 packetization code to core
+
+ * cairo: use pkg-config in makefile
+
+ * daala: update to latest libdaala (thanks Dmitrij D. Czarkoff)
+
+ * gst_video: use H.264 packetization API from core
+
+ * gst_video1: use H.264 packetization API from core
+
+ * gtk: fix segmentation fault on window close
+
+ * mwi: add 500ms delay after closing subscription
+
+ * oss: use pthread for ausrc instead of fd_listen (fixes FreeBSD)
+
+ * presence: use sipevent_sock instance from UA core
+ add 500ms delay after closing subscription
+
+ * v4l2_codec: new module
+
+ * vidinfo: new module
+
+ * zrtp: fix ZRTP over TURN by moving helper to layer 10
+ fix ZID verification (thanks Ingo Feinerer)
+
+
+2015-09-26 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.15
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT commit 86262a6fc17e19e2be82eb8a2a05ec0f884d3d38
+ * GIT tag: v0.4.15
+ * NOTE: Requires libre v0.4.13 or later
+
+ * added selftest binary
+
+ * baresip-core:
+ - audio: fix televent when pt != 101 (reported by AndyJRobinson)
+ - magic: use __func__ for C99 or later
+ - sip: make sip_req_send() public
+ - ua: add UA_EVENT_CALL_DTMF_START/END, thanks Gary Metalle
+
+ * Modules:
+
+ * alsa: added extra logging
+
+ * gtk: add support for libnotify (thanks Charles Lehner)
+
+ * video: fix potential null deref (thanks Tomasz Ostrowski)
+
+ * zrtp: added 36-bytes preamble for TURN-header
+
+
+2015-08-08 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.14
+
+ * GIT URL: https://github.com/alfredh/baresip.git
+ * GIT commit ebac23b0692de71ee4c3a436f0372013150c937f
+ * GIT tag: v0.4.14
+ * NOTE: Requires libre v0.4.13 or later
+
+ * new modules:
+ - gtk GTK+ 2.0 UI (thanks Charles E. Lehner)
+ - gst1 Gstreamer 1.0 audio module
+ - gst_video1 Gstreamer 1.0 video module (thanks Thomas Strobel)
+ - daala Experimental video-codec using Daala
+
+ * baresip-core:
+ - baresip: added -m argument to pre-load modules
+ - config: add kqueue to sample config (thanks Dmitrij D. Czarkoff)
+ - log: make code C89 compliant (thanks Victor Sergienko)
+ - module: added module_preload()
+ - ua: add CALL_EVENT_TRANSFER_FAILED
+ - ua: skip initial white space from uri (thanks Juha Heinanen)
+ - ua: ua_prev_call()
+ - videnc: move videnc_packet_h to update-handler
+
+ * build:
+ - added optional $(MOD)_CFLAGS for local module CFLAGS
+ - added project file for Visual C++ Express 2010
+ - freebsd: add include path to $(SYSROOT)/local/include
+ (thanks Hellmuth Michaelis)
+
+ * Modules:
+
+ * avcodec: make code C89 compliant (thanks Victor Sergienko)
+
+ * cons: make code C89 compliant (thanks Victor Sergienko)
+
+ * daala: new module
+
+ * dshow: updates for VC2010 (thanks Victor Sergienko)
+
+ * gst1: new module
+
+ * gst_video1: new module
+
+ * gtk: new module
+
+ * menu: fix crash when 0 UAs (thanks Hans Petter Selasky)
+ added command 'H' to hold previous call (thanks xanm)
+
+ * wincons: make code C89 compliant (thanks ggcoding)
+
+
+2015-06-20 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.13
+
+ * GIT commit 2e3e825ef5532dfde5a8b52de9ebaac51aa20a9c
+ * NOTE: Requires libre v0.4.12 or later
+
+ * new modules:
+ - aufile Audio module for using a WAV-file as audio input
+ - b2bua Back-to-Back User-Agent (B2BUA) module
+ - codec2 CODEC2 audio codec
+ - gst_video Gstreamer video codec
+ - h265 H.265 (HEVC) video codec
+
+ * baresip-core:
+ - contact: add support for access-control (thanks Doug Blewett)
+ - ausrc: change base-class to a const pointer
+ - auplay: change base-class to a const pointer
+ - vidsrc: change base-class to a const pointer
+ - vidisp: change base-class to a const pointer
+ - video: smooth sending of video packets
+
+
+ * Modules:
+
+ * amr: added support for octet-align mode (thanks to Stefan Sayer)
+
+ * aubridge: copy audio-samples if resampler not needed
+
+ * aufile: new module for using a WAV-file as audio source
+
+ * avcapture: only register 1 video source
+
+ * avformat: fix segfault on recent versions of libav
+
+ * b2bua: new experimental module
+
+ * codec2: new module for CODEC2 audio codec
+
+ * dtls_srtp: uppercase fingerprint, interop (thanks Juha Heinanen)
+ alternative SDP protocols for interop
+
+ * dtmfio: unregister event handler on close (thanks Hellmuth Michaelis)
+
+ * gst_video: new module using Gstreamer as a video codec
+ (Thanks to Victor Sergienko and Fadeev Alexander)
+
+ * h265: new module for H.265 video codec
+
+ * httpd: added raw mode (thanks Lorenzo Mangani)
+
+ * menu: create user-agent with a command 'R' (thanks Lorenzo Mangani)
+
+ * opus: add configuration of "opus_bitrate"
+ (thanks to Juha Heinanen)
+
+ * speex: add configuration of "speex_mode_nb" and "speex_mode_wb"
+ (thanks to Dmitrij D. Czarkoff and Juha Heinanen)
+
+ * vidloop: add VIDLOOP_INTERNAL_FMT and split encoder/decoder
+
+ * x11: catch Window delete (thanks to Doug Blewett)
+
+ * zrtp: initialize remote_zid (thanks to Ingo Feinerer)
+
+
+2014-12-24 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.12
+
+ * GIT commit 67993e35d980375458348b264c4a35a944bb5180
+ * NOTE: Requires libre v0.4.11 or later
+
+ * baresip:
+ - account: add regint and pubint
+ - audio: fix checking of sample-rate range
+ - config: remove the "input" block
+ - config: added support for quoted device parameters
+ - config: fix conversion of bandwidth to kbit/s
+ - config: generate more relevant config for FreeBSD and OpenBSD
+ (thanks Dmitrij D. Czarkoff)
+ - reg: add support for extracting GRUU parameter
+ - main: add -p option to set path to audio files
+ - sipreq: make response-handler optional
+ - ua: add support for GRUU (RFC 5627)
+ (many thanks to Juha Heinanen for starting this work and
+ helping out with the testing)
+ - ua: moved presence-status to each struct ua instance
+ - ua: add presence status to each User-Agent instance
+ - ua: use public-GRUU if set, otherwise local cuser
+ - ui: make UI single instance
+ - video: add VIDENC_INTERNAL_FMT (suggested by Victor Sergienko)
+
+ * docs: added sample configuration files
+
+ * account: added pubint for Publishing Interval
+
+ * avcodec: upgrade to recent ffmpeg/libav APIs
+ either FFmpeg or libav can be used
+
+ * celt: deleted module (replaced by opus)
+
+ * cons: update usage of struct ui, added output handler
+ added config: cons_listen 0.0.0.0:5555
+
+ * evdev: update usage of struct ui, added output handler
+ added config: evdev_device /dev/input/event0
+
+ * httpd: added ui output handler
+
+ * menu: added command 'o' for sending OPTION request
+ (thanks to Juha Heinanen)
+
+ added command 'D' for accepting incoming calls
+
+ * mwi: subscribe to MWI after Registration succeeded
+ (thanks to Juha Heinanen)
+
+ * opensles: add double-buffering and some tuning
+ (thanks to Francesco Bradascio)
+
+ * opus: added config "opus_bitrate" (thanks to Sebastian Reimers)
+
+ * presence: added support for PUBLISH (thanks to Juha Heinanen)
+ interop fixes and tuning
+
+ * stdio: update usage of struct ui, added output handler
+
+ * uuid: use internal version of generating UUID
+
+ * v4l2: use memory mapped mode only
+
+ * vumeter: dont call tmr_start from non-RE thread
+
+ * wincons: update usage of struct ui, added output handler
+
+ * winwave: fix bug when closing player device
+ (thanks to Tomasz Ostrowski)
+ add support for mapping device name to index
+
+ * zrtp: add support for verify SAS (thanks to Ingo Feinerer)
+
+
+2014-06-21 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.11
+
+ * GIT commit 7a465f2eb92f4e32740093e5ad4970d528908c51
+
+ * baresip:
+ - audio: added audio_ismuted() to get audio mute status
+ - audio: fix timestamp generation for stereo-streams
+ - audio: send outgoing audio-packets as soon as possible
+ - audio: upgrade to sample-based ausrc/auplay API
+ - auplay: change API to use samples instead of 8-bit buffer
+ - auplay: remove option to specify sample format (always S16LE)
+ - ausrc: change API to use samples instead of 8-bit buffer
+ - ausrc: remove option to specify sample format (always S16LE)
+ - call: added support for X-RTP-Stat header (thanks Lorenzo Mangani)
+ - call: check for common audio-codecs (thanks Juha Heinanen)
+ - logging: use info() instead of DEBUG_INFO();
+ - logging: use warning() instead of DEBUG_WARNING()
+ - play: convert WAV-file from little-endian to native-endian
+ - removed support for Symbian OS
+
+ * debian: upgrade debian files
+
+ * avcapture: also build for MacOSX
+
+ * alsa: fix sample-endianess with SND_PCM_FORMAT_S16
+ upgrade to sample-based ausrc/auplay API
+
+ * audiounit: upgrade to sample-based ausrc/auplay API
+
+ * auloop: upgrade to sample-based ausrc/auplay API
+
+ * coreaudio: upgrade to sample-based ausrc/auplay API
+
+ * dtls_srtp: use DTLS code from libre (needs libre v0.4.9 or later)
+ use SRTP code from libre (needs libre v0.4.9 or later)
+
+ * dtmfio: new module to send DTMF-events via FIFO file
+ (contributed by Aaron Herting)
+
+ * fakevideo: new module for fake video input/output driver
+
+ * gst: upgrade to sample-based ausrc/auplay API
+
+ * ice: set default candidates for ICE-lite
+
+ * libsrtp: module 'srtp.so' renamed to 'libsrtp.so'
+
+ * mda: Symbian MDA audio driver was deleted
+
+ * menu: fix issue with audio-mute on multiple calls
+
+ * opensles: upgrade to sample-based ausrc/auplay API
+
+ * oss: upgrade to sample-based ausrc/auplay API
+
+ * portaudio: upgrade to sample-based ausrc/auplay API
+
+ * rst: upgrade to sample-based ausrc/auplay API
+
+ * selftest: new module for testing the baresip core api
+
+ * sndio: new module for OpenBSD audio driver
+ (It was contributed by Dmitrij D. Czarkoff, thank you!)
+
+ * srtp: module is now using SRTP-stack from libre (v0.4.9 or later)
+
+ * syslog: use logging framework to get messages
+
+ * v4l2: add format negotiation and OpenBSD support
+ (contributed by Dmitrij D. Czarkoff)
+
+ * winwave: upgrade to sample-based ausrc/auplay API
+
+
+2014-01-23 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.10
+
+ * baresip:
+ - account: add account_set_display_name() -- thanks Dimitris
+ - audio: use both srate/channels to check if resampler is needed
+ - aufilt: change from frame_size to ptime
+ - auplay: change from frame_size to ptime
+ - ausrc: change from frame_size to ptime
+ - config: add optional ausrc_channels and auplay_channels
+ - config: create config dir with mode 0700 (suggested by Jann Horn)
+ - play: update auplay usage with ptime
+
+ * alsa: update to new ausrc/auplay API with ptime
+ fix bug when snd_pcm_readi() returns -EPIPE (thanks Remik)
+ open device from main thread instead of alsa-thread (thanks EL)
+ (caused problems with Sennheiser Century SC 660 + USB adapter)
+
+ * auloop: minor cleanups and improvements
+
+ * coreaudio: update to new ausrc/auplay API with ptime
+
+ * gst: update to new ausrc/auplay API with ptime
+
+ * l16: fix a bug with sample count
+
+ * opus: fix a memory corruption error in opus_decode_pkloss()
+
+ * oss: update to new ausrc/auplay API with ptime
+
+ * plc: update to new aufilt API with ptime
+
+ * portaudio: update to new ausrc/auplay API with ptime
+ fix bugs when using channels=2 (stereo)
+ configure device index using "device" parameter
+
+ * rst: update to new ausrc/auplay API with ptime
+
+ * speex_aec: update to new aufilt API with ptime
+
+ * speex_pp: update to new aufilt API with ptime
+
+ * winwave: update to new ausrc/auplay API with ptime
+
+ * zrtp: update to use libzrtp from Travis Cross' github
+ use config dir to store ZRTP cache-file (thanks Juha Heinanen)
+
+
+2014-01-06 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.9
+
+ * new modules:
+ - zrtp Media Path Key Agreement for Unicast Secure RTP
+
+ * build:
+ - added support for LLVM clang compiler
+
+ * baresip:
+ - account: add account_laddr()
+ - audio: upgrade to new librem auresamp API
+ - config: use oss,/dev/dsp as default device for FreeBSD
+ - log: added new logging framework
+ - main: added new verbose debug argument (-v)
+ - net: added sanity check for HAVE_INET6 build flag
+ - play: added play_set_path() -- thanks to Dimitris P.
+ - ua: added uag_find_param()
+ - ua: fix param-bug in ua_connect() -- thanks to Juha Heinanen
+
+ * aubridge: upgrade to new librem auresamp API
+
+ * avcodec: use new av_frame_alloc() api
+
+ * celt: deprecate CELT-module, use OPUS instead
+
+ * opengles: fix warnings (thanks to Dimitris P.)
+
+ * opensles: fix bugs in player and recorder
+
+ * opus: encode/decode sdp parameters as of I-D
+
+ * speex_resamp: module removed, replaced by librem's resampler
+
+ * zrtp: new module for ZRTP media encryption (use ;mediaenc=zrtp)
+
+
+2013-12-06 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.8
+
+ * new modules:
+ - dtls_srtp DTLS-SRTP media encryption module (RFC 5763,5764)
+ - aubridge Audio Bridge to connect auplay->ausrc
+ - vidbridge Video Bridge module to connect vidisp->vidsrc
+
+ * baresip:
+ - added RFC 5576 Source-Specific Media Attributes in SDP
+ - audio: set SDP bandwidth only if "rtp_bandwidth" config set
+ - play: do not store a copy of global config
+ - stream: save RTCP statistics from Sender-reports
+ - stream: add SDP ssrc attribute
+ - stream: added metrics for packets/bytes transmit/receive
+ - ua: added uag_current()/_set() to get/set current User-Agent
+ - video: set maximum RTP packet-size to 1024 bytes
+
+ * config:
+ - added "video_display module,device" for Video Display
+ - added "rtp_stats {off,on}" for RTP Statistics after Call
+ - default RTP bandwidth is now 0-0
+
+ * contact: dynamic command description for "Message" handling
+ dial from current UA (thanks to Simon Liebold)
+
+ * isac: upgrade to draft-ietf-avt-rtp-isac-04
+
+ * srtp: added auto-negotiation of RTP-profile for incoming calls
+ (RTP/AVP, RTP/AVPF, RTP/SAVP, RTP/SAVPF)
+
+ * vidloop: fix memory leak
+
+
+2013-11-12 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.7
+
+ * new modules:
+ - httpd HTTP webserver UI module
+
+ * baresip:
+ - added RFC 5506 Support for Reduced-Size RTCP
+ - audio: minor cleanups
+ - cmd: ignore RELEASE key in editor mode
+ - conf: add conf_get_sa()
+ - mnat: add address family (af) to session handler
+ - realtime: fixes for iOS (thanks Dimitris)
+ - ua: make ua_register() public
+ - ua: add ua_calls() to get list of calls
+ - ua: only create register client if regint > 0
+
+ * debian: update dependencies (thanks Juha Heinanen)
+
+ * rpm: added RPM package spec file
+
+ * alsa: open device from thread to avoid blocking re-main loop
+
+ * avcodec: build fixes for Debian Testing
+
+ * avformat: use sys_msleep()
+
+ * contact: improve matching logic (thanks EJC Lindner)
+
+ * dshow: initialize variables (found with cppcheck)
+
+ * evdev: fix formatted printing (found with cppcheck)
+
+ * ice: use address family (AF) from call
+
+ * ilbc: update to separate encoder/decoder states (thanks Dimitris)
+
+ * snapshot: initialize variables (found with cppcheck)
+
+ * stun: use address family (AF) from call
+
+ * turn: use address family (AF) from call
+
+ * uuid: fix usage of strncat()
+
+
+2013-10-11 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.6
+
+ * new modules:
+ - directfb DirectFB video display module (thanks Andreas Shimokawa)
+ - dshow Windows DirectShow vidsrc (thanks Dusan Stevanovic)
+ - wincons Console input driver for Windows
+
+ * baresip:
+ - audio: print audio-pipelines in console/debug
+ - aufilt: split into separate encoder+decoder states
+ - call: add local uri/name, dtmf-handler
+ - call: fix decoding of DTMF/SIP-INFO for '*' and '#'
+ - export CALL_EVENT_* in public API
+ - fix various clang warnings
+ - sipreq: use outbound proxy if specified (thanks EJC Lindner)
+ - ua: add possibility to specify 'struct call' for hangup/answer
+ - ua: move SIP extensions into a dynamic vector container
+ - ua: move playing of tones from call.c to ua.c
+ - vidfilt: split into separate encoder+decoder states
+ - vidisp: remove input handler
+
+ * menu: improve call-transfer handling
+
+ * plc: update to separate encoder/decoder states
+
+ * selfview: update to separate encoder/decoder states
+
+ * snapshot: remove state which was not needed
+
+ * sndfile: update to separate encoder/decoder states
+ print unique timestamp to saved files
+
+ * speex_aec: update to separate encoder/decoder states
+
+ * speex_pp: update to separate encoder/decoder states
+
+ * vidloop: update to separate encoder/decoder vidfilt states
+
+ * vumeter: update to separate encoder/decoder states
+
+ * wincons: new module for Console input on Win32
+
+
+2013-08-31 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.5
+
+ * new modules:
+ - account Account loader module
+ - natpmp NAT-PMP client (RFC 6886)
+ - sdl2 Video display using libSDL2
+
+ * baresip:
+ - account: added SIP account parser and container
+ - config: split conf.c into conf.c and config.c
+ - config: move enum audio_mode to struct config
+ - config: move uuid to struct config
+ - more usage of the #ifdef USE_VIDEO macro
+ - message: add handling of SIP MESSAGE send/recv
+ - mediaenc: added rtp_sock parameter to media-handler
+ - ua: cleanup public struct ua API
+ - vidisp api: remove unused 'parent' parameter
+ - call: handle incoming DTMF in SIP INFO (application/dtmf-relay)
+ - sdp: added sdp_decode_multipart()
+ - net: fix bug on IP-refresh when 'net_interface' is used
+ - video: minor cleanups
+ handle incoming RTCP_RTPFB_GNACK
+
+ * isac: fix encode_update() signature
+
+ * menu: move dialbuffer here from ua.c
+ added command 'g' to print current config
+
+ * mwi: multiple MWIs for multiple UAs
+
+ * presence: include supported methods in SIP messages
+
+ * srtp: improved interop and debugging
+ handle incoming RTP/RTCP-demultiplexing
+
+ * uuid: write loaded UUID directly to struct config
+
+ * vidloop: added video-filters
+
+
+2013-05-18 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.4
+
+ * new modules:
+ - g726 G.726 audio codec
+ - mwi Message Waiting Indication
+ - snapshot Save video-stream as PNG images
+
+ * config:
+ - added 'sip_certificate' to use a Certificate for SIP/TLS
+ - added 'ausrc_srate' and 'auplay_srate' to force DSP samplerate
+
+ * baresip:
+ - added a simple BFCP client
+ - aufilt: improved API
+ - mediaenc: improved API with session state
+ - ua: added event handler framework
+ - aucodec: improved API with separate encode/decode state
+ - vidcodec: improved API with separate encode/decode state
+ - sdp.c: added SDP helper functions
+ - ua: move registration client to reg.c
+ - audio: added internal resampler
+
+ * auloop: added config option 'auloop_codec' for setting codec
+
+ * ice: remove old 'ice_interface' config option
+
+ * menu: move handling of status-mode here
+
+ * selfview: added config option 'selfview_size'
+
+ * vp8: upgrade to draft-ietf-payload-vp8-08
+
+ * winwave: cleanup and minor fixes
+
+
+2013-01-01 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.3
+
+ * new modules:
+ - selfview Video selfview as video-filter module
+ - vumeter Audio-filter module to display recording/playback level
+
+ * config:
+ - added 'net_interface" to bind to a specific network interface
+ - added accounts 'regq' parameter for SIP Register client
+
+ * baresip:
+ - added video-filter plugin API (vidfilt)
+ - audio.c: cleanups, split into transmit/receive part
+ - ua: added SIP Allow-header (thanks Juha Heinanen)
+ - ua: added Register q-value (thanks Juha Heinanen)
+ - ua: fix DTMF end event bug
+
+ * avcodec: fix x264 fps bug (thanks Trevor Jim)
+
+ * ice: only include ufrag/pwd in session SDP (thanks Juha Heinanen)
+
+
+2012-09-09 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.2
+
+ * new modules:
+ - auloop Audio-loop test module
+ - contact Contacts module
+ - isac iSAC audio codec
+ - menu Interactive menu
+ - opengles OpenGLES video output
+ - presence Presence module
+ - syslog Syslog module
+ - vidloop Video-loop test module
+
+ * baresip:
+ - added support for call transfer
+ - added support for call waiting
+ - added multiple calls per user-agent
+ - added multiple registrations per user-agent
+ - cmd: added new command interface
+ - ua: handle SIP Require header for incoming calls
+ - ui: cleanup, use dynamic interactive menu
+
+ * config:
+ - added 'audio_alert' for ringtones etc.
+ - added 'outboundX=proxy' for multiple outbound proxies
+ - added 'module_tmp' for temporary module loading
+ - added 'module_app' for application modules
+
+ * avcodec: upgrade to latest FFmpeg and fix pts bug
+
+ * natbd: register command 'z' for status
+
+ * srtp: fix memleak on close
+
+ * uuid: added UUID loader
+
+
+2012-04-21 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.1
+
+ * baresip: do not include rem.h from baresip.h
+ rename struct conf to struct config
+ vidsrc API: move size to alloc handler
+ aucodec API: change fmtp type to 'const char *'
+ add SDP fmtp compare handler
+ vidcodec API: added enqueue and packetizer handlers
+ remove size from vidcodec_prm
+ remove decoder parameters from alloc
+ change fmtp type to 'const char *'
+ add SDP fmtp compare handler
+ remove aufile.c, use librem instead
+ audio: fix Telev timestamp (thanks Paulo Vicentini)
+ configurable order of playback/source start
+ ua_find: match AOR for interop (thanks Tomasz Ostrowski)
+ ua: more robust parsing for incoming MESSAGE
+ ua: password prompt (thanks to Juha Heinanen)
+
+ * build: detect amr, cairo, rst, silk modules
+
+ * config: split 'audio_dev' parameter into 'audio_player/audio_source'
+ order of audio_player/audio_source decide opening order
+ rename 'video_dev' parameter to 'video_source'
+ added optional 'auth_user=NAME' account parameter
+ (idea was suggested by Juha Heinanen)
+
+ * alsa: play: no need to call snd_pcm_start(), explictly started when
+ writing data to the device. (thanks to Christof Meerwald)
+
+ * amr: more portable AMR codec
+
+ * avcodec: automatic size from encoded frames
+ detect packetization-mode from SDP format
+ use enqueue handler
+
+ * avformat: update to latest versions of ffmpeg
+
+ * cairo: new experimental video source module
+
+ * cons: added support for TCP
+
+ * evdev: added KEY_KPx (thanks to ccwufu on OpenWRT forum)
+
+ * g7221: use bitrate from decoded SDP format
+ added optional G722_PCM_SHIFT for 14-bit compat
+
+ * rst: thread-based video source
+
+ * silk: fix crash, init encoder, bitrate=64000 and complexity=2
+ (reported by Juha Heinanen)
+
+ * srtp: decode SDES lifetime and MKI
+
+ * v4l, v4l2: better module detection for FreeBSD 9
+ do not include malloc.h
+ (thanks to Matthias Apitz)
+
+ * vpx: auto init of encoder
+
+ * winwave: fix memory leak (thanks to Tomasz Ostrowski)
+
+ * x11: add support for 16-bit graphics
+
+
+2011-12-25 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.0
+
+ * updated doxygen comments (thanks to Olle E. Johansson)
+
+ * docs: added modules description
+
+ * baresip: add ua_set_aumode(), configurable audio-tx mode
+ vidsrc API: added media_ctx shared with ausrc
+ ausrc API: add media_ctx shared with vidsrc
+ audio_encoder_set() - stop audio source first
+ audio_decoder_set() - include SDP format parameters
+ aufile: add PREFIX to share path (thanks to Juha Heinanen)
+ natbd.c: move code to a new module 'natbd'
+ get_login_name: check both LOGNAME and USER
+ ua.c: unique contact-user with address of struct ua
+ ua.c: find correct UA for incoming SIP Requests
+ ua_connect: param is optional (thanks to Juha Heinanen)
+ video: add video_set_source()
+
+ * amr: minor improvements
+
+ * audiounit: new module for MacOSX/iOS audio driver
+
+ * avcapture: new module for iOS video source
+
+ * avcodec: fixes for newer versions of libavcodec
+
+ * gsm: handle packet-loss
+
+ * natbd: move to separate module from core
+
+ * opengl: fix building on MacOSX 10.7
+ (thanks to David Jedda and Atle Samuelsen)
+
+ * opus: upgrade to opus v0.9.8
+
+ * rst: use media_ctx for shared audio/video stream
+
+ * sndfile: fix stereo mode
+
+
+2011-09-07 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.3.0
+
+ * baresip: use librem for media processing
+ added support for video selfview
+ aubuf, autone, vutil: moved to librem
+ ua: improved API
+ conf: use internal parser instead of fscanf()
+ vidloop: cleanup, use librem for processing
+
+ * config: add video_selfview={pip,window} parameter
+
+ * amr: new module for AMR and AMR-WB audio codecs (RFC 4867)
+
+ * avcodec, avformat: update to latest version of FFmpeg
+
+ * coreaudio: fix building on MacOSX 10.5 (thanks David Jedda)
+
+ * ice: fix building on MacOSX 10.5 (thanks David Jedda)
+
+ * opengl: remove deps to libswscale
+
+ * opensles: new module OpenSLES audio driver
+
+ * opus: new module for OPUS audio codec
+
+ * qtcapture: remove deps to libswscale
+
+ * rst: new module for mp3 audio streaming
+
+ * silk: new module for SILK audio codec
+
+ * v4l, v4l2: remove deps to libswscale
+
+ * x11: remove deps to libswscale, use librem vidconv instead
+
+ * x11grab: remove deps to libswscale
+
+
+2011-05-20 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.2.0
+
+ * baresip: Added support for SIP Outbound (RFC 5626)
+ The SDP Content Attribute (RFC 4796)
+ RTP/RTCP Multiplexing (RFC 5761)
+ RTP Keepalive (draft-ietf-avt-app-rtp-keepalive-09)
+
+ * config: add 'outbound' to sipnat parameter (remove stun, turn)
+ add rtpkeep={zero,stun,dyna,rtcp} parameter
+ audio_codecs parameter can now specify samplerate
+ add rtcp_mux for RTP/RTCP multiplexing on/off
+
+ * alsa: set buffersize and fix samplesize (thanks to Luigi Rizzo)
+
+ * avcodec: added support for MPEG4 video codec (RFC 3016)
+ wait for keyframe before decoding
+
+ * celt: upgrade libcelt version and cleanups
+
+ * coreaudio: fix buffering in recorder
+
+ * ice: several improvements and fixes
+ added new config options
+
+ * ilbc: handle asymmetric modes
+
+ * opengl: enable vertical sync
+
+ * sdl: upgrade to latest version of libSDL from mercurial
+
+ * vpx: added support for draft-westin-payload-vp8-02
+
+ * x11: handle remote display with optional shared memory
+
+ * x11grab: new video-source module (thanks to Luigi Rizzo)
+
+ * docs: updated doxygen comments
diff --git a/docs/THANKS b/docs/THANKS
new file mode 100644
index 0000000..b7f0949
--- /dev/null
+++ b/docs/THANKS
@@ -0,0 +1,47 @@
+@GGGO
+@elektm93
+@glenvt18
+@jungle-boogie
+Aaron Herting
+AlexMarlo
+Andreas Shimokawa
+Atle Samuelsen
+Charles Lehner
+Christian Hoene
+David Jedda
+Dimitris P.
+Dmitrij D. Czarkoff
+Doug Blewett
+Dusan Stevanovic
+EJC Lindner
+Fadeev Alexander
+Gary Metalle
+Hans Petter Selasky
+Harald Gutmann
+Hellmuth Michaelis
+Ingo Feinerer
+Iwan BK
+Jan Hoffmann
+Jonathan Sieber
+Juha Heinanen
+Lorenzo Mangani
+Luigi Rizzo
+Maciej Koman
+Matthias Apitz
+Mikhail Barg
+Ola Palm
+Olle E. Johansson
+Richard Perez
+Sebastian Reimers
+Stefan Sayer
+Steve Underwood
+Thibault Gueslin
+Thomas Strobel
+Tomasz Ostrowski
+Tony Langley
+Trevor Jim
+Victor Sergienko
+William King
+
+
+Sveriges Radio
diff --git a/docs/TODO b/docs/TODO
new file mode 100644
index 0000000..e47f214
--- /dev/null
+++ b/docs/TODO
@@ -0,0 +1,11 @@
+TODO:
+
+-------------------------------------------------------------------------------
+
+Please see:
+
+ https://github.com/alfredh/baresip/issues
+
+ https://github.com/alfredh/baresip/wiki/Roadmap
+
+-------------------------------------------------------------------------------
diff --git a/docs/examples/accounts b/docs/examples/accounts
new file mode 100644
index 0000000..2b4a39c
--- /dev/null
+++ b/docs/examples/accounts
@@ -0,0 +1,58 @@
+#
+# SIP accounts - one account per line -- sample configuration
+#
+# Displayname <sip:user:password@domain;uri-params>;addr-params
+#
+# uri-params:
+# ;transport={udp,tcp,tls}
+#
+# addr-params:
+# ;answermode={manual,early,auto}
+# ;audio_codecs=speex/16000,pcma,...
+# ;auth_user=username
+# ;mediaenc={srtp,srtp-mand,srtp-mandf,dtls_srtp,zrtp}
+# ;medianat={stun,turn,ice}
+# ;outbound="sip:primary.example.com;transport=tcp"
+# ;outbound2=sip:secondary.example.com
+# ;ptime={10,20,30,40,...}
+# ;regint=3600
+# ;pubint=0 (publishing off)
+# ;regq=0.5
+# ;rtpkeep={zero,stun,dyna,rtcp}
+# ;sipnat={outbound}
+# ;stunserver=stun:[user:pass]@host[:port]
+# ;video_codecs=h264,h263,...
+#
+# Examples:
+#
+# <sip:user:secret@domain.com;transport=tcp>
+# <sip:user:secret@1.2.3.4;transport=tcp>
+# <sip:user:secret@[2001:df8:0:16:216:6fff:fe91:614c]:5070;transport=tcp>
+#
+
+
+#
+# A very basic example
+#
+<sip:user@iptel.org>
+
+
+#
+# Use SIP Outbound over TCP, with ICE for Media NAT Traversal, and DTLS-SRTP for encryption
+#
+<sip:user:pass@example.com>;sipnat=outbound;outbound="sip:example.com;transport=tcp";medianat=ice;mediaenc=dtls_srtp
+
+
+#
+# Use ICE for Media NAT Traversal, using a specific STUN-server
+#
+<sip:user:pass@example.com>;medianat=ice;stunserver="stun:username:password@stunserver.org"
+
+
+#
+# Force audio-codec 'opus' and video-codec 'vp8'
+#
+<sip:user:pass@example.com>;audio_codecs=opus/48000/2;video_codecs=vp8
+
+
+# ... more examples can be added here ...
diff --git a/docs/examples/config b/docs/examples/config
new file mode 100644
index 0000000..1eabd62
--- /dev/null
+++ b/docs/examples/config
@@ -0,0 +1,177 @@
+#
+# baresip configuration -- example for linux
+#
+
+#------------------------------------------------------------------------------
+
+# Core
+poll_method epoll # poll, select, epoll ..
+
+# SIP
+sip_trans_bsize 128
+#sip_listen 0.0.0.0:5060
+#sip_certificate cert.pem
+
+# Audio
+audio_player alsa,default
+audio_source alsa,default
+audio_alert alsa,default
+audio_srate 8000-48000
+audio_channels 1-2
+#ausrc_srate 48000
+#auplay_srate 48000
+#ausrc_channels 0
+#auplay_channels 0
+
+# Video
+#video_source v4l2,/dev/video0
+#video_display x11,nil
+video_size 352x288
+video_bitrate 512000
+video_fps 25
+
+# AVT - Audio/Video Transport
+rtp_tos 184
+#rtp_ports 10000-20000
+#rtp_bandwidth 512-1024 # [kbit/s]
+rtcp_enable yes
+rtcp_mux no
+jitter_buffer_delay 5-10 # frames
+rtp_stats no
+
+# Network
+#dns_server 10.0.0.1:53
+#net_interface wlan1
+
+# BFCP
+#bfcp_proto udp
+
+#------------------------------------------------------------------------------
+# Modules
+
+#module_path /usr/local/lib/baresip/modules
+
+# UI Modules
+module stdio.so
+#module cons.so
+#module evdev.so
+#module httpd.so
+
+# Audio codec Modules (in order)
+module opus.so
+#module silk.so
+#module amr.so
+#module g7221.so
+#module g722.so
+#module g726.so
+module g711.so
+#module gsm.so
+#module l16.so
+#module speex.so
+#module bv32.so
+
+# Audio filter Modules (in encoding order)
+#module vumeter.so
+#module sndfile.so
+#module speex_aec.so
+#module speex_pp.so
+#module plc.so
+
+# Audio driver Modules
+module alsa.so
+#module portaudio.so
+
+# Video codec Modules (in order)
+module avcodec.so
+module vpx.so
+
+# Video filter Modules (in encoding order)
+#module selfview.so
+
+# Video source modules
+#module v4l.so
+module v4l2.so
+#module avformat.so
+#module x11grab.so
+#module cairo.so
+
+# Video display modules
+module x11.so
+#module sdl2.so
+
+# Audio/Video source modules
+#module rst.so
+#module gst.so
+
+# Media NAT modules
+module stun.so
+module turn.so
+module ice.so
+#module natpmp.so
+
+# Media encryption modules
+#module srtp.so
+module dtls_srtp.so
+
+
+#------------------------------------------------------------------------------
+# Temporary Modules (loaded then unloaded)
+
+module_tmp uuid.so
+module_tmp account.so
+
+
+#------------------------------------------------------------------------------
+# Application Modules
+
+module_app auloop.so
+module_app contact.so
+module_app menu.so
+#module_app mwi.so
+#module_app natbd.so
+#module_app presence.so
+#module_app syslog.so
+module_app vidloop.so
+#module_app gtk.so
+
+
+#------------------------------------------------------------------------------
+# Module parameters
+
+
+cons_listen 0.0.0.0:5555
+
+evdev_device /dev/input/event0
+
+# Speex codec parameters
+speex_quality 7 # 0-10
+speex_complexity 7 # 0-10
+speex_enhancement 0 # 0-1
+speex_mode_nb 3 # 1-6
+speex_mode_wb 6 # 1-6
+speex_vbr 0 # Variable Bit Rate 0-1
+speex_vad 0 # Voice Activity Detection 0-1
+speex_agc_level 8000
+
+# Opus codec parameters
+opus_bitrate 28000 # 6000-510000
+
+# NAT Behavior Discovery
+natbd_server creytiv.com
+natbd_interval 600 # in seconds
+
+# Selfview
+video_selfview window # {window,pip}
+#selfview_size 64x64
+
+# ICE
+ice_turn no
+ice_debug no
+ice_nomination regular # {regular,aggressive}
+ice_mode full # {full,lite}
+
+# ZRTP
+#zrtp_hash no # Disable SDP zrtp-hash (not recommended)
+
+# sndfile #
+snd_path /tmp/
diff --git a/docs/examples/contacts b/docs/examples/contacts
new file mode 100644
index 0000000..b5131a6
--- /dev/null
+++ b/docs/examples/contacts
@@ -0,0 +1,11 @@
+#
+# SIP contacts
+#
+# Displayname <sip:user@domain>;addr-params
+#
+# addr-params:
+# ;presence={none,p2p}
+#
+
+"Echo Server" <sip:echo@creytiv.com>
+"alfredh" <sip:alfredh@home>;presence=p2p
diff --git a/include/baresip.h b/include/baresip.h
new file mode 100644
index 0000000..746a95e
--- /dev/null
+++ b/include/baresip.h
@@ -0,0 +1,1211 @@
+/**
+ * @file baresip.h Public Interface to Baresip
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifndef BARESIP_H__
+#define BARESIP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** Defines the Baresip version string */
+#define BARESIP_VERSION "0.5.7"
+
+
+#ifndef NET_MAX_NS
+#define NET_MAX_NS (4)
+#endif
+
+
+/* forward declarations */
+struct sa;
+struct sdp_media;
+struct sdp_session;
+struct sip_msg;
+struct stream;
+struct ua;
+struct vidframe;
+struct vidrect;
+struct vidsz;
+
+
+/*
+ * Account
+ */
+
+/** Defines the answermodes */
+enum answermode {
+ ANSWERMODE_MANUAL = 0,
+ ANSWERMODE_EARLY,
+ ANSWERMODE_AUTO
+};
+
+struct account;
+
+int account_alloc(struct account **accp, const char *sipaddr);
+int account_debug(struct re_printf *pf, const struct account *acc);
+int account_set_auth_pass(struct account *acc, const char *pass);
+int account_set_display_name(struct account *acc, const char *dname);
+int account_auth(const struct account *acc, char **username, char **password,
+ const char *realm);
+struct list *account_aucodecl(const struct account *acc);
+struct list *account_vidcodecl(const struct account *acc);
+struct sip_addr *account_laddr(const struct account *acc);
+uint32_t account_regint(const struct account *acc);
+uint32_t account_pubint(const struct account *acc);
+uint32_t account_ptime(const struct account *acc);
+enum answermode account_answermode(const struct account *acc);
+const char *account_aor(const struct account *acc);
+const char *account_auth_user(const struct account *acc);
+const char *account_auth_pass(const struct account *acc);
+const char *account_outbound(const struct account *acc, unsigned ix);
+const char *account_stun_user(const struct account *acc);
+const char *account_stun_pass(const struct account *acc);
+const char *account_stun_host(const struct account *acc);
+
+
+/*
+ * Audio-level
+ */
+
+
+#define AULEVEL_MIN (-96.0)
+#define AULEVEL_MAX (0.0)
+
+
+double aulevel_calc_dbov(const int16_t *sampv, size_t sampc);
+
+
+/*
+ * Call
+ */
+
+enum call_event {
+ CALL_EVENT_INCOMING,
+ CALL_EVENT_RINGING,
+ CALL_EVENT_PROGRESS,
+ CALL_EVENT_ESTABLISHED,
+ CALL_EVENT_CLOSED,
+ CALL_EVENT_TRANSFER,
+ CALL_EVENT_TRANSFER_FAILED,
+};
+
+struct call;
+
+typedef void (call_event_h)(struct call *call, enum call_event ev,
+ const char *str, void *arg);
+typedef void (call_dtmf_h)(struct call *call, char key, void *arg);
+
+int call_modify(struct call *call);
+int call_hold(struct call *call, bool hold);
+int call_send_digit(struct call *call, char key);
+bool call_has_audio(const struct call *call);
+bool call_has_video(const struct call *call);
+int call_transfer(struct call *call, const char *uri);
+int call_status(struct re_printf *pf, const struct call *call);
+int call_debug(struct re_printf *pf, const struct call *call);
+void call_set_handlers(struct call *call, call_event_h *eh,
+ call_dtmf_h *dtmfh, void *arg);
+uint16_t call_scode(const struct call *call);
+uint32_t call_duration(const struct call *call);
+uint32_t call_setup_duration(const struct call *call);
+const char *call_peeruri(const struct call *call);
+const char *call_peername(const struct call *call);
+const char *call_localuri(const struct call *call);
+struct audio *call_audio(const struct call *call);
+struct video *call_video(const struct call *call);
+struct list *call_streaml(const struct call *call);
+struct ua *call_get_ua(const struct call *call);
+bool call_is_onhold(const struct call *call);
+bool call_is_outgoing(const struct call *call);
+void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms);
+uint32_t call_linenum(const struct call *call);
+struct call *call_find_linenum(const struct list *calls, uint32_t linenum);
+void call_set_current(struct list *calls, struct call *call);
+
+
+/*
+ * Conf (utils)
+ */
+
+
+/** Defines the configuration line handler */
+typedef int (confline_h)(const struct pl *addr, void *arg);
+
+int conf_configure(void);
+int conf_modules(void);
+void conf_path_set(const char *path);
+int conf_path_get(char *path, size_t sz);
+int conf_parse(const char *filename, confline_h *ch, void *arg);
+int conf_get_vidsz(const struct conf *conf, const char *name,
+ struct vidsz *sz);
+int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa);
+bool conf_fileexist(const char *path);
+void conf_close(void);
+struct conf *conf_cur(void);
+
+
+/*
+ * Config (core configuration)
+ */
+
+/** A range of numbers */
+struct range {
+ uint32_t min; /**< Minimum number */
+ uint32_t max; /**< Maximum number */
+};
+
+static inline bool in_range(const struct range *rng, uint32_t val)
+{
+ return rng ? (val >= rng->min && val <= rng->max) : false;
+}
+
+/** Audio transmit mode */
+enum audio_mode {
+ AUDIO_MODE_POLL = 0, /**< Polling mode */
+ AUDIO_MODE_THREAD, /**< Use dedicated thread */
+};
+
+
+/** SIP User-Agent */
+struct config_sip {
+ uint32_t trans_bsize; /**< SIP Transaction bucket size */
+ char uuid[64]; /**< Universally Unique Identifier */
+ char local[64]; /**< Local SIP Address */
+ char cert[256]; /**< SIP Certificate */
+};
+
+/** Call config */
+struct config_call {
+ uint32_t local_timeout; /**< Incoming call timeout [sec] 0=off */
+ uint32_t max_calls; /**< Maximum number of calls, 0=unlimited */
+};
+
+/** Audio */
+struct config_audio {
+ char audio_path[256]; /**< Audio file directory */
+ char src_mod[16]; /**< Audio source module */
+ char src_dev[128]; /**< Audio source device */
+ char play_mod[16]; /**< Audio playback module */
+ char play_dev[128]; /**< Audio playback device */
+ char alert_mod[16]; /**< Audio alert module */
+ char alert_dev[128]; /**< Audio alert device */
+ struct range srate; /**< Audio sampling rate in [Hz] */
+ struct range channels; /**< Nr. of audio channels (1=mono) */
+ uint32_t srate_play; /**< Opt. sampling rate for player */
+ uint32_t srate_src; /**< Opt. sampling rate for source */
+ uint32_t channels_play; /**< Opt. channels for player */
+ uint32_t channels_src; /**< Opt. channels for source */
+ bool src_first; /**< Audio source opened first */
+ enum audio_mode txmode; /**< Audio transmit mode */
+ bool level; /**< Enable audio level indication */
+ int src_fmt; /**< Audio source sample format */
+ int play_fmt; /**< Audio playback sample format */
+};
+
+#ifdef USE_VIDEO
+/** Video */
+struct config_video {
+ char src_mod[16]; /**< Video source module */
+ char src_dev[128]; /**< Video source device */
+ char disp_mod[16]; /**< Video display module */
+ char disp_dev[128]; /**< Video display device */
+ unsigned width, height; /**< Video resolution */
+ uint32_t bitrate; /**< Encoder bitrate in [bit/s] */
+ uint32_t fps; /**< Video framerate */
+ bool fullscreen; /**< Enable fullscreen display */
+};
+#endif
+
+/** Audio/Video Transport */
+struct config_avt {
+ uint8_t rtp_tos; /**< Type-of-Service for outg. RTP */
+ struct range rtp_ports; /**< RTP port range */
+ struct range rtp_bw; /**< RTP Bandwidth range [bit/s] */
+ bool rtcp_enable; /**< RTCP is enabled */
+ bool rtcp_mux; /**< RTP/RTCP multiplexing */
+ struct range jbuf_del; /**< Delay, number of frames */
+ bool rtp_stats; /**< Enable RTP statistics */
+ uint32_t rtp_timeout; /**< RTP Timeout in seconds (0=off) */
+};
+
+/* Network */
+struct config_net {
+ char ifname[16]; /**< Bind to interface (optional) */
+ struct {
+ char addr[64];
+ } nsv[NET_MAX_NS]; /**< Configured DNS nameservers */
+ size_t nsc; /**< Number of DNS nameservers */
+};
+
+#ifdef USE_VIDEO
+/* BFCP */
+struct config_bfcp {
+ char proto[16]; /**< BFCP Transport (optional) */
+};
+#endif
+
+/** SDP */
+struct config_sdp {
+ bool ebuacip; /**< Enable EBU-ACIP parameters */
+};
+
+
+/** Core configuration */
+struct config {
+
+ struct config_sip sip;
+
+ struct config_call call;
+
+ struct config_audio audio;
+
+#ifdef USE_VIDEO
+ struct config_video video;
+#endif
+ struct config_avt avt;
+
+ struct config_net net;
+
+#ifdef USE_VIDEO
+ struct config_bfcp bfcp;
+#endif
+
+ struct config_sdp sdp;
+};
+
+int config_parse_conf(struct config *cfg, const struct conf *conf);
+int config_print(struct re_printf *pf, const struct config *cfg);
+int config_write_template(const char *file, const struct config *cfg);
+struct config *conf_config(void);
+
+
+/*
+ * Contact
+ */
+
+enum presence_status {
+ PRESENCE_UNKNOWN,
+ PRESENCE_OPEN,
+ PRESENCE_CLOSED,
+ PRESENCE_BUSY
+};
+
+
+struct contact;
+typedef void (contact_update_h)(struct contact *c, bool removed, void *arg);
+
+struct contacts {
+ struct list cl;
+ struct hash *cht;
+
+ contact_update_h *handler;
+ void* handler_arg;
+};
+
+
+int contact_init(struct contacts *contacts);
+void contact_close(struct contacts *contacts);
+int contact_add(struct contacts *contacts,
+ struct contact **contactp, const struct pl *addr);
+void contact_remove(struct contacts *contacts, struct contact *c);
+void contact_set_update_handler(struct contacts *contacs,
+ contact_update_h *updateh, void *arg);
+int contacts_print(struct re_printf *pf, const struct contacts *contacts);
+enum presence_status contact_presence(const struct contact *c);
+void contact_set_presence(struct contact *c, enum presence_status status);
+bool contact_block_access(const struct contacts *contacts, const char *uri);
+struct contact *contact_find(const struct contacts *contacts,
+ const char *uri);
+struct sip_addr *contact_addr(const struct contact *c);
+struct list *contact_list(const struct contacts *contacts);
+const char *contact_str(const struct contact *c);
+const char *contact_presence_str(enum presence_status status);
+
+
+/*
+ * Media Context
+ */
+
+/** Media Context */
+struct media_ctx {
+ const char *id; /**< Media Context identifier */
+};
+
+
+/*
+ * Message
+ */
+
+typedef void (message_recv_h)(const struct pl *peer, const struct pl *ctype,
+ struct mbuf *body, void *arg);
+
+struct message;
+struct message_lsnr;
+
+int message_init(struct message **messagep);
+int message_listen(struct message_lsnr **lsnrp, struct message *message,
+ message_recv_h *h, void *arg);
+int message_send(struct ua *ua, const char *peer, const char *msg,
+ sip_resp_h *resph, void *arg);
+
+
+/*
+ * Audio Source
+ */
+
+struct ausrc;
+struct ausrc_st;
+
+/** Audio Source parameters */
+struct ausrc_prm {
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+ int fmt; /**< Sample format (enum aufmt) */
+};
+
+typedef void (ausrc_read_h)(const void *sampv, size_t sampc, void *arg);
+typedef void (ausrc_error_h)(int err, const char *str, void *arg);
+
+typedef int (ausrc_alloc_h)(struct ausrc_st **stp, const struct ausrc *ausrc,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+
+int ausrc_register(struct ausrc **asp, struct list *ausrcl, const char *name,
+ ausrc_alloc_h *alloch);
+const struct ausrc *ausrc_find(const struct list *ausrcl, const char *name);
+int ausrc_alloc(struct ausrc_st **stp, struct list *ausrcl,
+ struct media_ctx **ctx,
+ const char *name,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+
+
+/*
+ * Audio Player
+ */
+
+struct auplay;
+struct auplay_st;
+
+/** Audio Player parameters */
+struct auplay_prm {
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+ int fmt; /**< Sample format (enum aufmt) */
+};
+
+typedef void (auplay_write_h)(void *sampv, size_t sampc, void *arg);
+
+typedef int (auplay_alloc_h)(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+
+int auplay_register(struct auplay **pp, struct list *auplayl,
+ const char *name, auplay_alloc_h *alloch);
+const struct auplay *auplay_find(const struct list *auplayl, const char *name);
+int auplay_alloc(struct auplay_st **stp, struct list *auplayl,
+ const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+
+
+/*
+ * Audio Filter
+ */
+
+struct aufilt;
+
+/* Base class */
+struct aufilt_enc_st {
+ const struct aufilt *af;
+ struct le le;
+};
+
+struct aufilt_dec_st {
+ const struct aufilt *af;
+ struct le le;
+};
+
+/** Audio Filter Parameters */
+struct aufilt_prm {
+ uint32_t srate; /**< Sampling rate in [Hz] */
+ uint8_t ch; /**< Number of channels */
+ uint32_t ptime; /**< Wanted packet-time in [ms] */
+};
+
+typedef int (aufilt_encupd_h)(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm);
+typedef int (aufilt_encode_h)(struct aufilt_enc_st *st,
+ int16_t *sampv, size_t *sampc);
+
+typedef int (aufilt_decupd_h)(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm);
+typedef int (aufilt_decode_h)(struct aufilt_dec_st *st,
+ int16_t *sampv, size_t *sampc);
+
+struct aufilt {
+ struct le le;
+ const char *name;
+ aufilt_encupd_h *encupdh;
+ aufilt_encode_h *ench;
+ aufilt_decupd_h *decupdh;
+ aufilt_decode_h *dech;
+};
+
+void aufilt_register(struct list *aufiltl, struct aufilt *af);
+void aufilt_unregister(struct aufilt *af);
+
+
+/*
+ * Log
+ */
+
+enum log_level {
+ LEVEL_DEBUG = 0,
+ LEVEL_INFO,
+ LEVEL_WARN,
+ LEVEL_ERROR,
+};
+
+typedef void (log_h)(uint32_t level, const char *msg);
+
+struct log {
+ struct le le;
+ log_h *h;
+};
+
+void log_register_handler(struct log *logh);
+void log_unregister_handler(struct log *logh);
+void log_enable_debug(bool enable);
+void log_enable_info(bool enable);
+void log_enable_stderr(bool enable);
+void vlog(enum log_level level, const char *fmt, va_list ap);
+void loglv(enum log_level level, const char *fmt, ...);
+void debug(const char *fmt, ...);
+void info(const char *fmt, ...);
+void warning(const char *fmt, ...);
+void error_msg(const char *fmt, ...);
+
+
+/*
+ * Menc - Media encryption (for RTP)
+ */
+
+struct menc;
+struct menc_sess;
+struct menc_media;
+
+
+typedef void (menc_error_h)(int err, void *arg);
+
+typedef int (menc_sess_h)(struct menc_sess **sessp, struct sdp_session *sdp,
+ bool offerer, menc_error_h *errorh, void *arg);
+
+typedef int (menc_media_h)(struct menc_media **mp, struct menc_sess *sess,
+ struct rtp_sock *rtp, int proto,
+ void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm);
+
+struct menc {
+ struct le le;
+ const char *id;
+ const char *sdp_proto;
+ menc_sess_h *sessh;
+ menc_media_h *mediah;
+};
+
+void menc_register(struct list *mencl, struct menc *menc);
+void menc_unregister(struct menc *menc);
+const struct menc *menc_find(const struct list *mencl, const char *id);
+
+
+/*
+ * Net - Networking
+ */
+
+struct network;
+
+typedef void (net_change_h)(void *arg);
+
+int net_alloc(struct network **netp, const struct config_net *cfg, int af);
+int net_use_nameserver(struct network *net, const struct sa *ns);
+void net_change(struct network *net, uint32_t interval,
+ net_change_h *ch, void *arg);
+void net_force_change(struct network *net);
+bool net_check(struct network *net);
+int net_af(const struct network *net);
+int net_debug(struct re_printf *pf, const struct network *net);
+const struct sa *net_laddr_af(const struct network *net, int af);
+const char *net_domain(const struct network *net);
+struct dnsc *net_dnsc(const struct network *net);
+
+
+/*
+ * Play - audio file player
+ */
+
+struct play;
+struct player;
+
+int play_file(struct play **playp, struct player *player,
+ const char *filename, int repeat);
+int play_tone(struct play **playp, struct player *player,
+ struct mbuf *tone,
+ uint32_t srate, uint8_t ch, int repeat);
+int play_init(struct player **playerp);
+void play_set_path(struct player *player, const char *path);
+
+
+/*
+ * User Agent
+ */
+
+struct ua;
+
+/** Events from User-Agent */
+enum ua_event {
+ UA_EVENT_REGISTERING = 0,
+ UA_EVENT_REGISTER_OK,
+ UA_EVENT_REGISTER_FAIL,
+ UA_EVENT_UNREGISTERING,
+ UA_EVENT_SHUTDOWN,
+ UA_EVENT_EXIT,
+
+ UA_EVENT_CALL_INCOMING,
+ UA_EVENT_CALL_RINGING,
+ UA_EVENT_CALL_PROGRESS,
+ UA_EVENT_CALL_ESTABLISHED,
+ UA_EVENT_CALL_CLOSED,
+ UA_EVENT_CALL_TRANSFER_FAILED,
+ UA_EVENT_CALL_DTMF_START,
+ UA_EVENT_CALL_DTMF_END,
+ UA_EVENT_CALL_RTCP,
+
+ UA_EVENT_MAX,
+};
+
+/** Video mode */
+enum vidmode {
+ VIDMODE_OFF = 0, /**< Video disabled */
+ VIDMODE_ON, /**< Video enabled */
+};
+
+/** Defines the User-Agent event handler */
+typedef void (ua_event_h)(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg);
+typedef void (options_resp_h)(int err, const struct sip_msg *msg, void *arg);
+
+typedef void (ua_exit_h)(void *arg);
+
+/* Multiple instances */
+int ua_alloc(struct ua **uap, const char *aor);
+int ua_connect(struct ua *ua, struct call **callp,
+ const char *from_uri, const char *uri,
+ const char *params, enum vidmode vmode);
+void ua_hangup(struct ua *ua, struct call *call,
+ uint16_t scode, const char *reason);
+int ua_answer(struct ua *ua, struct call *call);
+int ua_progress(struct ua *ua, struct call *call);
+int ua_hold_answer(struct ua *ua, struct call *call);
+int ua_options_send(struct ua *ua, const char *uri,
+ options_resp_h *resph, void *arg);
+int ua_debug(struct re_printf *pf, const struct ua *ua);
+int ua_print_calls(struct re_printf *pf, const struct ua *ua);
+int ua_print_status(struct re_printf *pf, const struct ua *ua);
+int ua_print_supported(struct re_printf *pf, const struct ua *ua);
+int ua_register(struct ua *ua);
+void ua_unregister(struct ua *ua);
+bool ua_isregistered(const struct ua *ua);
+void ua_pub_gruu_set(struct ua *ua, const struct pl *pval);
+const char *ua_aor(const struct ua *ua);
+const char *ua_cuser(const struct ua *ua);
+const char *ua_local_cuser(const struct ua *ua);
+struct account *ua_account(const struct ua *ua);
+const char *ua_outbound(const struct ua *ua);
+struct call *ua_call(const struct ua *ua);
+struct call *ua_prev_call(const struct ua *ua);
+struct list *ua_calls(const struct ua *ua);
+enum presence_status ua_presence_status(const struct ua *ua);
+void ua_presence_status_set(struct ua *ua, const enum presence_status status);
+void ua_set_media_af(struct ua *ua, int af_media);
+
+
+/* One instance */
+int ua_init(const char *software, bool udp, bool tcp, bool tls,
+ bool prefer_ipv6);
+void ua_close(void);
+void ua_stop_all(bool forced);
+void uag_set_exit_handler(ua_exit_h *exith, void *arg);
+int uag_reset_transp(bool reg, bool reinvite);
+int uag_event_register(ua_event_h *eh, void *arg);
+void uag_event_unregister(ua_event_h *eh);
+void uag_set_sub_handler(sip_msg_h *subh);
+int ua_print_sip_status(struct re_printf *pf, void *unused);
+int uag_set_extra_params(const char *eprm);
+struct ua *uag_find(const struct pl *cuser);
+struct ua *uag_find_aor(const char *aor);
+struct ua *uag_find_param(const char *name, const char *val);
+struct sip *uag_sip(void);
+const char *uag_event_str(enum ua_event ev);
+struct list *uag_list(void);
+void uag_current_set(struct ua *ua);
+struct ua *uag_current(void);
+struct sipsess_sock *uag_sipsess_sock(void);
+struct sipevent_sock *uag_sipevent_sock(void);
+
+
+/*
+ * User Interface
+ */
+
+struct ui_sub {
+ struct list uil; /**< List of UIs (struct ui) */
+ struct cmd_ctx *uictx;
+};
+
+typedef int (ui_output_h)(const char *str);
+
+/** Defines a User-Interface module */
+struct ui {
+ struct le le; /**< Linked-list element */
+ const char *name; /**< Name of the UI-module */
+ ui_output_h *outputh; /**< Handler for output strings (optional) */
+};
+
+void ui_register(struct ui_sub *uis, struct ui *ui);
+void ui_unregister(struct ui *ui);
+
+void ui_reset(struct ui_sub *uis);
+void ui_input_key(struct ui_sub *uis, char key, struct re_printf *pf);
+void ui_input_str(const char *str);
+int ui_input_pl(struct re_printf *pf, const struct pl *pl);
+void ui_output(struct ui_sub *uis, const char *fmt, ...);
+bool ui_isediting(const struct ui_sub *uis);
+int ui_password_prompt(char **passwordp);
+
+
+/*
+ * Command interface
+ */
+
+/* special keys */
+#define KEYCODE_NONE (0x00)
+#define KEYCODE_REL (0x04) /* Key was released */
+#define KEYCODE_ESC (0x1b)
+
+
+/** Command flags */
+enum {
+ CMD_PRM = (1<<0), /**< Command with parameter */
+ CMD_PROG = (1<<1), /**< Show progress */
+
+ CMD_IPRM = CMD_PRM | CMD_PROG, /**< Interactive parameter */
+};
+
+/** Command arguments */
+struct cmd_arg {
+ char key; /**< Which key was pressed */
+ char *prm; /**< Optional parameter */
+ bool complete; /**< True if complete */
+ void *data; /**< Application data */
+};
+
+/** Defines a command */
+struct cmd {
+ const char *name; /**< Long command */
+ char key; /**< Short command */
+ int flags; /**< Optional command flags */
+ const char *desc; /**< Description string */
+ re_printf_h *h; /**< Command handler */
+};
+
+struct cmd_ctx;
+struct commands;
+
+
+int cmd_init(struct commands **commandsp);
+int cmd_register(struct commands *commands,
+ const struct cmd *cmdv, size_t cmdc);
+void cmd_unregister(struct commands *commands, const struct cmd *cmdv);
+int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf, void *data);
+int cmd_process_long(struct commands *commands, const char *str, size_t len,
+ struct re_printf *pf_resp, void *data);
+int cmd_print(struct re_printf *pf, const struct commands *commands);
+const struct cmd *cmd_find_long(const struct commands *commands,
+ const char *name);
+struct cmds *cmds_find(const struct commands *commands,
+ const struct cmd *cmdv);
+
+
+/*
+ * Video Source
+ */
+
+struct vidsrc;
+struct vidsrc_st;
+
+/** Video Source parameters */
+struct vidsrc_prm {
+ int orient; /**< Wanted picture orientation (enum vidorient) */
+ int fps; /**< Wanted framerate */
+};
+
+typedef void (vidsrc_frame_h)(struct vidframe *frame, void *arg);
+typedef void (vidsrc_error_h)(int err, void *arg);
+
+typedef int (vidsrc_alloc_h)(struct vidsrc_st **vsp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size,
+ const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg);
+
+typedef void (vidsrc_update_h)(struct vidsrc_st *st, struct vidsrc_prm *prm,
+ const char *dev);
+
+int vidsrc_register(struct vidsrc **vp, struct list *vidsrcl, const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh);
+const struct vidsrc *vidsrc_find(const struct list *vidsrcl, const char *name);
+int vidsrc_alloc(struct vidsrc_st **stp, struct list *vidsrcl,
+ const char *name,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg);
+
+
+/*
+ * Video Display
+ */
+
+struct vidisp;
+struct vidisp_st;
+
+/** Video Display parameters */
+struct vidisp_prm {
+ void *view; /**< Optional view (set by application or module) */
+ bool fullscreen; /**< Enable fullscreen display */
+};
+
+typedef void (vidisp_resize_h)(const struct vidsz *size, void *arg);
+
+typedef int (vidisp_alloc_h)(struct vidisp_st **vp,
+ const struct vidisp *vd, struct vidisp_prm *prm,
+ const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+typedef int (vidisp_update_h)(struct vidisp_st *st, bool fullscreen,
+ int orient, const struct vidrect *window);
+typedef int (vidisp_disp_h)(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+typedef void (vidisp_hide_h)(struct vidisp_st *st);
+
+int vidisp_register(struct vidisp **vp, struct list *vidispl, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh);
+int vidisp_alloc(struct vidisp_st **stp, struct list *vidispl,
+ const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+int vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+const struct vidisp *vidisp_find(const struct list *vidispl, const char *name);
+
+
+/*
+ * Audio Codec
+ */
+
+/** Audio Codec parameters */
+struct auenc_param {
+ uint32_t ptime; /**< Packet time in [ms] */
+};
+
+struct auenc_state;
+struct audec_state;
+struct aucodec;
+
+typedef int (auenc_update_h)(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+typedef int (auenc_encode_h)(struct auenc_state *aes, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc);
+
+typedef int (audec_update_h)(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp);
+typedef int (audec_decode_h)(struct audec_state *ads, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len);
+typedef int (audec_plc_h)(struct audec_state *ads,
+ int16_t *sampv, size_t *sampc);
+
+struct aucodec {
+ struct le le;
+ const char *pt;
+ const char *name;
+ uint32_t srate; /* Audio samplerate */
+ uint32_t crate; /* RTP Clock rate */
+ uint8_t ch;
+ const char *fmtp;
+ auenc_update_h *encupdh;
+ auenc_encode_h *ench;
+ audec_update_h *decupdh;
+ audec_decode_h *dech;
+ audec_plc_h *plch;
+ sdp_fmtp_enc_h *fmtp_ench;
+ sdp_fmtp_cmp_h *fmtp_cmph;
+};
+
+void aucodec_register(struct list *aucodecl, struct aucodec *ac);
+void aucodec_unregister(struct aucodec *ac);
+const struct aucodec *aucodec_find(const struct list *aucodecl,
+ const char *name, uint32_t srate,
+ uint8_t ch);
+
+
+/*
+ * Video Codec
+ */
+
+/** Video Codec parameters */
+struct videnc_param {
+ unsigned bitrate; /**< Encoder bitrate in [bit/s] */
+ unsigned pktsize; /**< RTP packetsize in [bytes] */
+ unsigned fps; /**< Video framerate */
+ uint32_t max_fs;
+};
+
+struct videnc_state;
+struct viddec_state;
+struct vidcodec;
+
+typedef int (videnc_packet_h)(bool marker, uint32_t rtp_ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len,
+ void *arg);
+
+typedef int (videnc_update_h)(struct videnc_state **vesp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+typedef int (videnc_encode_h)(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+typedef int (viddec_update_h)(struct viddec_state **vdsp,
+ const struct vidcodec *vc, const char *fmtp);
+typedef int (viddec_decode_h)(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq,
+ struct mbuf *mb);
+
+struct vidcodec {
+ struct le le;
+ const char *pt;
+ const char *name;
+ const char *variant;
+ const char *fmtp;
+ videnc_update_h *encupdh;
+ videnc_encode_h *ench;
+ viddec_update_h *decupdh;
+ viddec_decode_h *dech;
+ sdp_fmtp_enc_h *fmtp_ench;
+ sdp_fmtp_cmp_h *fmtp_cmph;
+};
+
+void vidcodec_register(struct list *vidcodecl, struct vidcodec *vc);
+void vidcodec_unregister(struct vidcodec *vc);
+const struct vidcodec *vidcodec_find(const struct list *vidcodecl,
+ const char *name, const char *variant);
+const struct vidcodec *vidcodec_find_encoder(const struct list *vidcodecl,
+ const char *name);
+const struct vidcodec *vidcodec_find_decoder(const struct list *vidcodecl,
+ const char *name);
+
+
+/*
+ * Video Filter
+ */
+
+struct vidfilt;
+
+/* Base class */
+struct vidfilt_enc_st {
+ const struct vidfilt *vf;
+ struct le le;
+};
+
+struct vidfilt_dec_st {
+ const struct vidfilt *vf;
+ struct le le;
+};
+
+typedef int (vidfilt_encupd_h)(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf);
+typedef int (vidfilt_encode_h)(struct vidfilt_enc_st *st,
+ struct vidframe *frame);
+
+typedef int (vidfilt_decupd_h)(struct vidfilt_dec_st **stp, void **ctx,
+ const struct vidfilt *vf);
+typedef int (vidfilt_decode_h)(struct vidfilt_dec_st *st,
+ struct vidframe *frame);
+
+struct vidfilt {
+ struct le le;
+ const char *name;
+ vidfilt_encupd_h *encupdh;
+ vidfilt_encode_h *ench;
+ vidfilt_decupd_h *decupdh;
+ vidfilt_decode_h *dech;
+};
+
+void vidfilt_register(struct list *vidfiltl, struct vidfilt *vf);
+void vidfilt_unregister(struct vidfilt *vf);
+int vidfilt_enc_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf);
+int vidfilt_dec_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf);
+
+
+/*
+ * Audio stream
+ */
+
+struct audio;
+
+void audio_mute(struct audio *a, bool muted);
+bool audio_ismuted(const struct audio *a);
+void audio_set_devicename(struct audio *a, const char *src, const char *play);
+int audio_set_source(struct audio *au, const char *mod, const char *device);
+int audio_set_player(struct audio *au, const char *mod, const char *device);
+void audio_encoder_cycle(struct audio *audio);
+int audio_level_get(const struct audio *au, double *level);
+int audio_debug(struct re_printf *pf, const struct audio *a);
+struct stream *audio_strm(const struct audio *a);
+
+
+/*
+ * Video stream
+ */
+
+struct video;
+
+void video_mute(struct video *v, bool muted);
+void *video_view(const struct video *v);
+int video_set_fullscreen(struct video *v, bool fs);
+int video_set_orient(struct video *v, int orient);
+void video_vidsrc_set_device(struct video *v, const char *dev);
+int video_set_source(struct video *v, const char *name, const char *dev);
+void video_set_devicename(struct video *v, const char *src, const char *disp);
+void video_encoder_cycle(struct video *video);
+int video_debug(struct re_printf *pf, const struct video *v);
+uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps);
+double video_calc_seconds(uint32_t rtp_ts);
+struct stream *video_strm(const struct video *v);
+
+
+/*
+ * Generic stream
+ */
+
+const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm);
+
+
+/*
+ * Media NAT
+ */
+
+struct mnat;
+struct mnat_sess;
+struct mnat_media;
+
+typedef void (mnat_estab_h)(int err, uint16_t scode, const char *reason,
+ void *arg);
+
+typedef int (mnat_sess_h)(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *sdp, bool offerer,
+ mnat_estab_h *estabh, void *arg);
+
+typedef int (mnat_media_h)(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm);
+
+typedef int (mnat_update_h)(struct mnat_sess *sess);
+
+int mnat_register(struct mnat **mnatp, struct list *mnatl,
+ const char *id, const char *ftag,
+ mnat_sess_h *sessh, mnat_media_h *mediah,
+ mnat_update_h *updateh);
+
+
+/*
+ * Real-time
+ */
+int realtime_enable(bool enable, int fps);
+
+
+/*
+ * SDP
+ */
+
+bool sdp_media_has_media(const struct sdp_media *m);
+int sdp_media_find_unused_pt(const struct sdp_media *m);
+int sdp_fingerprint_decode(const char *attr, struct pl *hash,
+ uint8_t *md, size_t *sz);
+uint32_t sdp_media_rattr_u32(const struct sdp_media *sdpm, const char *name);
+const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m,
+ const char *name);
+
+
+/*
+ * SIP Request
+ */
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...);
+
+
+/*
+ * H.264
+ */
+
+/** NAL unit types (RFC 3984, Table 1) */
+enum {
+ H264_NAL_UNKNOWN = 0,
+ /* 1-23 NAL unit Single NAL unit packet per H.264 */
+ H264_NAL_SLICE = 1,
+ H264_NAL_DPA = 2,
+ H264_NAL_DPB = 3,
+ H264_NAL_DPC = 4,
+ H264_NAL_IDR_SLICE = 5,
+ H264_NAL_SEI = 6,
+ H264_NAL_SPS = 7,
+ H264_NAL_PPS = 8,
+ H264_NAL_AUD = 9,
+ H264_NAL_END_SEQUENCE = 10,
+ H264_NAL_END_STREAM = 11,
+ H264_NAL_FILLER_DATA = 12,
+ H264_NAL_SPS_EXT = 13,
+ H264_NAL_AUX_SLICE = 19,
+
+ H264_NAL_STAP_A = 24, /**< Single-time aggregation packet */
+ H264_NAL_STAP_B = 25, /**< Single-time aggregation packet */
+ H264_NAL_MTAP16 = 26, /**< Multi-time aggregation packet */
+ H264_NAL_MTAP24 = 27, /**< Multi-time aggregation packet */
+ H264_NAL_FU_A = 28, /**< Fragmentation unit */
+ H264_NAL_FU_B = 29, /**< Fragmentation unit */
+};
+
+/**
+ * H.264 Header defined in RFC 3984
+ *
+ * <pre>
+ +---------------+
+ |0|1|2|3|4|5|6|7|
+ +-+-+-+-+-+-+-+-+
+ |F|NRI| Type |
+ +---------------+
+ * </pre>
+ */
+struct h264_hdr {
+ unsigned f:1; /**< 1 bit - Forbidden zero bit (must be 0) */
+ unsigned nri:2; /**< 2 bits - nal_ref_idc */
+ unsigned type:5; /**< 5 bits - nal_unit_type */
+};
+
+int h264_hdr_encode(const struct h264_hdr *hdr, struct mbuf *mb);
+int h264_hdr_decode(struct h264_hdr *hdr, struct mbuf *mb);
+
+/** Fragmentation Unit header */
+struct h264_fu {
+ unsigned s:1; /**< Start bit */
+ unsigned e:1; /**< End bit */
+ unsigned r:1; /**< The Reserved bit MUST be equal to 0 */
+ unsigned type:5; /**< The NAL unit payload type */
+};
+
+int h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb);
+int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb);
+
+const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end);
+
+int h264_packetize(uint32_t rtp_ts, const uint8_t *buf, size_t len,
+ size_t pktsize, videnc_packet_h *pkth, void *arg);
+int h264_nal_send(bool first, bool last,
+ bool marker, uint32_t ihdr, uint32_t rtp_ts,
+ const uint8_t *buf, size_t size, size_t maxsz,
+ videnc_packet_h *pkth, void *arg);
+static inline bool h264_is_keyframe(int type)
+{
+ return type == H264_NAL_SPS;
+}
+
+
+/*
+ * Modules
+ */
+
+#ifdef STATIC
+#define DECL_EXPORTS(name) exports_ ##name
+#else
+#define DECL_EXPORTS(name) exports
+#endif
+
+
+int module_preload(const char *module);
+int module_load(const char *name);
+void module_unload(const char *name);
+
+
+/*
+ * MOS (Mean Opinion Score)
+ */
+
+double mos_calculate(double *r_factor, double rtt,
+ double jitter, uint32_t num_packets_lost);
+
+
+/*
+ * Generic event
+ */
+
+int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm);
+
+
+/*
+ * Baresip instance
+ */
+
+int baresip_init(struct config *cfg, bool prefer_ipv6);
+void baresip_close(void);
+struct network *baresip_network(void);
+struct contacts *baresip_contacts(void);
+struct commands *baresip_commands(void);
+struct player *baresip_player(void);
+struct message *baresip_message(void);
+struct list *baresip_mnatl(void);
+struct list *baresip_mencl(void);
+struct list *baresip_aucodecl(void);
+struct list *baresip_ausrcl(void);
+struct list *baresip_auplayl(void);
+struct list *baresip_aufiltl(void);
+struct list *baresip_vidcodecl(void);
+struct list *baresip_vidsrcl(void);
+struct list *baresip_vidispl(void);
+struct list *baresip_vidfiltl(void);
+struct ui_sub *baresip_uis(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* BARESIP_H__ */
diff --git a/mk/Doxyfile b/mk/Doxyfile
new file mode 100644
index 0000000..3606286
--- /dev/null
+++ b/mk/Doxyfile
@@ -0,0 +1,238 @@
+# Doxyfile 1.4.7
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME = baresip
+PROJECT_NUMBER = 0.5.7
+OUTPUT_DIRECTORY = ../baresip-dox
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+#USE_WINDOWS_ENCODING = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH =
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS = YES
+SEPARATE_MEMBER_PAGES = NO
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = YES
+OPTIMIZE_OUTPUT_JAVA = NO
+#BUILTIN_STL_SUPPORT = NO
+DISTRIBUTE_GROUP_DOC = NO
+SUBGROUPING = YES
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+HIDE_UNDOC_MEMBERS = YES
+HIDE_UNDOC_CLASSES = YES
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = NO
+SHOW_INCLUDE_FILES = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_BY_SCOPE_NAME = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+#SHOW_DIRECTORIES = NO
+FILE_VERSION_FILTER =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = YES
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = YES
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = .
+FILE_PATTERNS = *.cpp *.c \
+ *.h \
+ *.m \
+ *.dox
+RECURSIVE = YES
+EXCLUDE =
+
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = */.svn/*
+
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS = *
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+#REFERENCES_LINK_SOURCE = YES
+#USE_HTAGS = NO
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+#HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = NO
+USE_PDFLATEX = NO
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = YES
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+SEARCH_INCLUDES = YES
+INCLUDE_PATH = include
+INCLUDE_FILE_PATTERNS =
+PREDEFINED = "DEBUG_MODULE=foo /**< hei */"
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+PERL_PATH = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = YES
+HAVE_DOT = YES
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+UML_LOOK = NO
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = YES
+#CALLER_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+DOT_GRAPH_MAX_NODES = 250
+#MAX_DOT_GRAPH_WIDTH = 1024
+#MAX_DOT_GRAPH_HEIGHT = 1024
+#MAX_DOT_GRAPH_DEPTH = 1000
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE = NO
diff --git a/mk/mod.mk b/mk/mod.mk
new file mode 100644
index 0000000..cf9a16f
--- /dev/null
+++ b/mk/mod.mk
@@ -0,0 +1,105 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+$(MOD)_OBJS := $(patsubst %.c,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.c,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.cpp,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.cpp,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.m,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.m,$($(MOD)_SRCS)))
+$(MOD)_OBJS += $(patsubst %.S,$(BUILD)/modules/$(MOD)/%.o,\
+ $(filter %.S,$($(MOD)_SRCS)))
+
+-include $($(MOD)_OBJS:.o=.d)
+
+
+$(MOD)_NAME := $(MOD)
+
+
+#
+# function to extract the name of the module from the file/dir path
+#
+modulename = $(lastword $(subst /, ,$(dir $1)))
+
+
+ifeq ($(STATIC),)
+
+#
+# Dynamically loaded modules
+#
+
+$(MOD)$(MOD_SUFFIX): $($(MOD)_OBJS)
+ @echo " LD [M] $@"
+ $(HIDE)$(LD) $(LFLAGS) $(SH_LFLAGS) $(MOD_LFLAGS) \
+ $($(basename $@)_OBJS) \
+ $($(basename $@)_LFLAGS) -L$(LIBRE_SO) -lre -o $@
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.c $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CC [M] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_CFLAGS) \
+ -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.m $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " OC [M] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_CFLAGS) $(OBJCFLAGS) \
+ -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.cpp $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CXX [M] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CXX) $(CXXFLAGS) $($(call modulename,$@)_CXXFLAGS) \
+ -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.S $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " AS [M] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+else
+
+#
+# Static linking of modules
+#
+
+# needed to deref variable now, append to list
+MOD_OBJS := $(MOD_OBJS) $($(MOD)_OBJS)
+MOD_LFLAGS := $(MOD_LFLAGS) $($(MOD)_LFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.c $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CC [m] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_CFLAGS) \
+ -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.m $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " OC [m] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) $($(call modulename,$@)_CFLAGS) $(OBJCFLAGS) \
+ -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.cpp $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " CXX [m] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CXX) $(CXXFLAGS) $($(call modulename,$@)_CXXFLAGS) \
+ -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+$(BUILD)/modules/$(MOD)/%.o: modules/$(MOD)/%.S $(BUILD) Makefile mk/mod.mk \
+ modules/$(MOD)/module.mk mk/modules.mk
+ @echo " AS [m] $@"
+ @mkdir -p $(dir $@)
+ $(HIDE)$(CC) $(CFLAGS) -DMOD_NAME=\"$(MOD)\" -c $< -o $@ $(DFLAGS)
+
+endif
diff --git a/mk/modules.mk b/mk/modules.mk
new file mode 100644
index 0000000..9211dfd
--- /dev/null
+++ b/mk/modules.mk
@@ -0,0 +1,467 @@
+#
+# modules.mk
+#
+# Copyright (C) 2010 - 2017 Creytiv.com
+#
+# External libraries:
+#
+# USE_ALSA ALSA audio driver
+# USE_AMR Adaptive Multi-Rate (AMR) audio codec
+# USE_AUDIOUNIT AudioUnit audio driver for OSX/iOS
+# USE_AVCAPTURE AVFoundation video capture for OSX/iOS
+# USE_AVCODEC avcodec video codec module
+# USE_AVFORMAT avformat video source module
+# USE_BV32 BroadVoice32 Wideband Audio codec
+# USE_CAIRO Cairo module
+# USE_CONS Console input driver
+# USE_COREAUDIO MacOSX Coreaudio audio driver
+# USE_EVDEV Event Device module
+# USE_G711 G.711 audio codec
+# USE_G722 G.722 audio codec
+# USE_G722_1 G.722.1 audio codec
+# USE_G726 G.726 audio codec
+# USE_GSM GSM audio codec
+# USE_GST Gstreamer 0.10 audio module
+# USE_GST1 Gstreamer 1.0 audio module
+# USE_GST_VIDEO Gstreamer 0.10 video module
+# USE_GST_VIDEO1 Gstreamer 1.0 video module
+# USE_GTK GTK+ user interface
+# USE_H265 H.265 video codec
+# USE_ILBC iLBC audio codec
+# USE_ISAC iSAC audio codec
+# USE_JACK JACK Audio Connection Kit audio driver
+# USE_L16 L16 audio codec
+# USE_MPA MPA audo codec
+# USE_MPG123 Use mpg123
+# USE_OMX_RPI RaspberryPi VideoCore display driver
+# USE_OMX_BELLAGIO libomxil-bellagio xvideosink driver
+# USE_OPUS Opus audio codec
+# USE_OSS OSS audio driver
+# USE_PLC Packet Loss Concealment
+# USE_PORTAUDIO Portaudio audio driver
+# USE_PULSE Pulseaudio audio driver
+# USE_SDL libSDL video output
+# USE_SILK SILK (Skype) audio codec
+# USE_SNDFILE sndfile wav dumper
+# USE_SPEEX Speex audio codec
+# USE_SPEEX_AEC Speex Acoustic Echo Canceller
+# USE_SPEEX_PP Speex preprocessor
+# USE_SRTP Secure RTP module using libre
+# USE_STDIO stdio input driver
+# USE_SYSLOG Syslog module
+# USE_V4L Video4Linux module
+# USE_V4L2 Video4Linux2 module
+# USE_WINWAVE Windows audio driver
+# USE_X11 X11 video output
+#
+
+
+# Default is enabled
+MOD_AUTODETECT := 1
+
+ifneq ($(MOD_AUTODETECT),)
+
+USE_CONS := 1
+USE_G711 := 1
+USE_L16 := 1
+
+ifneq ($(OS),win32)
+
+USE_ALSA := $(shell [ -f $(SYSROOT)/include/alsa/asoundlib.h ] || \
+ [ -f $(SYSROOT_ALT)/include/alsa/asoundlib.h ] && echo "yes")
+USE_AMR := $(shell [ -d $(SYSROOT)/include/opencore-amrnb ] || \
+ [ -d $(SYSROOT_ALT)/include/opencore-amrnb ] || \
+ [ -d $(SYSROOT)/local/include/amrnb ] || \
+ [ -d $(SYSROOT)/include/amrnb ] && echo "yes")
+USE_AVCODEC := $(shell [ -f $(SYSROOT)/include/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT)/local/include/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT)/include/$(MACHINE)/libavcodec/avcodec.h ] || \
+ [ -f $(SYSROOT_ALT)/include/libavcodec/avcodec.h ] && echo "yes")
+USE_AVFORMAT := $(shell [ -f $(SYSROOT)/include/libavformat/avformat.h ] || \
+ [ -f $(SYSROOT)/local/include/libavformat/avformat.h ] || \
+ [ -f $(SYSROOT)/include/$(MACHINE)/libavformat/avformat.h ] || \
+ [ -f $(SYSROOT_ALT)/include/libavformat/avformat.h ] && echo "yes")
+USE_AVAHI := $(shell pkg-config --exists avahi-client && echo "yes")
+USE_BV32 := $(shell [ -f $(SYSROOT)/include/bv32/bv32.h ] || \
+ [ -f $(SYSROOT)/local/include/bv32/bv32.h ] && echo "yes")
+USE_CAIRO := $(shell [ -f $(SYSROOT)/include/cairo/cairo.h ] || \
+ [ -f $(SYSROOT)/local/include/cairo/cairo.h ] || \
+ [ -f $(SYSROOT_ALT)/include/cairo/cairo.h ] && echo "yes")
+USE_DTLS := $(shell [ -f $(SYSROOT)/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/dtls1.h ] && echo "yes")
+USE_DTLS_SRTP := $(shell [ -f $(SYSROOT)/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/srtp.h ] && echo "yes")
+USE_G722 := $(shell [ -f $(SYSROOT)/include/spandsp/g722.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/g722.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/g722.h ] && echo "yes")
+USE_G722_1 := $(shell [ -f $(SYSROOT)/include/g722_1.h ] || \
+ [ -f $(SYSROOT_ALT)/include/g722_1.h ] || \
+ [ -f $(SYSROOT)/local/include/g722_1.h ] && echo "yes")
+USE_G726 := $(shell [ -f $(SYSROOT)/include/spandsp/g726.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/g726.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/g726.h ] && echo "yes")
+USE_GSM := $(shell [ -f $(SYSROOT)/include/gsm.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gsm.h ] || \
+ [ -f $(SYSROOT)/include/gsm/gsm.h ] || \
+ [ -f $(SYSROOT)/local/include/gsm.h ] || \
+ [ -f $(SYSROOT)/local/include/gsm/gsm.h ] && echo "yes")
+USE_GST := $(shell pkg-config --exists gstreamer-0.10 && echo "yes")
+USE_GST1 := $(shell pkg-config --exists gstreamer-1.0 && echo "yes")
+USE_GST_VIDEO := \
+ $(shell pkg-config --exists gstreamer-0.10 gstreamer-app-0.10 \
+ && echo "yes")
+USE_GST_VIDEO1 := $(shell pkg-config --exists gstreamer-1.0 gstreamer-app-1.0 \
+ && echo "yes")
+USE_GTK := $(shell pkg-config 'gtk+-2.0 >= 2.22' && \
+ pkg-config 'glib-2.0 >= 2.32' && echo "yes")
+ifneq ($(USE_AVCODEC),)
+USE_H265 := $(shell [ -f $(SYSROOT)/include/x265.h ] || \
+ [ -f $(SYSROOT)/local/include/x265.h ] || \
+ [ -f $(SYSROOT_ALT)/include/x265.h ] && echo "yes")
+endif
+USE_ILBC := $(shell [ -f $(SYSROOT)/include/iLBC_define.h ] || \
+ [ -f $(SYSROOT)/local/include/iLBC_define.h ] && echo "yes")
+USE_ISAC := $(shell [ -f $(SYSROOT)/include/isac.h ] || \
+ [ -f $(SYSROOT)/local/include/isac.h ] && echo "yes")
+USE_JACK := $(shell [ -f $(SYSROOT)/include/jack/jack.h ] || \
+ [ -f $(SYSROOT)/local/include/jack/jack.h ] && echo "yes")
+USE_MPG123 := $(shell [ -f $(SYSROOT)/include/mpg123.h ] || \
+ [ -f $(SYSROOT)/local/include/mpg123.h ] || \
+ [ -f $(SYSROOT_ALT)/include/mpg123.h ] && echo "yes")
+USE_OPUS := $(shell [ -f $(SYSROOT)/include/opus/opus.h ] || \
+ [ -f $(SYSROOT_ALT)/include/opus/opus.h ] || \
+ [ -f $(SYSROOT)/local/include/opus/opus.h ] && echo "yes")
+USE_OSS := $(shell [ -f $(SYSROOT)/include/soundcard.h ] || \
+ [ -f $(SYSROOT)/include/linux/soundcard.h ] || \
+ [ -f $(SYSROOT)/include/sys/soundcard.h ] && echo "yes")
+USE_PLC := $(shell [ -f $(SYSROOT)/include/spandsp/plc.h ] || \
+ [ -f $(SYSROOT_ALT)/include/spandsp/plc.h ] || \
+ [ -f $(SYSROOT)/local/include/spandsp/plc.h ] && echo "yes")
+USE_PORTAUDIO := $(shell [ -f $(SYSROOT)/local/include/portaudio.h ] || \
+ [ -f $(SYSROOT)/include/portaudio.h ] || \
+ [ -f $(SYSROOT_ALT)/include/portaudio.h ] && echo "yes")
+USE_PULSE := $(shell pkg-config --exists libpulse && echo "yes")
+USE_SDL := $(shell [ -f $(SYSROOT)/include/SDL/SDL.h ] || \
+ [ -f $(SYSROOT)/local/include/SDL/SDL.h ] || \
+ [ -f $(SYSROOT_ALT)/include/SDL/SDl.h ] && echo "yes")
+USE_SDL2 := $(shell [ -f $(SYSROOT)/include/SDL2/SDL.h ] || \
+ [ -f $(SYSROOT)/local/include/SDL2/SDL.h ] || \
+ [ -f $(SYSROOT_ALT)/include/SDL2/SDl.h ] && echo "yes")
+USE_SILK := $(shell [ -f $(SYSROOT)/include/silk/SKP_Silk_SDK_API.h ] || \
+ [ -f $(SYSROOT_ALT)/include/silk/SKP_Silk_SDK_API.h ] || \
+ [ -f $(SYSROOT)/local/include/silk/SKP_Silk_SDK_API.h ] && echo "yes")
+USE_SNDFILE := $(shell [ -f $(SYSROOT)/include/sndfile.h ] || \
+ [ -f $(SYSROOT_ALT)/include/sndfile.h ] || \
+ [ -f $(SYSROOT_ALT)/usr/local/include/sndfile.h ] && echo "yes")
+USE_STDIO := $(shell [ -f $(SYSROOT)/include/termios.h ] && echo "yes")
+HAVE_SPEEXDSP := $(shell \
+ [ -f $(SYSROOT)/local/lib/libspeexdsp$(LIB_SUFFIX) ] || \
+ [ -f $(SYSROOT)/lib/libspeexdsp$(LIB_SUFFIX) ] || \
+ [ -f $(SYSROOT_ALT)/lib/libspeexdsp$(LIB_SUFFIX) ] && echo "yes")
+ifeq ($(HAVE_SPEEXDSP),)
+HAVE_SPEEXDSP := \
+ $(shell find $(SYSROOT)/lib -name libspeexdsp$(LIB_SUFFIX) 2>/dev/null)
+endif
+ifneq ($(USE_MPG123),)
+ifneq ($(HAVE_SPEEXDSP),)
+USE_MPA := $(shell [ -f $(SYSROOT)/include/twolame.h ] || \
+ [ -f $(SYSROOT)/local/include/twolame.h ] || \
+ [ -f $(SYSROOT_ALT)/include/twolame.h ] && echo "yes")
+endif
+endif
+USE_SPEEX := $(shell [ -f $(SYSROOT)/include/speex.h ] || \
+ [ -f $(SYSROOT)/include/speex/speex.h ] || \
+ [ -f $(SYSROOT)/local/include/speex.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex.h ] && echo "yes")
+USE_SPEEX_AEC := $(shell [ -f $(SYSROOT)/include/speex/speex_echo.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex_echo.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex_echo.h ] && echo "yes")
+USE_SPEEX_PP := $(shell [ -f $(SYSROOT)/include/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/local/include/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/local/include/speex/speex_preprocess.h ] || \
+ [ -f $(SYSROOT_ALT)/include/speex/speex_preprocess.h ] || \
+ [ -f $(SYSROOT)/include/speex/speex_preprocess.h ] && echo "yes")
+USE_SYSLOG := $(shell [ -f $(SYSROOT)/include/syslog.h ] || \
+ [ -f $(SYSROOT_ALT)/include/syslog.h ] || \
+ [ -f $(SYSROOT)/local/include/syslog.h ] && echo "yes")
+HAVE_LIBMQTT := $(shell [ -f $(SYSROOT)/include/mosquitto.h ] || \
+ [ -f $(SYSROOT)/local/include/mosquitto.h ] \
+ && echo "yes")
+USE_V4L := $(shell [ -f $(SYSROOT)/include/libv4l1.h ] || \
+ [ -f $(SYSROOT)/local/include/libv4l1.h ] \
+ && echo "yes")
+HAVE_LIBV4L2 := $(shell [ -f $(SYSROOT)/include/libv4l2.h ] || \
+ [ -f $(SYSROOT)/local/include/libv4l2.h ] \
+ && echo "yes")
+USE_V4L2 := $(shell [ -f $(SYSROOT)/include/linux/videodev2.h ] || \
+ [ -f $(SYSROOT)/local/include/linux/videodev2.h ] || \
+ [ -f $(SYSROOT)/include/sys/videoio.h ] \
+ && echo "yes")
+USE_X11 := $(shell [ -f $(SYSROOT)/include/X11/Xlib.h ] || \
+ [ -f $(SYSROOT)/local/include/X11/Xlib.h ] || \
+ [ -f $(SYSROOT_ALT)/include/X11/Xlib.h ] && echo "yes")
+USE_ZRTP := $(shell [ -f $(SYSROOT)/include/libzrtp/zrtp.h ] || \
+ [ -f $(SYSROOT)/local/include/libzrtp/zrtp.h ] || \
+ [ -f $(SYSROOT_ALT)/include/libzrtp/zrtp.h ] && echo "yes")
+USE_VPX := $(shell [ -f $(SYSROOT)/include/vpx/vp8.h ] \
+ || [ -f $(SYSROOT)/local/include/vpx/vp8.h ] \
+ || [ -f $(SYSROOT_ALT)/include/vpx/vp8.h ] \
+ && echo "yes")
+USE_OMX_RPI := $(shell [ -f /opt/vc/include/bcm_host.h ] || \
+ [ -f $(SYSROOT)/include/bcm_host.h ] \
+ || [ -f $(SYSROOT_ALT)/include/bcm_host.h ] \
+ && echo "yes")
+USE_OMX_BELLAGIO := $(shell [ -f /usr/include/OMX_Core.h ] \
+ || [ -f $(SYSROOT)/include/OMX_Core.h ] \
+ || [ -f $(SYSROOT_ALT)/include/OMX_Core.h ] \
+ && echo "yes")
+else
+# Windows.
+# Accounts for mingw with Windows SDK (formerly known as Platform SDK)
+# mounted at /winsdk
+USE_DSHOW := $(shell [ -f /winsdk/Include/um/dshow.h ] && echo "yes")
+endif
+
+# Platform specific modules
+ifeq ($(OS),darwin)
+USE_COREAUDIO := yes
+USE_OPENGL := yes
+
+USE_AVFOUNDATION := \
+ $(shell [ -d /System/Library/Frameworks/AVFoundation.framework ] \
+ && echo "yes")
+
+USE_AUDIOUNIT := \
+ $(shell [ -d /System/Library/Frameworks/AudioUnit.framework ] \
+ && echo "yes")
+
+ifneq ($(USE_AVFOUNDATION),)
+USE_AVCAPTURE := yes
+else
+USE_QTCAPTURE := yes
+endif
+
+
+endif
+ifeq ($(OS),linux)
+USE_EVDEV := $(shell [ -f $(SYSROOT)/include/linux/input.h ] && echo "yes")
+MODULES += dtmfio
+endif
+ifeq ($(OS),win32)
+USE_WINWAVE := yes
+MODULES += wincons
+endif
+ifeq ($(OS),openbsd)
+MODULES += sndio
+endif
+ifeq ($(OS),freebsd)
+MODULES += dtmfio
+endif
+
+ifneq ($(USE_GTK),)
+USE_LIBNOTIFY := $(shell pkg-config 'libnotify glib-2.0 < 2.40' && echo "yes")
+endif
+
+endif
+
+# ------------------------------------------------------------------------- #
+
+MODULES += $(EXTRA_MODULES)
+MODULES += stun turn ice natbd auloop presence
+MODULES += menu contact vumeter mwi account natpmp httpd
+MODULES += srtp
+MODULES += uuid
+MODULES += debug_cmd
+
+ifneq ($(HAVE_LIBMQTT),)
+MODULES += mqtt
+endif
+
+ifneq ($(HAVE_PTHREAD),)
+MODULES += aubridge aufile
+endif
+ifneq ($(USE_VIDEO),)
+MODULES += vidloop selfview vidbridge
+ifneq ($(HAVE_PTHREAD),)
+MODULES += fakevideo
+endif
+endif
+
+
+ifneq ($(USE_ALSA),)
+MODULES += alsa
+endif
+ifneq ($(USE_AMR),)
+MODULES += amr
+endif
+ifneq ($(USE_AUDIOUNIT),)
+MODULES += audiounit
+endif
+ifneq ($(USE_AVCAPTURE),)
+MODULES += avcapture
+endif
+ifneq ($(USE_AVCODEC),)
+MODULES += avcodec
+ifneq ($(USE_AVFORMAT),)
+MODULES += avformat
+endif
+ifneq ($(USE_AVAHI),)
+MODULES += avahi
+endif
+endif
+ifneq ($(USE_BV32),)
+MODULES += bv32
+endif
+ifneq ($(USE_CAIRO),)
+MODULES += cairo
+MODULES += vidinfo
+ifneq ($(USE_MPG123),)
+MODULES += rst
+endif
+endif
+ifneq ($(USE_CONS),)
+MODULES += cons
+endif
+ifneq ($(USE_COREAUDIO),)
+MODULES += coreaudio
+endif
+ifneq ($(USE_DTLS_SRTP),)
+MODULES += dtls_srtp
+endif
+ifneq ($(USE_QTCAPTURE),)
+MODULES += qtcapture
+CFLAGS += -DQTCAPTURE_RUNLOOP
+endif
+ifneq ($(USE_EVDEV),)
+MODULES += evdev
+endif
+ifneq ($(USE_G711),)
+MODULES += g711
+endif
+ifneq ($(USE_G722),)
+MODULES += g722
+endif
+ifneq ($(USE_G722_1),)
+MODULES += g7221
+endif
+ifneq ($(USE_G726),)
+MODULES += g726
+endif
+ifneq ($(USE_GSM),)
+MODULES += gsm
+endif
+ifneq ($(USE_GST),)
+MODULES += gst
+endif
+ifneq ($(USE_GST1),)
+MODULES += gst1
+endif
+ifneq ($(USE_GST_VIDEO),)
+MODULES += gst_video
+endif
+ifneq ($(USE_GST_VIDEO1),)
+MODULES += gst_video1
+endif
+ifneq ($(USE_GTK),)
+MODULES += gtk
+endif
+ifneq ($(USE_H265),)
+MODULES += h265
+endif
+ifneq ($(USE_ILBC),)
+MODULES += ilbc
+endif
+ifneq ($(USE_ISAC),)
+MODULES += isac
+endif
+ifneq ($(USE_JACK),)
+MODULES += jack
+endif
+ifneq ($(USE_L16),)
+MODULES += l16
+endif
+ifneq ($(USE_OPENGL),)
+MODULES += opengl
+endif
+ifneq ($(USE_OPENGLES),)
+MODULES += opengles
+endif
+ifneq ($(USE_OPUS),)
+MODULES += opus
+endif
+ifneq ($(USE_MPA),)
+MODULES += mpa
+endif
+ifneq ($(USE_OSS),)
+MODULES += oss
+endif
+ifneq ($(USE_PLC),)
+MODULES += plc
+endif
+ifneq ($(USE_PORTAUDIO),)
+MODULES += portaudio
+endif
+ifneq ($(USE_PULSE),)
+MODULES += pulse
+endif
+ifneq ($(USE_SDL),)
+MODULES += sdl
+endif
+ifneq ($(USE_SDL2),)
+MODULES += sdl2
+endif
+ifneq ($(USE_SILK),)
+MODULES += silk
+endif
+ifneq ($(USE_SNDFILE),)
+MODULES += sndfile
+endif
+ifneq ($(USE_SPEEX),)
+MODULES += speex
+endif
+ifneq ($(USE_SPEEX_AEC),)
+MODULES += speex_aec
+endif
+ifneq ($(USE_SPEEX_PP),)
+MODULES += speex_pp
+endif
+ifneq ($(USE_STDIO),)
+MODULES += stdio
+endif
+ifneq ($(USE_SYSLOG),)
+MODULES += syslog
+endif
+ifneq ($(USE_V4L),)
+MODULES += v4l
+endif
+ifneq ($(USE_V4L2),)
+MODULES += v4l2 v4l2_codec
+endif
+ifneq ($(USE_OMX_RPI),)
+MODULES += omx
+endif
+ifneq ($(USE_OMX_BELLAGIO),)
+MODULES += omx
+endif
+ifneq ($(USE_VPX),)
+MODULES += vp8
+MODULES += $(shell pkg-config 'vpx >= 1.3.0' && echo "vp9")
+endif
+ifneq ($(USE_WINWAVE),)
+MODULES += winwave
+endif
+ifneq ($(USE_X11),)
+MODULES += x11 x11grab
+endif
+ifneq ($(USE_ZRTP),)
+MODULES += zrtp
+endif
+ifneq ($(USE_GZRTP),)
+MODULES += gzrtp
+endif
+ifneq ($(USE_DSHOW),)
+MODULES += dshow
+endif
diff --git a/mk/win32/baresip.sln b/mk/win32/baresip.sln
new file mode 100644
index 0000000..7c791e8
--- /dev/null
+++ b/mk/win32/baresip.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baresip-win32", "baresip.vcxproj", "{4B89C2D8-FB32-4D7C-9019-752A5664781C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {3E767371-A72B-4F5C-A695-8F844B0889C5} = {3E767371-A72B-4F5C-A695-8F844B0889C5}
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} = {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "re-win32", "..\..\..\re\mk\win32\re.vcxproj", "{40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rem-win32", "..\..\..\rem\mk\win32\rem.vcxproj", "{3E767371-A72B-4F5C-A695-8F844B0889C5}"
+ ProjectSection(ProjectDependencies) = postProject
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D} = {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Debug|Win32.Build.0 = Debug|Win32
+ {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Release|Win32.ActiveCfg = Release|Win32
+ {4B89C2D8-FB32-4D7C-9019-752A5664781C}.Release|Win32.Build.0 = Release|Win32
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Debug|Win32.Build.0 = Debug|Win32
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Release|Win32.ActiveCfg = Release|Win32
+ {40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}.Release|Win32.Build.0 = Release|Win32
+ {3E767371-A72B-4F5C-A695-8F844B0889C5}.Debug|Win32.ActiveCfg = Debug|Win32
+ {3E767371-A72B-4F5C-A695-8F844B0889C5}.Debug|Win32.Build.0 = Debug|Win32
+ {3E767371-A72B-4F5C-A695-8F844B0889C5}.Release|Win32.ActiveCfg = Release|Win32
+ {3E767371-A72B-4F5C-A695-8F844B0889C5}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/mk/win32/baresip.vcxproj b/mk/win32/baresip.vcxproj
new file mode 100644
index 0000000..c321b5f
--- /dev/null
+++ b/mk/win32/baresip.vcxproj
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>baresip-win32</ProjectName>
+ <ProjectGuid>{4B89C2D8-FB32-4D7C-9019-752A5664781C}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <PlatformToolset>v140</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <PlatformToolset>v140</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <CompileAs>CompileAsC</CompileAs>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalOptions>winmm.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions)</AdditionalOptions>
+ <OutputFile>$(OutDir)baresip-win32.exe</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)baresip-win32.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>include;..\..\include;..\..\..\re\include;..\..\..\rem\include;..\..\..\misc;..\..\..\dirent\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;STATIC;HAVE_IO_H;HAVE_SELECT;USE_VIDEO;_CRT_SECURE_NO_DEPRECATE;FD_SETSIZE=1024;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalOptions>winmm.lib wsock32.lib ..\..\..\re\$(Platform)\$(Configuration)\bin\re-win32.lib ..\..\..\rem\$(Platform)\$(Configuration)\bin\rem-win32.lib %(AdditionalOptions)</AdditionalOptions>
+ <OutputFile>$(OutDir)baresip-win32.exe</OutputFile>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\rem\mk\win32\rem.vcxproj">
+ <Project>{3e767371-a72b-4f5c-a695-8f844b0889c5}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\re\mk\win32\re.vcxproj">
+ <Project>{40b28df6-4b4a-411a-9eb7-8d80c2a29b9d}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\baresip.h" />
+ <ClInclude Include="..\..\modules\amr\amr.h" />
+ <ClInclude Include="..\..\modules\natpmp\libnatpmp.h" />
+ <ClInclude Include="..\..\modules\presence\presence.h" />
+ <ClInclude Include="..\..\modules\srtp\sdes.h" />
+ <ClInclude Include="..\..\modules\vidbridge\vidbridge.h" />
+ <ClInclude Include="..\..\modules\winwave\winwave.h" />
+ <ClInclude Include="..\..\src\core.h" />
+ <ClInclude Include="..\..\src\magic.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\modules\account\account.c" />
+ <ClCompile Include="..\..\modules\amr\amr.c" />
+ <ClCompile Include="..\..\modules\amr\sdp.c" />
+ <ClCompile Include="..\..\modules\auloop\auloop.c" />
+ <ClCompile Include="..\..\modules\b2bua\b2bua.c" />
+ <ClCompile Include="..\..\modules\cons\cons.c" />
+ <ClCompile Include="..\..\modules\contact\contact.c" />
+ <ClCompile Include="..\..\modules\debug_cmd\debug_cmd.c" />
+ <ClCompile Include="..\..\modules\dshow\dshow.cpp">
+ <CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">CompileAsCpp</CompileAs>
+ <CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">CompileAsCpp</CompileAs>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\echo\echo.c" />
+ <ClCompile Include="..\..\modules\g711\g711.c" />
+ <ClCompile Include="..\..\modules\httpd\httpd.c" />
+ <ClCompile Include="..\..\modules\ice\ice.c" />
+ <ClCompile Include="..\..\modules\l16\l16.c" />
+ <ClCompile Include="..\..\modules\menu\menu.c" />
+ <ClCompile Include="..\..\modules\mwi\mwi.c" />
+ <ClCompile Include="..\..\modules\natbd\natbd.c" />
+ <ClCompile Include="..\..\modules\natpmp\libnatpmp.c" />
+ <ClCompile Include="..\..\modules\natpmp\natpmp.c" />
+ <ClCompile Include="..\..\modules\presence\notifier.c" />
+ <ClCompile Include="..\..\modules\presence\presence.c" />
+ <ClCompile Include="..\..\modules\presence\publisher.c" />
+ <ClCompile Include="..\..\modules\presence\subscriber.c" />
+ <ClCompile Include="..\..\modules\selfview\selfview.c" />
+ <ClCompile Include="..\..\modules\srtp\sdes.c" />
+ <ClCompile Include="..\..\modules\srtp\srtp.c" />
+ <ClCompile Include="..\..\modules\stun\stun.c" />
+ <ClCompile Include="..\..\modules\turn\turn.c" />
+ <ClCompile Include="..\..\modules\uuid\uuid.c" />
+ <ClCompile Include="..\..\modules\vidbridge\disp.c" />
+ <ClCompile Include="..\..\modules\vidbridge\src.c" />
+ <ClCompile Include="..\..\modules\vidbridge\vidbridge.c" />
+ <ClCompile Include="..\..\modules\vidloop\vidloop.c" />
+ <ClCompile Include="..\..\modules\vumeter\vumeter.c" />
+ <ClCompile Include="..\..\modules\wincons\wincons.c" />
+ <ClCompile Include="..\..\modules\winwave\play.c" />
+ <ClCompile Include="..\..\modules\winwave\src.c" />
+ <ClCompile Include="..\..\modules\winwave\winwave.c" />
+ <ClCompile Include="..\..\src\account.c" />
+ <ClCompile Include="..\..\src\aucodec.c" />
+ <ClCompile Include="..\..\src\audio.c" />
+ <ClCompile Include="..\..\src\aufilt.c" />
+ <ClCompile Include="..\..\src\auplay.c" />
+ <ClCompile Include="..\..\src\ausrc.c" />
+ <ClCompile Include="..\..\src\baresip.c" />
+ <ClCompile Include="..\..\src\bfcp.c" />
+ <ClCompile Include="..\..\src\call.c" />
+ <ClCompile Include="..\..\src\cmd.c" />
+ <ClCompile Include="..\..\src\conf.c" />
+ <ClCompile Include="..\..\src\config.c" />
+ <ClCompile Include="..\..\src\contact.c" />
+ <ClCompile Include="..\..\src\h264.c" />
+ <ClCompile Include="..\..\src\log.c" />
+ <ClCompile Include="..\..\src\main.c" />
+ <ClCompile Include="..\..\src\mctrl.c" />
+ <ClCompile Include="..\..\src\menc.c" />
+ <ClCompile Include="..\..\src\message.c" />
+ <ClCompile Include="..\..\src\metric.c" />
+ <ClCompile Include="..\..\src\mnat.c" />
+ <ClCompile Include="..\..\src\module.c" />
+ <ClCompile Include="..\..\src\mos.c" />
+ <ClCompile Include="..\..\src\net.c" />
+ <ClCompile Include="..\..\src\play.c" />
+ <ClCompile Include="..\..\src\realtime.c" />
+ <ClCompile Include="..\..\src\reg.c" />
+ <ClCompile Include="..\..\src\rtpkeep.c" />
+ <ClCompile Include="..\..\src\sdp.c" />
+ <ClCompile Include="..\..\src\sipreq.c" />
+ <ClCompile Include="..\..\src\stream.c" />
+ <ClCompile Include="..\..\src\ua.c" />
+ <ClCompile Include="..\..\src\ui.c" />
+ <ClCompile Include="..\..\src\vidcodec.c" />
+ <ClCompile Include="..\..\src\video.c" />
+ <ClCompile Include="..\..\src\vidfilt.c" />
+ <ClCompile Include="..\..\src\vidisp.c" />
+ <ClCompile Include="..\..\src\vidsrc.c" />
+ <ClCompile Include="static.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/mk/win32/baresip.vcxproj.filters b/mk/win32/baresip.vcxproj.filters
new file mode 100644
index 0000000..5098a2b
--- /dev/null
+++ b/mk/win32/baresip.vcxproj.filters
@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="include">
+ <UniqueIdentifier>{2334866b-8357-4e18-8898-48db4dfe2a15}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src">
+ <UniqueIdentifier>{5c30c989-28e5-402f-aaa0-860eafa95cc5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\video">
+ <UniqueIdentifier>{f5f435ce-121f-4b09-b66f-89e75e8abb40}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\static">
+ <UniqueIdentifier>{7f749aed-9d81-46c6-bdde-690ddc51a0a4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules">
+ <UniqueIdentifier>{6c97676c-7c18-404f-85a1-2fded4f104e3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\account">
+ <UniqueIdentifier>{f528695e-cf16-4c35-89ed-adcc330b63bc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\amr">
+ <UniqueIdentifier>{b8e32268-7d2c-46ae-b6a1-d7b02bbd4026}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\auloop">
+ <UniqueIdentifier>{0e11ace7-27c2-4ad3-8b5e-b3bf772f0b4d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\b2bua">
+ <UniqueIdentifier>{f44d6650-15a0-4fb3-8fe3-d91f522be831}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\cons">
+ <UniqueIdentifier>{6606d763-a0be-4bcc-a689-a9e5e38df7f5}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\contact">
+ <UniqueIdentifier>{785597c1-f018-4f86-ae98-4855e6130bf7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\debug_cmd">
+ <UniqueIdentifier>{b139606b-86de-4c90-af91-f44b04bd1330}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\dshow">
+ <UniqueIdentifier>{4c01b3b6-a748-4cf1-af45-a8d5b7735267}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\echo">
+ <UniqueIdentifier>{3c2cd0c3-9de0-42ca-8f3a-6d7d929ce501}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\g711">
+ <UniqueIdentifier>{e679754c-643a-4284-9ed6-bf6fdc73e152}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\httpd">
+ <UniqueIdentifier>{8ca10960-9be4-4d19-9e32-2ae5bc415c0a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\ice">
+ <UniqueIdentifier>{1def0f02-ca40-49ee-b5e1-95aac5d2dda3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\l16">
+ <UniqueIdentifier>{5446737f-2a0f-4b4f-a86e-55d9c20118e7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\menu">
+ <UniqueIdentifier>{ebbcb368-d1fa-4899-80cf-e43ff4fde9b2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\mwi">
+ <UniqueIdentifier>{a7b0f22a-b82a-4594-a367-d8b8a9882f0d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\natbd">
+ <UniqueIdentifier>{7ceb961c-1033-4259-8f3e-ffa2d28a0f12}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\natpmp">
+ <UniqueIdentifier>{c093ccc3-d662-46e0-83df-2eb34e7e7d21}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\presence">
+ <UniqueIdentifier>{c82f29fb-bb8d-45c7-ad53-d25e997c1453}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\selfview">
+ <UniqueIdentifier>{ed5cc330-666d-4f9a-81a2-db7873b7abaa}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\srtp">
+ <UniqueIdentifier>{31ac6861-a9e9-467a-82ae-53d1d929e696}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\stun">
+ <UniqueIdentifier>{6e5dac26-781e-4683-958f-2e18fdc0b42e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\turn">
+ <UniqueIdentifier>{5a3c72cb-a1a5-4c46-b0b5-761d3677e56a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\uuid">
+ <UniqueIdentifier>{a4d5f1d9-5629-479e-86d4-4e7b6d58a38a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vidbridge">
+ <UniqueIdentifier>{044bc40b-b2a8-4450-b76a-d959559df6b1}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vidloop">
+ <UniqueIdentifier>{71dc9cb5-19b1-4f74-bc23-02ca6b5a10b3}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\vumeter">
+ <UniqueIdentifier>{bc7810ea-bc52-4bee-a15c-aed12cf2a777}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\wincons">
+ <UniqueIdentifier>{8e7e8b8a-dbe7-4b58-80f3-e632b40d0e4e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="modules\winwave">
+ <UniqueIdentifier>{e3bd480b-289e-4754-b1a2-55ea85a7ef45}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\baresip.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\core.h">
+ <Filter>src</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\magic.h">
+ <Filter>src</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\amr\amr.h">
+ <Filter>modules\amr</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\natpmp\libnatpmp.h">
+ <Filter>modules\natpmp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\presence\presence.h">
+ <Filter>modules\presence</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\srtp\sdes.h">
+ <Filter>modules\srtp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\vidbridge\vidbridge.h">
+ <Filter>modules\vidbridge</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\modules\winwave\winwave.h">
+ <Filter>modules\winwave</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\src\account.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\aucodec.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\audio.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\aufilt.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\auplay.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ausrc.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\baresip.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\call.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\cmd.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\conf.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\config.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\contact.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\log.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\main.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\menc.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\message.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\metric.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mnat.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\module.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mos.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\play.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\realtime.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\reg.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtpkeep.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipreq.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stream.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ua.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ui.c">
+ <Filter>src</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\h264.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mctrl.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidcodec.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\video.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidfilt.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidisp.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\vidsrc.c">
+ <Filter>src\video</Filter>
+ </ClCompile>
+ <ClCompile Include="static.c">
+ <Filter>src\static</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\account\account.c">
+ <Filter>modules\account</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\amr\amr.c">
+ <Filter>modules\amr</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\amr\sdp.c">
+ <Filter>modules\amr</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\auloop\auloop.c">
+ <Filter>modules\auloop</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\b2bua\b2bua.c">
+ <Filter>modules\b2bua</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\cons\cons.c">
+ <Filter>modules\cons</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\contact\contact.c">
+ <Filter>modules\contact</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\debug_cmd\debug_cmd.c">
+ <Filter>modules\debug_cmd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\dshow\dshow.cpp">
+ <Filter>modules\dshow</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\echo\echo.c">
+ <Filter>modules\echo</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\g711\g711.c">
+ <Filter>modules\g711</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\httpd\httpd.c">
+ <Filter>modules\httpd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\ice\ice.c">
+ <Filter>modules\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\l16\l16.c">
+ <Filter>modules\l16</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\menu\menu.c">
+ <Filter>modules\menu</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\mwi\mwi.c">
+ <Filter>modules\mwi</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natbd\natbd.c">
+ <Filter>modules\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natpmp\libnatpmp.c">
+ <Filter>modules\natpmp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\natpmp\natpmp.c">
+ <Filter>modules\natpmp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\notifier.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\presence.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\publisher.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\presence\subscriber.c">
+ <Filter>modules\presence</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\selfview\selfview.c">
+ <Filter>modules\selfview</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\srtp\sdes.c">
+ <Filter>modules\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\srtp\srtp.c">
+ <Filter>modules\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\stun\stun.c">
+ <Filter>modules\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\turn\turn.c">
+ <Filter>modules\turn</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\uuid\uuid.c">
+ <Filter>modules\uuid</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\disp.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\src.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidbridge\vidbridge.c">
+ <Filter>modules\vidbridge</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vidloop\vidloop.c">
+ <Filter>modules\vidloop</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\vumeter\vumeter.c">
+ <Filter>modules\vumeter</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\wincons\wincons.c">
+ <Filter>modules\wincons</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\play.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\src.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\modules\winwave\winwave.c">
+ <Filter>modules\winwave</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/mk/win32/static.c b/mk/win32/static.c
new file mode 100644
index 0000000..4d67d9e
--- /dev/null
+++ b/mk/win32/static.c
@@ -0,0 +1,38 @@
+/* static.c - manually updated */
+#include <re_types.h>
+#include <re_mod.h>
+
+extern const struct mod_export exports_cons;
+extern const struct mod_export exports_wincons;
+extern const struct mod_export exports_g711;
+extern const struct mod_export exports_winwave;
+extern const struct mod_export exports_dshow;
+extern const struct mod_export exports_account;
+extern const struct mod_export exports_contact;
+extern const struct mod_export exports_menu;
+extern const struct mod_export exports_auloop;
+extern const struct mod_export exports_vidloop;
+extern const struct mod_export exports_uuid;
+extern const struct mod_export exports_stun;
+extern const struct mod_export exports_turn;
+extern const struct mod_export exports_ice;
+extern const struct mod_export exports_vumeter;
+
+
+const struct mod_export *mod_table[] = {
+ &exports_wincons,
+ &exports_g711,
+ &exports_winwave,
+ &exports_dshow,
+ &exports_account,
+ &exports_contact,
+ &exports_menu,
+ &exports_auloop,
+ &exports_vidloop,
+ &exports_uuid,
+ &exports_stun,
+ &exports_turn,
+ &exports_ice,
+ &exports_vumeter,
+ NULL
+};
diff --git a/modules/account/account.c b/modules/account/account.c
new file mode 100644
index 0000000..c972908
--- /dev/null
+++ b/modules/account/account.c
@@ -0,0 +1,213 @@
+/**
+ * @file account/account.c Load SIP accounts from file
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup account account
+ *
+ * Load SIP accounts from a file
+ *
+ * This module is loading SIP accounts from file ~/.baresip/accounts.
+ * If the file exist and is readable, all SIP accounts will be populated
+ * from this file. If the file does not exist, a template file will be
+ * created.
+ *
+ * Examples:
+ \verbatim
+ "User 1 with password prompt" <sip:user@domain.com>
+ "User 2 with stored password" <sip:user:pass@domain.com>
+ "User 2 with ICE" <sip:user@1.2.3.4;transport=tcp>;medianat=ice
+ "User 3 with IPv6" <sip:user@[2001:df8:0:16:216:6fff:fe91:614c]:5070>
+ \endverbatim
+ */
+
+
+static int account_write_template(const char *file)
+{
+ FILE *f = NULL;
+ const char *login, *pass, *domain;
+ int r, err = 0;
+
+ info("account: creating accounts template %s\n", file);
+
+ f = fopen(file, "w");
+ if (!f)
+ return errno;
+
+ login = pass = sys_username();
+ if (!login) {
+ login = "user";
+ pass = "pass";
+ }
+
+ domain = net_domain(baresip_network());
+ if (!domain)
+ domain = "domain";
+
+ r = re_fprintf(f,
+ "#\n"
+ "# SIP accounts - one account per line\n"
+ "#\n"
+ "# Displayname <sip:user:password@domain"
+ ";uri-params>;addr-params\n"
+ "#\n"
+ "# uri-params:\n"
+ "# ;transport={udp,tcp,tls}\n"
+ "#\n"
+ "# addr-params:\n"
+ "# ;answermode={manual,early,auto}\n"
+ "# ;audio_codecs=speex/16000,pcma,...\n"
+ "# ;auth_user=username\n"
+ "# ;mediaenc={srtp,srtp-mand,srtp-mandf"
+ ",dtls_srtp,zrtp}\n"
+ "# ;medianat={stun,turn,ice}\n"
+ "# ;outbound=\"sip:primary.example.com"
+ ";transport=tcp\"\n"
+ "# ;outbound2=sip:secondary.example.com\n"
+ "# ;ptime={10,20,30,40,...}\n"
+ "# ;regint=3600\n"
+ "# ;pubint=0 (publishing off)\n"
+ "# ;regq=0.5\n"
+ "# ;rtpkeep={zero,stun,dyna,rtcp}\n"
+ "# ;sipnat={outbound}\n"
+ "# ;stunuser=STUN/TURN/ICE-username\n"
+ "# ;stunpass=STUN/TURN/ICE-password\n"
+ "# ;stunserver=stun:[user:pass]@host[:port]\n"
+ "# ;video_codecs=h264,h263,...\n"
+ "#\n"
+ "# Examples:\n"
+ "#\n"
+ "# <sip:user:secret@domain.com;transport=tcp>\n"
+ "# <sip:user:secret@1.2.3.4;transport=tcp>\n"
+ "# <sip:user:secret@"
+ "[2001:df8:0:16:216:6fff:fe91:614c]:5070"
+ ";transport=tcp>\n"
+ "#\n"
+ "#<sip:%s:%s@%s>\n", login, pass, domain);
+ if (r < 0)
+ err = ENOMEM;
+
+ if (f)
+ (void)fclose(f);
+
+ return err;
+}
+
+
+/**
+ * Add a User-Agent (UA)
+ *
+ * @param addr SIP Address string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int line_handler(const struct pl *addr, void *arg)
+{
+ char buf[512];
+ struct ua *ua;
+ struct account *acc;
+ int err;
+ (void)arg;
+
+ (void)pl_strcpy(addr, buf, sizeof(buf));
+
+ err = ua_alloc(&ua, buf);
+ if (err)
+ return err;
+
+ acc = ua_account(ua);
+ if (!acc) {
+ warning("account: no account for this ua\n");
+ return ENOENT;
+ }
+
+ /* optional password prompt */
+ if (!str_isset(account_auth_pass(acc))) {
+ char *pass = NULL;
+
+ (void)re_printf("Please enter password for %s: ",
+ account_aor(acc));
+
+ err = ui_password_prompt(&pass);
+ if (err)
+ goto out;
+
+ err = account_set_auth_pass(acc, pass);
+
+ mem_deref(pass);
+ }
+
+ out:
+ return err;
+}
+
+
+/**
+ * Read the SIP accounts from the ~/.baresip/accounts file
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int account_read_file(void)
+{
+ char path[256] = "", file[256] = "";
+ uint32_t n;
+ int err;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err) {
+ warning("account: conf_path_get (%m)\n", err);
+ return err;
+ }
+
+ if (re_snprintf(file, sizeof(file), "%s/accounts", path) < 0)
+ return ENOMEM;
+
+ if (!conf_fileexist(file)) {
+
+ (void)fs_mkdir(path, 0700);
+
+ err = account_write_template(file);
+ if (err)
+ return err;
+ }
+
+ err = conf_parse(file, line_handler, NULL);
+ if (err)
+ return err;
+
+ n = list_count(uag_list());
+ info("Populated %u account%s\n", n, 1==n ? "" : "s");
+
+ if (list_isempty(uag_list())) {
+ info("account: No SIP accounts found\n"
+ " -- check your config "
+ "or add an account using 'uanew' command\n");
+ }
+
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ return account_read_file();
+}
+
+
+static int module_close(void)
+{
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(account) = {
+ "account",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/account/module.mk b/modules/account/module.mk
new file mode 100644
index 0000000..8f461d6
--- /dev/null
+++ b/modules/account/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+MOD := account
+$(MOD)_SRCS += account.c
+
+include mk/mod.mk
diff --git a/modules/alsa/alsa.c b/modules/alsa/alsa.c
new file mode 100644
index 0000000..78de296
--- /dev/null
+++ b/modules/alsa/alsa.c
@@ -0,0 +1,184 @@
+/**
+ * @file alsa.c ALSA sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _POSIX_SOURCE 1
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "alsa.h"
+
+
+/**
+ * @defgroup alsa alsa
+ *
+ * Advanced Linux Sound Architecture (ALSA) audio driver module
+ *
+ *
+ * References:
+ *
+ * http://www.alsa-project.org/main/index.php/Main_Page
+ */
+
+
+char alsa_dev[64] = "default";
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+
+int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch,
+ uint32_t num_frames,
+ snd_pcm_format_t pcmfmt)
+{
+ snd_pcm_hw_params_t *hw_params = NULL;
+ snd_pcm_uframes_t period = num_frames, bufsize = num_frames * 4;
+ int err;
+
+ debug("alsa: reset: srate=%u, ch=%u, num_frames=%u, pcmfmt=%s\n",
+ srate, ch, num_frames, snd_pcm_format_name(pcmfmt));
+
+ err = snd_pcm_hw_params_malloc(&hw_params);
+ if (err < 0) {
+ warning("alsa: cannot allocate hw params (%s)\n",
+ snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_any(pcm, hw_params);
+ if (err < 0) {
+ warning("alsa: cannot initialize hw params (%s)\n",
+ snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_set_access(pcm, hw_params,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0) {
+ warning("alsa: cannot set access type (%s)\n",
+ snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_set_format(pcm, hw_params, pcmfmt);
+ if (err < 0) {
+ warning("alsa: cannot set sample format %d (%s)\n",
+ pcmfmt, snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_set_rate(pcm, hw_params, srate, 0);
+ if (err < 0) {
+ warning("alsa: cannot set sample rate to %u Hz (%s)\n",
+ srate, snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_set_channels(pcm, hw_params, ch);
+ if (err < 0) {
+ warning("alsa: cannot set channel count to %d (%s)\n",
+ ch, snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_hw_params_set_period_size_near(pcm, hw_params,
+ &period, 0);
+ if (err < 0) {
+ warning("alsa: cannot set period size to %d (%s)\n",
+ period, snd_strerror(err));
+ }
+
+ err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &bufsize);
+ if (err < 0) {
+ warning("alsa: cannot set buffer size to %d (%s)\n",
+ bufsize, snd_strerror(err));
+ }
+
+ err = snd_pcm_hw_params(pcm, hw_params);
+ if (err < 0) {
+ warning("alsa: cannot set parameters (%s)\n",
+ snd_strerror(err));
+ goto out;
+ }
+
+ err = snd_pcm_prepare(pcm);
+ if (err < 0) {
+ warning("alsa: cannot prepare audio interface for use (%s)\n",
+ snd_strerror(err));
+ goto out;
+ }
+
+ err = 0;
+
+ out:
+ snd_pcm_hw_params_free(hw_params);
+
+ if (err) {
+ warning("alsa: init failed: err=%d\n", err);
+ }
+
+ return err;
+}
+
+
+snd_pcm_format_t aufmt_to_alsaformat(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return SND_PCM_FORMAT_S16;
+ case AUFMT_FLOAT: return SND_PCM_FORMAT_FLOAT;
+ case AUFMT_S24_3LE: return SND_PCM_FORMAT_S24_3LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+
+static int alsa_init(void)
+{
+ struct pl val;
+ int err;
+
+ /* XXX: remove check later */
+ if (0 == conf_get(conf_cur(), "alsa_sample_format", &val)) {
+
+ warning("alsa: alsa_sample_format is deprecated"
+ " -- use ausrc_format or auplay_format instead\n");
+
+ (void)val;
+ }
+
+ err = ausrc_register(&ausrc, baresip_ausrcl(),
+ "alsa", alsa_src_alloc);
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "alsa", alsa_play_alloc);
+
+ return err;
+}
+
+
+static int alsa_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ /* releases all resources of the global configuration tree,
+ and sets snd_config to NULL. */
+ snd_config_update_free_global();
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(alsa) = {
+ "alsa",
+ "sound",
+ alsa_init,
+ alsa_close
+};
diff --git a/modules/alsa/alsa.h b/modules/alsa/alsa.h
new file mode 100644
index 0000000..fbd67f2
--- /dev/null
+++ b/modules/alsa/alsa.h
@@ -0,0 +1,20 @@
+/**
+ * @file alsa.h ALSA sound driver -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+extern char alsa_dev[64];
+
+
+int alsa_reset(snd_pcm_t *pcm, uint32_t srate, uint32_t ch,
+ uint32_t num_frames, snd_pcm_format_t pcmfmt);
+snd_pcm_format_t aufmt_to_alsaformat(enum aufmt fmt);
+int alsa_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+int alsa_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
diff --git a/modules/alsa/alsa_play.c b/modules/alsa/alsa_play.c
new file mode 100644
index 0000000..6547e21
--- /dev/null
+++ b/modules/alsa/alsa_play.c
@@ -0,0 +1,169 @@
+/**
+ * @file alsa_play.c ALSA sound driver - player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _POSIX_SOURCE 1
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "alsa.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* pointer to base-class (inheritance) */
+ pthread_t thread;
+ bool run;
+ snd_pcm_t *write;
+ void *sampv;
+ size_t sampc;
+ auplay_write_h *wh;
+ void *arg;
+ struct auplay_prm prm;
+ char *device;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ /* Wait for termination of other thread */
+ if (st->run) {
+ debug("alsa: stopping playback thread (%s)\n", st->device);
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->write)
+ snd_pcm_close(st->write);
+
+ mem_deref(st->sampv);
+ mem_deref(st->device);
+}
+
+
+static void *write_thread(void *arg)
+{
+ struct auplay_st *st = arg;
+ int n;
+ int num_frames;
+
+ num_frames = st->prm.srate * st->prm.ptime / 1000;
+
+ while (st->run) {
+ const int samples = num_frames;
+ void *sampv;
+
+ st->wh(st->sampv, st->sampc, st->arg);
+
+ sampv = st->sampv;
+
+ n = snd_pcm_writei(st->write, sampv, samples);
+
+ if (-EPIPE == n) {
+ snd_pcm_prepare(st->write);
+
+ n = snd_pcm_writei(st->write, sampv, samples);
+ if (n != samples) {
+ warning("alsa: write error: %s\n",
+ snd_strerror(n));
+ }
+ }
+ else if (n < 0) {
+ warning("alsa: write error: %s\n", snd_strerror(n));
+ }
+ else if (n != samples) {
+ warning("alsa: write: wrote %d of %d samples\n",
+ n, samples);
+ }
+ }
+
+ return NULL;
+}
+
+
+int alsa_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ snd_pcm_format_t pcmfmt;
+ int num_frames;
+ int err;
+
+ if (!stp || !ap || !prm || !wh)
+ return EINVAL;
+
+ if (!str_isset(device))
+ device = alsa_dev;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = str_dup(&st->device, device);
+ if (err)
+ goto out;
+
+ st->prm = *prm;
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ num_frames = st->prm.srate * st->prm.ptime / 1000;
+
+ st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = snd_pcm_open(&st->write, st->device, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0) {
+ warning("alsa: could not open auplay device '%s' (%s)\n",
+ st->device, snd_strerror(err));
+ goto out;
+ }
+
+ pcmfmt = aufmt_to_alsaformat(prm->fmt);
+ if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) {
+ warning("alsa: unknown sample format '%s'\n",
+ aufmt_name(prm->fmt));
+ err = EINVAL;
+ goto out;
+ }
+
+ err = alsa_reset(st->write, st->prm.srate, st->prm.ch, num_frames,
+ pcmfmt);
+ if (err) {
+ warning("alsa: could not reset player '%s' (%s)\n",
+ st->device, snd_strerror(err));
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, write_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ debug("alsa: playback started (%s)\n", st->device);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/alsa/alsa_src.c b/modules/alsa/alsa_src.c
new file mode 100644
index 0000000..a21f846
--- /dev/null
+++ b/modules/alsa/alsa_src.c
@@ -0,0 +1,174 @@
+/**
+ * @file alsa_src.c ALSA sound driver - recorder
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _POSIX_SOURCE 1
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "alsa.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* pointer to base-class (inheritance) */
+ pthread_t thread;
+ bool run;
+ snd_pcm_t *read;
+ void *sampv;
+ size_t sampc;
+ ausrc_read_h *rh;
+ void *arg;
+ struct ausrc_prm prm;
+ char *device;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ /* Wait for termination of other thread */
+ if (st->run) {
+ debug("alsa: stopping recording thread (%s)\n", st->device);
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->read)
+ snd_pcm_close(st->read);
+
+ mem_deref(st->sampv);
+ mem_deref(st->device);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+ int num_frames;
+ int err;
+
+ num_frames = st->prm.srate * st->prm.ptime / 1000;
+
+ /* Start */
+ err = snd_pcm_start(st->read);
+ if (err) {
+ warning("alsa: could not start ausrc device '%s' (%s)\n",
+ st->device, snd_strerror(err));
+ goto out;
+ }
+
+ while (st->run) {
+ size_t sampc;
+ void *sampv;
+
+ sampv = st->sampv;
+
+ err = snd_pcm_readi(st->read, sampv, num_frames);
+ if (err == -EPIPE) {
+ snd_pcm_prepare(st->read);
+ continue;
+ }
+ else if (err <= 0) {
+ continue;
+ }
+
+ sampc = err * st->prm.ch;
+
+ st->rh(st->sampv, sampc, st->arg);
+ }
+
+ out:
+ return NULL;
+}
+
+
+int alsa_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ snd_pcm_format_t pcmfmt;
+ int num_frames;
+ int err;
+ (void)ctx;
+ (void)errh;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ if (!str_isset(device))
+ device = alsa_dev;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = str_dup(&st->device, device);
+ if (err)
+ goto out;
+
+ st->prm = *prm;
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ num_frames = st->prm.srate * st->prm.ptime / 1000;
+
+ st->sampv = mem_alloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = snd_pcm_open(&st->read, st->device, SND_PCM_STREAM_CAPTURE, 0);
+ if (err < 0) {
+ warning("alsa: could not open ausrc device '%s' (%s)\n",
+ st->device, snd_strerror(err));
+ goto out;
+ }
+
+ pcmfmt = aufmt_to_alsaformat(prm->fmt);
+ if (pcmfmt == SND_PCM_FORMAT_UNKNOWN) {
+ warning("alsa: unknown sample format '%s'\n",
+ aufmt_name(prm->fmt));
+ err = EINVAL;
+ goto out;
+ }
+
+ err = alsa_reset(st->read, st->prm.srate, st->prm.ch, num_frames,
+ pcmfmt);
+ if (err) {
+ warning("alsa: could not reset source '%s' (%s)\n",
+ st->device, snd_strerror(err));
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ debug("alsa: recording started (%s) format=%s\n",
+ st->device, aufmt_name(prm->fmt));
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/alsa/module.mk b/modules/alsa/module.mk
new file mode 100644
index 0000000..c93f9b5
--- /dev/null
+++ b/modules/alsa/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := alsa
+$(MOD)_SRCS += alsa.c alsa_src.c alsa_play.c
+$(MOD)_LFLAGS += -lasound
+
+include mk/mod.mk
diff --git a/modules/amr/amr.c b/modules/amr/amr.c
new file mode 100644
index 0000000..885a318
--- /dev/null
+++ b/modules/amr/amr.c
@@ -0,0 +1,344 @@
+/**
+ * @file amr.c Adaptive Multi-Rate (AMR) audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#ifdef AMR_NB
+#include <interf_enc.h>
+#include <interf_dec.h>
+#endif
+#ifdef AMR_WB
+#ifdef _TYPEDEF_H
+#define typedef_h
+#endif
+#include <enc_if.h>
+#include <dec_if.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+#include "amr.h"
+
+
+#ifdef VO_AMRWBENC_ENC_IF_H
+#define IF2E_IF_encode E_IF_encode
+#define IF2D_IF_decode D_IF_decode
+#endif
+
+
+/**
+ * @defgroup amr amr
+ *
+ * This module supports both AMR Narrowband (8000 Hz) and
+ * AMR Wideband (16000 Hz) audio codecs.
+ *
+ * NOTE: only octet-align mode is supported.
+ *
+ *
+ * Reference:
+ *
+ * http://tools.ietf.org/html/rfc4867
+ *
+ * http://www.penguin.cz/~utx/amr
+ */
+
+
+#ifndef L_FRAME16k
+#define L_FRAME16k 320
+#endif
+
+#ifndef NB_SERIAL_MAX
+#define NB_SERIAL_MAX 61
+#endif
+
+enum {
+ FRAMESIZE_NB = 160
+};
+
+
+struct auenc_state {
+ const struct aucodec *ac;
+ void *enc; /**< Encoder state */
+};
+
+struct audec_state {
+ const struct aucodec *ac;
+ void *dec; /**< Decoder state */
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ switch (st->ac->srate) {
+
+#ifdef AMR_NB
+ case 8000:
+ Encoder_Interface_exit(st->enc);
+ break;
+#endif
+
+#ifdef AMR_WB
+ case 16000:
+ E_IF_exit(st->enc);
+ break;
+#endif
+ }
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ switch (st->ac->srate) {
+
+#ifdef AMR_NB
+ case 8000:
+ Decoder_Interface_exit(st->dec);
+ break;
+#endif
+
+#ifdef AMR_WB
+ case 16000:
+ D_IF_exit(st->dec);
+ break;
+#endif
+ }
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ac = ac;
+
+ switch (ac->srate) {
+
+#ifdef AMR_NB
+ case 8000:
+ st->enc = Encoder_Interface_init(0);
+ break;
+#endif
+
+#ifdef AMR_WB
+ case 16000:
+ st->enc = E_IF_init();
+ break;
+#endif
+ }
+
+ if (!st->enc)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ac = ac;
+
+ switch (ac->srate) {
+
+#ifdef AMR_NB
+ case 8000:
+ st->dec = Decoder_Interface_init();
+ break;
+#endif
+
+#ifdef AMR_WB
+ case 16000:
+ st->dec = D_IF_init();
+ break;
+#endif
+ }
+
+ if (!st->dec)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+#ifdef AMR_WB
+static int encode_wb(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int n;
+
+ if (sampc != L_FRAME16k)
+ return EINVAL;
+
+ if (*len < NB_SERIAL_MAX)
+ return ENOMEM;
+
+ /* CMR value 15 indicates that no mode request is present */
+ buf[0] = 15 << 4;
+
+ n = IF2E_IF_encode(st->enc, 8, sampv, &buf[1], 0);
+ if (n <= 0)
+ return EPROTO;
+
+ *len = (1 + n);
+
+ return 0;
+}
+
+
+static int decode_wb(struct audec_state *st, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ if (*sampc < L_FRAME16k)
+ return ENOMEM;
+ if (len > NB_SERIAL_MAX)
+ return EINVAL;
+
+ IF2D_IF_decode(st->dec, &buf[1], sampv, 0);
+
+ *sampc = L_FRAME16k;
+
+ return 0;
+}
+#endif
+
+
+#ifdef AMR_NB
+static int encode_nb(struct auenc_state *st, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ int r;
+
+ if (!st || !buf || !len || !sampv || sampc != FRAMESIZE_NB)
+ return EINVAL;
+ if (*len < NB_SERIAL_MAX)
+ return ENOMEM;
+
+ /* CMR value 15 indicates that no mode request is present */
+ buf[0] = 15 << 4;
+
+ r = Encoder_Interface_Encode(st->enc, MR122, sampv, &buf[1], 0);
+ if (r <= 0)
+ return EPROTO;
+
+ *len = (1 + r);
+
+ return 0;
+}
+
+
+static int decode_nb(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ if (!st || !sampv || !sampc || !buf)
+ return EINVAL;
+
+ if (len > NB_SERIAL_MAX)
+ return EPROTO;
+
+ if (*sampc < L_FRAME16k)
+ return ENOMEM;
+
+ Decoder_Interface_Decode(st->dec, &buf[1], sampv, 0);
+
+ *sampc = FRAMESIZE_NB;
+
+ return 0;
+}
+#endif
+
+
+#ifdef AMR_WB
+static struct aucodec amr_wb = {
+ LE_INIT, NULL, "AMR-WB", 16000, 16000, 1, NULL,
+ encode_update, encode_wb,
+ decode_update, decode_wb,
+ NULL, amr_fmtp_enc, amr_fmtp_cmp
+};
+#endif
+#ifdef AMR_NB
+static struct aucodec amr_nb = {
+ LE_INIT, NULL, "AMR", 8000, 8000, 1, NULL,
+ encode_update, encode_nb,
+ decode_update, decode_nb,
+ NULL, amr_fmtp_enc, amr_fmtp_cmp
+};
+#endif
+
+
+static int module_init(void)
+{
+ int err = 0;
+
+#ifdef AMR_WB
+ aucodec_register(baresip_aucodecl(), &amr_wb);
+#endif
+#ifdef AMR_NB
+ aucodec_register(baresip_aucodecl(), &amr_nb);
+#endif
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+#ifdef AMR_WB
+ aucodec_unregister(&amr_wb);
+#endif
+#ifdef AMR_NB
+ aucodec_unregister(&amr_nb);
+#endif
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(amr) = {
+ "amr",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/amr/amr.h b/modules/amr/amr.h
new file mode 100644
index 0000000..6746a83
--- /dev/null
+++ b/modules/amr/amr.h
@@ -0,0 +1,10 @@
+/**
+ * @file amr/amr.h AMR module -- internal interface
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+
+int amr_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
+bool amr_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg);
diff --git a/modules/amr/module.mk b/modules/amr/module.mk
new file mode 100644
index 0000000..328f048
--- /dev/null
+++ b/modules/amr/module.mk
@@ -0,0 +1,64 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := amr
+$(MOD)_SRCS += amr.c sdp.c
+
+
+ifneq ($(shell [ -d $(SYSROOT)/include/opencore-amrnb ] && echo 1 ),)
+$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/include/opencore-amrnb
+$(MOD)_LFLAGS += -lopencore-amrnb
+else
+ifneq ($(shell [ -d $(SYSROOT_ALT)/include/opencore-amrnb ] && echo 1 ),)
+$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT_ALT)/include/opencore-amrnb
+$(MOD)_LFLAGS += -lopencore-amrnb
+else
+ifneq ($(shell [ -d $(SYSROOT)/local/include/amrnb ] && echo 1),)
+$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/local/include/amrnb
+$(MOD)_LFLAGS += -lamrnb
+else
+ifneq ($(shell [ -d $(SYSROOT)/include/amrnb ] && echo 1),)
+$(MOD)_CFLAGS += -DAMR_NB=1 -I$(SYSROOT)/include/amrnb
+$(MOD)_LFLAGS += -lamrnb
+endif
+endif
+endif
+endif
+
+
+ifneq ($(shell [ -f $(SYSROOT_ALT)/include/opencore-amrwb/enc_if.h ] && \
+ echo 1 ),)
+$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT_ALT)/include/opencore-amrwb
+$(MOD)_LFLAGS += -lopencore-amrwb
+else
+ifneq ($(shell [ -f $(SYSROOT)/local/include/amrwb/enc_if.h ] && echo 1),)
+$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/local/include/amrwb
+$(MOD)_LFLAGS += -lamrwb
+else
+ifneq ($(shell [ -f $(SYSROOT)/include/amrwb/enc_if.h ] && echo 1),)
+$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/include/amrwb
+$(MOD)_LFLAGS += -lamrwb
+else
+ifneq ($(shell [ -f $(SYSROOT)/include/vo-amrwbenc/enc_if.h ] && echo 1),)
+$(MOD)_CFLAGS += -DAMR_WB=1 -I$(SYSROOT)/include/vo-amrwbenc
+$(MOD)_LFLAGS += -lvo-amrwbenc
+endif
+endif
+endif
+endif
+
+
+# extra for decoder
+ifneq ($(shell [ -f $(SYSROOT)/include/opencore-amrwb/dec_if.h ] && echo 1 ),)
+$(MOD)_CFLAGS += -I$(SYSROOT)/include/opencore-amrwb
+$(MOD)_LFLAGS += -lopencore-amrwb
+endif
+
+
+$(MOD)_LFLAGS += -lm
+
+
+include mk/mod.mk
diff --git a/modules/amr/sdp.c b/modules/amr/sdp.c
new file mode 100644
index 0000000..e4b0cdf
--- /dev/null
+++ b/modules/amr/sdp.c
@@ -0,0 +1,56 @@
+/**
+ * @file amr/sdp.c AMR SDP Functions
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "amr.h"
+
+
+static bool amr_octet_align(const char *fmtp)
+{
+ struct pl pl, oa;
+
+ if (!fmtp)
+ return false;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "octet-align", &oa))
+ return 0 == pl_strcmp(&oa, "1");
+
+ return false;
+}
+
+
+int amr_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ const struct aucodec *ac = arg;
+ (void)offer;
+
+ if (!mb || !fmt || !ac)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s octet-align=1\r\n",
+ fmt->id);
+}
+
+
+bool amr_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg)
+{
+ const struct aucodec *ac = arg;
+ (void)lfmtp;
+
+ if (!ac)
+ return false;
+
+ if (!amr_octet_align(rfmtp)) {
+ info("amr: octet-align mode is required\n");
+ return false;
+ }
+
+ return true;
+}
diff --git a/modules/aubridge/aubridge.c b/modules/aubridge/aubridge.c
new file mode 100644
index 0000000..13e0527
--- /dev/null
+++ b/modules/aubridge/aubridge.c
@@ -0,0 +1,67 @@
+/**
+ * @file aubridge.c Audio bridge
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "aubridge.h"
+
+
+/**
+ * @defgroup aubridge aubridge
+ *
+ * Audio bridge module
+ *
+ * This module can be used to connect two audio devices together,
+ * so that all output to AUPLAY device is bridged as the input to
+ * a AUSRC device.
+ *
+ * Sample config:
+ *
+ \verbatim
+ audio_player aubridge,pseudo0
+ audio_source aubridge,pseudo0
+ \endverbatim
+ */
+
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+struct hash *ht_device;
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = hash_alloc(&ht_device, 32);
+ if (err)
+ return err;
+
+ err = ausrc_register(&ausrc, baresip_ausrcl(), "aubridge", src_alloc);
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "aubridge", play_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ ht_device = mem_deref(ht_device);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(aubridge) = {
+ "aubridge",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/aubridge/aubridge.h b/modules/aubridge/aubridge.h
new file mode 100644
index 0000000..7e24b6c
--- /dev/null
+++ b/modules/aubridge/aubridge.h
@@ -0,0 +1,41 @@
+/**
+ * @file aubridge.h Audio bridge -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct device;
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ struct device *dev;
+ struct ausrc_prm prm;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ struct device *dev;
+ struct auplay_prm prm;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+extern struct hash *ht_device;
+
+
+int play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+
+
+int device_connect(struct device **devp, const char *device,
+ struct auplay_st *auplay, struct ausrc_st *ausrc);
+void device_stop(struct device *dev);
diff --git a/modules/aubridge/device.c b/modules/aubridge/device.c
new file mode 100644
index 0000000..392d8e0
--- /dev/null
+++ b/modules/aubridge/device.c
@@ -0,0 +1,187 @@
+/**
+ * @file device.c Audio bridge -- virtual device table
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <pthread.h>
+#include "aubridge.h"
+
+
+/* The packet-time is fixed to 20 milliseconds */
+enum {PTIME = 20};
+
+
+struct device {
+ struct le le;
+ const struct ausrc_st *ausrc;
+ const struct auplay_st *auplay;
+ char name[64];
+ pthread_t thread;
+ volatile bool run;
+};
+
+
+static void destructor(void *arg)
+{
+ struct device *dev = arg;
+
+ device_stop(dev);
+
+ list_unlink(&dev->le);
+}
+
+
+static bool list_apply_handler(struct le *le, void *arg)
+{
+ struct device *st = le->data;
+
+ return 0 == str_cmp(st->name, arg);
+}
+
+
+static struct device *find_device(const char *device)
+{
+ return list_ledata(hash_lookup(ht_device, hash_joaat_str(device),
+ list_apply_handler, (void *)device));
+}
+
+
+static void *device_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct device *dev = arg;
+ struct auresamp rs;
+ int16_t *sampv_in, *sampv_out;
+ size_t sampc_in;
+ size_t sampc_out;
+ int err;
+
+ sampc_in = dev->auplay->prm.srate * dev->auplay->prm.ch * PTIME/1000;
+ sampc_out = dev->ausrc->prm.srate * dev->ausrc->prm.ch * PTIME/1000;
+
+ auresamp_init(&rs);
+
+ sampv_in = mem_alloc(2 * sampc_in, NULL);
+ sampv_out = mem_alloc(2 * sampc_out, NULL);
+ if (!sampv_in || !sampv_out)
+ goto out;
+
+ err = auresamp_setup(&rs,
+ dev->auplay->prm.srate, dev->auplay->prm.ch,
+ dev->ausrc->prm.srate, dev->ausrc->prm.ch);
+ if (err)
+ goto out;
+
+ while (dev->run) {
+
+ (void)sys_msleep(4);
+
+ if (!dev->run)
+ break;
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+
+ if (dev->auplay && dev->auplay->wh) {
+ dev->auplay->wh(sampv_in, sampc_in, dev->auplay->arg);
+ }
+
+ if (rs.resample) {
+ err = auresamp(&rs,
+ sampv_out, &sampc_out,
+ sampv_in, sampc_in);
+ if (err) {
+ warning("aubridge: auresamp error"
+ " sampc_out=%zu, sampc_in=%zu (%m)\n",
+ sampc_out, sampc_in, err);
+ }
+
+ if (dev->ausrc && dev->ausrc->rh) {
+ dev->ausrc->rh(sampv_out, sampc_out,
+ dev->ausrc->arg);
+ }
+ }
+ else {
+ if (dev->ausrc && dev->ausrc->rh) {
+ dev->ausrc->rh(sampv_in, sampc_in,
+ dev->ausrc->arg);
+ }
+ }
+
+ ts += PTIME;
+ }
+
+ out:
+ mem_deref(sampv_in);
+ mem_deref(sampv_out);
+
+ return NULL;
+}
+
+
+int device_connect(struct device **devp, const char *device,
+ struct auplay_st *auplay, struct ausrc_st *ausrc)
+{
+ struct device *dev;
+ int err = 0;
+
+ if (!devp)
+ return EINVAL;
+ if (!str_isset(device))
+ return ENODEV;
+
+ dev = find_device(device);
+ if (dev) {
+ *devp = mem_ref(dev);
+ }
+ else {
+ dev = mem_zalloc(sizeof(*dev), destructor);
+ if (!dev)
+ return ENOMEM;
+
+ str_ncpy(dev->name, device, sizeof(dev->name));
+
+ hash_append(ht_device, hash_joaat_str(device), &dev->le, dev);
+
+ *devp = dev;
+
+ info("aubridge: created device '%s'\n", device);
+ }
+
+ if (auplay)
+ dev->auplay = auplay;
+ if (ausrc)
+ dev->ausrc = ausrc;
+
+ /* wait until we have both SRC+PLAY */
+ if (dev->ausrc && dev->auplay && !dev->run) {
+
+ dev->run = true;
+ err = pthread_create(&dev->thread, NULL, device_thread, dev);
+ if (err) {
+ dev->run = false;
+ }
+ }
+
+ return err;
+}
+
+
+void device_stop(struct device *dev)
+{
+ if (!dev)
+ return;
+
+ dev->auplay = NULL;
+ dev->ausrc = NULL;
+
+ if (dev->run) {
+ dev->run = false;
+ pthread_join(dev->thread, NULL);
+ }
+}
diff --git a/modules/aubridge/module.mk b/modules/aubridge/module.mk
new file mode 100644
index 0000000..b8e0105
--- /dev/null
+++ b/modules/aubridge/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := aubridge
+$(MOD)_SRCS += aubridge.c device.c src.c play.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/aubridge/play.c b/modules/aubridge/play.c
new file mode 100644
index 0000000..ff0b98f
--- /dev/null
+++ b/modules/aubridge/play.c
@@ -0,0 +1,58 @@
+/**
+ * @file aubridge/play.c Audio bridge -- playback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "aubridge.h"
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ device_stop(st->dev);
+
+ mem_deref(st->dev);
+}
+
+
+int play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("aubridge: playback: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->prm = *prm;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = device_connect(&st->dev, device, st, NULL);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/aubridge/src.c b/modules/aubridge/src.c
new file mode 100644
index 0000000..87fd64a
--- /dev/null
+++ b/modules/aubridge/src.c
@@ -0,0 +1,61 @@
+/**
+ * @file aubridge/src.c Audio bridge -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "aubridge.h"
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ device_stop(st->dev);
+
+ mem_deref(st->dev);
+}
+
+
+int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err = 0;
+ (void)ctx;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("aubridge: source: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->prm = *prm;
+ st->rh = rh;
+ st->arg = arg;
+
+ err = device_connect(&st->dev, device, NULL, st);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/audiounit/audiounit.c b/modules/audiounit/audiounit.c
new file mode 100644
index 0000000..b03bcec
--- /dev/null
+++ b/modules/audiounit/audiounit.c
@@ -0,0 +1,97 @@
+/**
+ * @file audiounit.c AudioUnit sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <re.h>
+#include <baresip.h>
+#include "audiounit.h"
+
+
+/**
+ * @defgroup audiounit audiounit
+ *
+ * Audio driver module for OSX/iOS AudioUnit
+ */
+
+
+AudioComponent output_comp = NULL;
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+#if TARGET_OS_IPHONE
+static void interruptionListener(void *data, UInt32 inInterruptionState)
+{
+ (void)data;
+
+ if (inInterruptionState == kAudioSessionBeginInterruption) {
+ info("audiounit: interrupt Begin\n");
+ audiosess_interrupt(true);
+ }
+ else if (inInterruptionState == kAudioSessionEndInterruption) {
+ info("audiounit: interrupt End\n");
+ audiosess_interrupt(false);
+ }
+}
+#endif
+
+
+static int module_init(void)
+{
+ AudioComponentDescription desc;
+ int err;
+
+#if TARGET_OS_IPHONE
+ OSStatus ret;
+
+ ret = AudioSessionInitialize(NULL, NULL, interruptionListener, 0);
+ if (ret && ret != kAudioSessionAlreadyInitialized) {
+ warning("audiounit: AudioSessionInitialize: %d\n", ret);
+ return ENODEV;
+ }
+#endif
+
+ desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IPHONE
+ desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
+#else
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+#endif
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ output_comp = AudioComponentFindNext(NULL, &desc);
+ if (!output_comp) {
+ warning("audiounit: Voice Processing I/O not found\n");
+ return ENOENT;
+ }
+
+ err = auplay_register(&auplay, baresip_auplayl(),
+ "audiounit", audiounit_player_alloc);
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "audiounit", audiounit_recorder_alloc);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(audiounit) = {
+ "audiounit",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/audiounit/audiounit.h b/modules/audiounit/audiounit.h
new file mode 100644
index 0000000..28e4a3a
--- /dev/null
+++ b/modules/audiounit/audiounit.h
@@ -0,0 +1,27 @@
+/**
+ * @file audiounit.h AudioUnit sound driver -- Internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+AudioComponent output_comp;
+
+
+struct audiosess;
+struct audiosess_st;
+
+typedef void (audiosess_int_h)(bool start, void *arg);
+
+int audiosess_alloc(struct audiosess_st **stp,
+ audiosess_int_h *inth, void *arg);
+void audiosess_interrupt(bool interrupted);
+
+
+int audiounit_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/audiounit/module.mk b/modules/audiounit/module.mk
new file mode 100644
index 0000000..1dd1a30
--- /dev/null
+++ b/modules/audiounit/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := audiounit
+$(MOD)_SRCS += audiounit.c
+$(MOD)_SRCS += sess.c
+$(MOD)_SRCS += player.c
+$(MOD)_SRCS += recorder.c
+$(MOD)_LFLAGS += -framework CoreAudio -framework AudioToolbox
+
+include mk/mod.mk
diff --git a/modules/audiounit/player.c b/modules/audiounit/player.c
new file mode 100644
index 0000000..230b1ad
--- /dev/null
+++ b/modules/audiounit/player.c
@@ -0,0 +1,212 @@
+/**
+ * @file audiounit/player.c AudioUnit output player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "audiounit.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ struct audiosess_st *sess;
+ AudioUnit au;
+ pthread_mutex_t mutex;
+ auplay_write_h *wh;
+ void *arg;
+ uint32_t sampsz;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ pthread_mutex_lock(&st->mutex);
+ st->wh = NULL;
+ pthread_mutex_unlock(&st->mutex);
+
+ AudioOutputUnitStop(st->au);
+ AudioUnitUninitialize(st->au);
+ AudioComponentInstanceDispose(st->au);
+
+ mem_deref(st->sess);
+
+ pthread_mutex_destroy(&st->mutex);
+}
+
+
+static OSStatus output_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct auplay_st *st = inRefCon;
+ auplay_write_h *wh;
+ void *arg;
+ uint32_t i;
+
+ (void)ioActionFlags;
+ (void)inTimeStamp;
+ (void)inBusNumber;
+ (void)inNumberFrames;
+
+ pthread_mutex_lock(&st->mutex);
+ wh = st->wh;
+ arg = st->arg;
+ pthread_mutex_unlock(&st->mutex);
+
+ if (!wh)
+ return 0;
+
+ for (i = 0; i < ioData->mNumberBuffers; ++i) {
+
+ AudioBuffer *ab = &ioData->mBuffers[i];
+
+ wh(ab->mData, ab->mDataByteSize/st->sampsz, arg);
+ }
+
+ return 0;
+}
+
+
+static void interrupt_handler(bool interrupted, void *arg)
+{
+ struct auplay_st *st = arg;
+
+ if (interrupted)
+ AudioOutputUnitStop(st->au);
+ else
+ AudioOutputUnitStart(st->au);
+}
+
+
+static uint32_t aufmt_to_formatflags(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger;
+ case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat;
+ default: return 0;
+ }
+}
+
+
+int audiounit_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ AudioStreamBasicDescription fmt;
+ AudioUnitElement outputBus = 0;
+ AURenderCallbackStruct cb;
+ struct auplay_st *st;
+ UInt32 enable = 1;
+ OSStatus ret = 0;
+ Float64 hw_srate = 0.0;
+ UInt32 hw_size = sizeof(hw_srate);
+ int err;
+
+ (void)device;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ err = audiosess_alloc(&st->sess, interrupt_handler, st);
+ if (err)
+ goto out;
+
+ ret = AudioComponentInstanceNew(output_comp, &st->au);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output, outputBus,
+ &enable, sizeof(enable));
+ if (ret) {
+ warning("audiounit: EnableIO failed (%d)\n", ret);
+ goto out;
+ }
+
+ st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt);
+
+ fmt.mSampleRate = prm->srate;
+ fmt.mFormatID = kAudioFormatLinearPCM;
+#if TARGET_OS_IPHONE
+ fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt)
+ | kAudioFormatFlagsNativeEndian
+ | kAudioFormatFlagIsPacked;
+#else
+ fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt)
+ | kAudioFormatFlagIsPacked;
+#endif
+ fmt.mBitsPerChannel = 8 * st->sampsz;
+ fmt.mChannelsPerFrame = prm->ch;
+ fmt.mBytesPerFrame = st->sampsz * prm->ch;
+ fmt.mFramesPerPacket = 1;
+ fmt.mBytesPerPacket = st->sampsz * prm->ch;
+
+ ret = AudioUnitInitialize(st->au);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitSetProperty(st->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, outputBus,
+ &fmt, sizeof(fmt));
+ if (ret)
+ goto out;
+
+ cb.inputProc = output_callback;
+ cb.inputProcRefCon = st;
+ ret = AudioUnitSetProperty(st->au,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, outputBus,
+ &cb, sizeof(cb));
+ if (ret)
+ goto out;
+
+ ret = AudioOutputUnitStart(st->au);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitGetProperty(st->au,
+ kAudioUnitProperty_SampleRate,
+ kAudioUnitScope_Output,
+ 0,
+ &hw_srate,
+ &hw_size);
+ if (ret)
+ goto out;
+
+ debug("audiounit: player hardware sample rate is now at %f Hz\n",
+ hw_srate);
+
+ out:
+ if (ret) {
+ warning("audiounit: player failed: %d (%c%c%c%c)\n", ret,
+ ret>>24, ret>>16, ret>>8, ret);
+ err = ENODEV;
+ }
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/audiounit/recorder.c b/modules/audiounit/recorder.c
new file mode 100644
index 0000000..b66ba07
--- /dev/null
+++ b/modules/audiounit/recorder.c
@@ -0,0 +1,263 @@
+/**
+ * @file audiounit/recorder.c AudioUnit input recorder
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <TargetConditionals.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "audiounit.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ struct audiosess_st *sess;
+ AudioUnit au;
+ pthread_mutex_t mutex;
+ int ch;
+ ausrc_read_h *rh;
+ void *arg;
+ uint32_t sampsz;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ pthread_mutex_lock(&st->mutex);
+ st->rh = NULL;
+ pthread_mutex_unlock(&st->mutex);
+
+ AudioOutputUnitStop(st->au);
+ AudioUnitUninitialize(st->au);
+ AudioComponentInstanceDispose(st->au);
+
+ mem_deref(st->sess);
+
+ pthread_mutex_destroy(&st->mutex);
+}
+
+
+static OSStatus input_callback(void *inRefCon,
+ AudioUnitRenderActionFlags *ioActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber,
+ UInt32 inNumberFrames,
+ AudioBufferList *ioData)
+{
+ struct ausrc_st *st = inRefCon;
+ AudioBufferList abl;
+ OSStatus ret;
+ ausrc_read_h *rh;
+ void *arg;
+
+ (void)ioData;
+
+ pthread_mutex_lock(&st->mutex);
+ rh = st->rh;
+ arg = st->arg;
+ pthread_mutex_unlock(&st->mutex);
+
+ if (!rh)
+ return 0;
+
+ abl.mNumberBuffers = 1;
+ abl.mBuffers[0].mNumberChannels = st->ch;
+ abl.mBuffers[0].mData = NULL;
+ abl.mBuffers[0].mDataByteSize = inNumberFrames * st->sampsz;
+
+ ret = AudioUnitRender(st->au,
+ ioActionFlags,
+ inTimeStamp,
+ inBusNumber,
+ inNumberFrames,
+ &abl);
+ if (ret) {
+ debug("audiounit: record: AudioUnitRender error (%d)\n", ret);
+ return ret;
+ }
+
+ rh(abl.mBuffers[0].mData,
+ abl.mBuffers[0].mDataByteSize/st->sampsz, arg);
+
+ return 0;
+}
+
+
+static void interrupt_handler(bool interrupted, void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (interrupted)
+ AudioOutputUnitStop(st->au);
+ else
+ AudioOutputUnitStart(st->au);
+}
+
+
+static uint32_t aufmt_to_formatflags(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return kLinearPCMFormatFlagIsSignedInteger;
+ case AUFMT_FLOAT: return kLinearPCMFormatFlagIsFloat;
+ default: return 0;
+ }
+}
+
+
+int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ AudioStreamBasicDescription fmt;
+ AudioUnitElement inputBus = 1;
+ AURenderCallbackStruct cb;
+ struct ausrc_st *st;
+ UInt32 enable = 1;
+#if ! TARGET_OS_IPHONE
+ UInt32 ausize = sizeof(AudioDeviceID);
+ AudioDeviceID inputDevice;
+ AudioObjectPropertyAddress auAddress = {
+ kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+#endif
+ Float64 hw_srate = 0.0;
+ UInt32 hw_size = sizeof(hw_srate);
+ OSStatus ret = 0;
+ int err;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+ st->ch = prm->ch;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ err = audiosess_alloc(&st->sess, interrupt_handler, st);
+ if (err)
+ goto out;
+
+ ret = AudioComponentInstanceNew(output_comp, &st->au);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input, inputBus,
+ &enable, sizeof(enable));
+ if (ret)
+ goto out;
+
+#if ! TARGET_OS_IPHONE
+ enable = 0;
+ ret = AudioUnitSetProperty(st->au, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output, 0,
+ &enable, sizeof(enable));
+ if (ret)
+ goto out;
+
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &auAddress,
+ 0,
+ NULL,
+ &ausize,
+ &inputDevice);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitSetProperty(st->au,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &inputDevice,
+ sizeof(inputDevice));
+ if (ret)
+ goto out;
+#endif
+
+ st->sampsz = (uint32_t)aufmt_sample_size(prm->fmt);
+
+ fmt.mSampleRate = prm->srate;
+ fmt.mFormatID = kAudioFormatLinearPCM;
+#if TARGET_OS_IPHONE
+ fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt)
+ | kAudioFormatFlagsNativeEndian
+ | kAudioFormatFlagIsPacked;
+#else
+ fmt.mFormatFlags = aufmt_to_formatflags(prm->fmt)
+ | kLinearPCMFormatFlagIsPacked;
+#endif
+ fmt.mBitsPerChannel = 8 * st->sampsz;
+ fmt.mChannelsPerFrame = prm->ch;
+ fmt.mBytesPerFrame = st->sampsz * prm->ch;
+ fmt.mFramesPerPacket = 1;
+ fmt.mBytesPerPacket = st->sampsz * prm->ch;
+ fmt.mReserved = 0;
+
+ ret = AudioUnitSetProperty(st->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, inputBus,
+ &fmt, sizeof(fmt));
+ if (ret)
+ goto out;
+
+ /* NOTE: done after desc */
+ ret = AudioUnitInitialize(st->au);
+ if (ret)
+ goto out;
+
+ cb.inputProc = input_callback;
+ cb.inputProcRefCon = st;
+ ret = AudioUnitSetProperty(st->au,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, inputBus,
+ &cb, sizeof(cb));
+ if (ret)
+ goto out;
+
+ ret = AudioOutputUnitStart(st->au);
+ if (ret)
+ goto out;
+
+ ret = AudioUnitGetProperty(st->au,
+ kAudioUnitProperty_SampleRate,
+ kAudioUnitScope_Input,
+ 0,
+ &hw_srate,
+ &hw_size);
+ if (ret)
+ goto out;
+
+ debug("audiounit: record hardware sample rate is now at %f Hz\n",
+ hw_srate);
+
+ out:
+ if (ret) {
+ warning("audiounit: record failed: %d (%c%c%c%c)\n", ret,
+ ret>>24, ret>>16, ret>>8, ret);
+ err = ENODEV;
+ }
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/audiounit/sess.c b/modules/audiounit/sess.c
new file mode 100644
index 0000000..abc966e
--- /dev/null
+++ b/modules/audiounit/sess.c
@@ -0,0 +1,174 @@
+/**
+ * @file sess.c AudioUnit sound driver - session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <re.h>
+#include <baresip.h>
+#include "audiounit.h"
+
+
+struct audiosess {
+ struct list sessl;
+};
+
+
+struct audiosess_st {
+ struct audiosess *as;
+ struct le le;
+ audiosess_int_h *inth;
+ void *arg;
+};
+
+
+static struct audiosess *gas;
+
+
+#if TARGET_OS_IPHONE
+static void propListener(void *inClientData, AudioSessionPropertyID inID,
+ UInt32 inDataSize, const void *inData)
+{
+ struct audiosess *sess = inClientData;
+ CFDictionaryRef dref = inData;
+ CFNumberRef nref;
+ SInt32 reason = 0;
+
+ (void)inDataSize;
+ (void)sess;
+
+ if (kAudioSessionProperty_AudioRouteChange != inID)
+ return;
+
+ nref = CFDictionaryGetValue(
+ dref,
+ CFSTR(kAudioSession_AudioRouteChangeKey_Reason)
+ );
+
+ CFNumberGetValue(nref, kCFNumberSInt32Type, &reason);
+
+ info("audiounit: AudioRouteChange - reason %d\n", reason);
+}
+#endif
+
+
+static void sess_destructor(void *arg)
+{
+ struct audiosess_st *st = arg;
+
+ list_unlink(&st->le);
+ mem_deref(st->as);
+}
+
+
+static void destructor(void *arg)
+{
+ struct audiosess *as = arg;
+#if TARGET_OS_IPHONE
+ AudioSessionPropertyID id = kAudioSessionProperty_AudioRouteChange;
+
+ AudioSessionRemovePropertyListenerWithUserData(id, propListener, as);
+ AudioSessionSetActive(false);
+#endif
+
+ list_flush(&as->sessl);
+
+ gas = NULL;
+}
+
+
+int audiosess_alloc(struct audiosess_st **stp,
+ audiosess_int_h *inth, void *arg)
+{
+ struct audiosess_st *st = NULL;
+ struct audiosess *as = NULL;
+ int err = 0;
+ bool created = false;
+#if TARGET_OS_IPHONE
+ AudioSessionPropertyID id = kAudioSessionProperty_AudioRouteChange;
+ UInt32 category;
+ OSStatus ret;
+#endif
+
+ if (!stp)
+ return EINVAL;
+
+#if TARGET_OS_IPHONE
+ /* Must be done for all modules */
+ category = kAudioSessionCategory_PlayAndRecord;
+ ret = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(category), &category);
+ if (ret) {
+ warning("audiounit: Audio Category: %d\n", ret);
+ return EINVAL;
+ }
+#endif
+
+ if (gas)
+ goto makesess;
+
+ as = mem_zalloc(sizeof(*as), destructor);
+ if (!as)
+ return ENOMEM;
+
+#if TARGET_OS_IPHONE
+ ret = AudioSessionSetActive(true);
+ if (ret) {
+ warning("audiounit: AudioSessionSetActive: %d\n", ret);
+ err = ENOSYS;
+ goto out;
+ }
+
+ ret = AudioSessionAddPropertyListener(id, propListener, as);
+ if (ret) {
+ warning("audiounit: AudioSessionAddPropertyListener: %d\n",
+ ret);
+ err = EINVAL;
+ goto out;
+ }
+#endif
+
+ gas = as;
+ created = true;
+
+ makesess:
+ st = mem_zalloc(sizeof(*st), sess_destructor);
+ if (!st) {
+ err = ENOMEM;
+ goto out;
+ }
+ st->inth = inth;
+ st->arg = arg;
+ st->as = created ? gas : mem_ref(gas);
+
+ list_append(&gas->sessl, &st->le, st);
+
+ out:
+ if (err) {
+ mem_deref(as);
+ mem_deref(st);
+ }
+ else {
+ *stp = st;
+ }
+
+ return err;
+}
+
+
+void audiosess_interrupt(bool start)
+{
+ struct le *le;
+
+ if (!gas)
+ return;
+
+ for (le = gas->sessl.head; le; le = le->next) {
+
+ struct audiosess_st *st = le->data;
+
+ if (st->inth)
+ st->inth(start, st->arg);
+ }
+}
diff --git a/modules/aufile/aufile.c b/modules/aufile/aufile.c
new file mode 100644
index 0000000..307aee1
--- /dev/null
+++ b/modules/aufile/aufile.c
@@ -0,0 +1,265 @@
+/**
+ * @file aufile.c WAV Audio Source
+ *
+ * Copyright (C) 2015 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup aufile aufile
+ *
+ * Audio module for using a WAV-file as audio input
+ */
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* base class */
+ struct tmr tmr;
+ struct aufile *aufile;
+ struct aubuf *aubuf;
+ uint32_t ptime;
+ size_t sampc;
+ bool run;
+ pthread_t thread;
+ ausrc_read_h *rh;
+ ausrc_error_h *errh;
+ void *arg;
+};
+
+
+static struct ausrc *ausrc;
+
+
+static void destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ tmr_cancel(&st->tmr);
+
+ mem_deref(st->aufile);
+ mem_deref(st->aubuf);
+}
+
+
+static void *play_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct ausrc_st *st = arg;
+ int16_t *sampv;
+
+ sampv = mem_alloc(st->sampc * 2, NULL);
+ if (!sampv)
+ return NULL;
+
+ while (st->run) {
+
+ sys_msleep(4);
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+
+ aubuf_read_samp(st->aubuf, sampv, st->sampc);
+
+ st->rh(sampv, st->sampc, st->arg);
+
+ ts += st->ptime;
+ }
+
+ mem_deref(sampv);
+
+ info("aufile: player thread exited\n");
+
+ return NULL;
+}
+
+
+static void timeout(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ tmr_start(&st->tmr, 1000, timeout, st);
+
+ /* check if audio buffer is empty */
+ if (aubuf_cur_size(st->aubuf) < (2 * st->sampc)) {
+
+ info("aufile: end of file\n");
+
+ /* error handler must be called from re_main thread */
+ if (st->errh)
+ st->errh(0, "end of file", st->arg);
+ }
+}
+
+
+static int read_file(struct ausrc_st *st)
+{
+ struct mbuf *mb;
+ int err;
+
+ for (;;) {
+ uint16_t *sampv;
+ size_t i;
+
+ mb = mbuf_alloc(4096);
+ if (!mb)
+ return ENOMEM;
+
+ mb->end = mb->size;
+
+ err = aufile_read(st->aufile, mb->buf, &mb->end);
+ if (err)
+ break;
+
+ if (mb->end == 0) {
+ info("aufile: end of file\n");
+ break;
+ }
+
+ /* convert from Little-Endian to Native-Endian */
+ sampv = (void *)mb->buf;
+ for (i=0; i<mb->end/2; i++) {
+ sampv[i] = sys_ltohs(sampv[i]);
+ }
+
+ aubuf_append(st->aubuf, mb);
+
+ mb = mem_deref(mb);
+ }
+
+ info("aufile: loaded %zu bytes\n", aubuf_cur_size(st->aubuf));
+
+ mem_deref(mb);
+ return err;
+}
+
+
+static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *dev,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ struct aufile_prm fprm;
+ int err;
+ (void)ctx;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("aufile: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ info("aufile: loading input file '%s'\n", dev);
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ err = aufile_open(&st->aufile, &fprm, dev, AUFILE_READ);
+ if (err) {
+ warning("aufile: failed to open file '%s' (%m)\n", dev, err);
+ goto out;
+ }
+
+ info("aufile: %s: %u Hz, %d channels\n",
+ dev, fprm.srate, fprm.channels);
+
+ if (fprm.srate != prm->srate) {
+ warning("aufile: input file (%s) must have sample-rate"
+ " %u Hz\n", dev, prm->srate);
+ err = ENODEV;
+ goto out;
+ }
+ if (fprm.channels != prm->ch) {
+ warning("aufile: input file (%s) must have channels = %d\n",
+ dev, prm->ch);
+ err = ENODEV;
+ goto out;
+ }
+ if (fprm.fmt != AUFMT_S16LE) {
+ warning("aufile: input file must have format S16LE\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->ptime = prm->ptime;
+
+ info("aufile: audio ptime=%u sampc=%zu aubuf=[%u:%u]\n",
+ st->ptime, st->sampc,
+ prm->srate * prm->ch * 2,
+ prm->srate * prm->ch * 40);
+
+ /* 1 - inf seconds of audio */
+ err = aubuf_alloc(&st->aubuf,
+ prm->srate * prm->ch * 2,
+ 0);
+ if (err)
+ goto out;
+
+ err = read_file(st);
+ if (err)
+ goto out;
+
+ tmr_start(&st->tmr, 1000, timeout, st);
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, play_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return ausrc_register(&ausrc, baresip_ausrcl(),
+ "aufile", alloc_handler);
+}
+
+
+static int module_close(void)
+{
+ ausrc = mem_deref(ausrc);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(aufile) = {
+ "aufile",
+ "ausrc",
+ module_init,
+ module_close
+};
diff --git a/modules/aufile/module.mk b/modules/aufile/module.mk
new file mode 100644
index 0000000..49ebdc3
--- /dev/null
+++ b/modules/aufile/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := aufile
+$(MOD)_SRCS += aufile.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/auloop/auloop.c b/modules/auloop/auloop.c
new file mode 100644
index 0000000..8324a88
--- /dev/null
+++ b/modules/auloop/auloop.c
@@ -0,0 +1,413 @@
+/**
+ * @file auloop.c Audio loop
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup auloop auloop
+ *
+ * Application module for testing audio drivers
+ *
+ * The audio loop will connect the AUSRC device to the AUPLAY device
+ * so that a local loopback audio can be heard. Different audio parameters
+ * can be tested, such as sampling rate and number of channels.
+ *
+ * The following commands are available:
+ \verbatim
+ /auloop Start audio-loop
+ /auloop_stop Stop audio-loop
+ \endverbatim
+ */
+
+
+/* Configurable items */
+#define PTIME 20
+
+
+/** Audio Loop */
+struct audio_loop {
+ uint32_t index;
+ struct aubuf *ab;
+ struct ausrc_st *ausrc;
+ struct auplay_st *auplay;
+ const struct aucodec *ac;
+ struct auenc_state *enc;
+ struct audec_state *dec;
+ int16_t *sampv;
+ size_t sampc;
+ struct tmr tmr;
+ uint32_t srate;
+ uint32_t ch;
+ enum aufmt fmt;
+
+ uint32_t n_read;
+ uint32_t n_write;
+};
+
+static const struct {
+ uint32_t srate;
+ uint32_t ch;
+} configv[] = {
+ { 8000, 1},
+ {16000, 1},
+ {32000, 1},
+ {44100, 1},
+ {48000, 1},
+ { 8000, 2},
+ {16000, 2},
+ {32000, 2},
+ {44100, 2},
+ {48000, 2},
+};
+
+static struct audio_loop *gal = NULL;
+static char aucodec[64];
+
+
+static void auloop_destructor(void *arg)
+{
+ struct audio_loop *al = arg;
+
+ tmr_cancel(&al->tmr);
+ mem_deref(al->ausrc);
+ mem_deref(al->auplay);
+ mem_deref(al->sampv);
+ mem_deref(al->ab);
+ mem_deref(al->enc);
+ mem_deref(al->dec);
+}
+
+
+static void print_stats(struct audio_loop *al)
+{
+ double rw_ratio = 0.0;
+
+ if (al->n_write)
+ rw_ratio = 1.0 * al->n_read / al->n_write;
+
+ (void)re_fprintf(stdout, "\r%uHz %dch %s "
+ " n_read=%u n_write=%u rw_ratio=%.2f",
+ al->srate, al->ch, aufmt_name(al->fmt),
+ al->n_read, al->n_write, rw_ratio);
+
+ if (str_isset(aucodec))
+ (void)re_fprintf(stdout, " codec='%s'", aucodec);
+
+ fflush(stdout);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct audio_loop *al = arg;
+
+ tmr_start(&al->tmr, 100, tmr_handler, al);
+ print_stats(al);
+}
+
+
+static int codec_read(struct audio_loop *al, int16_t *sampv, size_t sampc)
+{
+ uint8_t x[2560];
+ size_t xlen = sizeof(x);
+ int err;
+
+ aubuf_read_samp(al->ab, al->sampv, al->sampc);
+
+ err = al->ac->ench(al->enc, x, &xlen, al->sampv, al->sampc);
+ if (err)
+ goto out;
+
+ if (al->ac->dech) {
+ err = al->ac->dech(al->dec, sampv, &sampc, x, xlen);
+ if (err)
+ goto out;
+ }
+ else {
+ info("auloop: no decode handler\n");
+ }
+
+ out:
+
+ return err;
+}
+
+
+static void read_handler(const void *sampv, size_t sampc, void *arg)
+{
+ struct audio_loop *al = arg;
+ size_t num_bytes = sampc * aufmt_sample_size(al->fmt);
+ int err;
+
+ ++al->n_read;
+
+ err = aubuf_write(al->ab, sampv, num_bytes);
+ if (err) {
+ warning("auloop: aubuf_write: %m\n", err);
+ }
+}
+
+
+static void write_handler(void *sampv, size_t sampc, void *arg)
+{
+ struct audio_loop *al = arg;
+ size_t num_bytes = sampc * aufmt_sample_size(al->fmt);
+ int err;
+
+ ++al->n_write;
+
+ /* read from beginning */
+ if (al->ac) {
+ err = codec_read(al, sampv, sampc);
+ if (err) {
+ warning("auloop: codec_read error "
+ "on %zu samples (%m)\n", sampc, err);
+ }
+ }
+ else {
+ aubuf_read(al->ab, sampv, num_bytes);
+ }
+}
+
+
+static void error_handler(int err, const char *str, void *arg)
+{
+ (void)arg;
+ warning("auloop: ausrc error: %m (%s)\n", err, str);
+ gal = mem_deref(gal);
+}
+
+
+static void start_codec(struct audio_loop *al, const char *name)
+{
+ struct auenc_param prm = {PTIME};
+ int err;
+
+ al->ac = aucodec_find(baresip_aucodecl(), name,
+ configv[al->index].srate,
+ configv[al->index].ch);
+ if (!al->ac) {
+ warning("auloop: could not find codec: %s\n", name);
+ return;
+ }
+
+ if (al->ac->encupdh) {
+ err = al->ac->encupdh(&al->enc, al->ac, &prm, NULL);
+ if (err) {
+ warning("auloop: encoder update failed: %m\n", err);
+ }
+ }
+
+ if (al->ac->decupdh) {
+ err = al->ac->decupdh(&al->dec, al->ac, NULL);
+ if (err) {
+ warning("auloop: decoder update failed: %m\n", err);
+ }
+ }
+}
+
+
+static int auloop_reset(struct audio_loop *al)
+{
+ struct auplay_prm auplay_prm;
+ struct ausrc_prm ausrc_prm;
+ const struct config *cfg = conf_config();
+ int err;
+
+ if (!cfg)
+ return ENOENT;
+
+ if (cfg->audio.src_fmt != cfg->audio.play_fmt) {
+ warning("auloop: ausrc_format and auplay_format"
+ " must be the same\n");
+ return EINVAL;
+ }
+
+ al->fmt = cfg->audio.src_fmt;
+
+ /* Optional audio codec */
+ if (str_isset(aucodec)) {
+ if (cfg->audio.src_fmt != AUFMT_S16LE) {
+ warning("auloop: only s16 supported with codec\n");
+ return EINVAL;
+ }
+
+ start_codec(al, aucodec);
+ }
+
+ /* audio player/source must be stopped first */
+ al->auplay = mem_deref(al->auplay);
+ al->ausrc = mem_deref(al->ausrc);
+
+ al->sampv = mem_deref(al->sampv);
+ al->ab = mem_deref(al->ab);
+
+ al->srate = configv[al->index].srate;
+ al->ch = configv[al->index].ch;
+
+ if (str_isset(aucodec)) {
+ al->sampc = al->srate * al->ch * PTIME / 1000;
+ al->sampv = mem_alloc(al->sampc * 2, NULL);
+ if (!al->sampv)
+ return ENOMEM;
+ }
+
+ info("Audio-loop: %uHz, %dch\n", al->srate, al->ch);
+
+ err = aubuf_alloc(&al->ab, 320, 0);
+ if (err)
+ return err;
+
+ auplay_prm.srate = al->srate;
+ auplay_prm.ch = al->ch;
+ auplay_prm.ptime = PTIME;
+ auplay_prm.fmt = al->fmt;
+ err = auplay_alloc(&al->auplay, baresip_auplayl(),
+ cfg->audio.play_mod, &auplay_prm,
+ cfg->audio.play_dev, write_handler, al);
+ if (err) {
+ warning("auloop: auplay %s,%s failed: %m\n",
+ cfg->audio.play_mod, cfg->audio.play_dev,
+ err);
+ return err;
+ }
+
+ ausrc_prm.srate = al->srate;
+ ausrc_prm.ch = al->ch;
+ ausrc_prm.ptime = PTIME;
+ ausrc_prm.fmt = al->fmt;
+ err = ausrc_alloc(&al->ausrc, baresip_ausrcl(),
+ NULL, cfg->audio.src_mod,
+ &ausrc_prm, cfg->audio.src_dev,
+ read_handler, error_handler, al);
+ if (err) {
+ warning("auloop: ausrc %s,%s failed: %m\n", cfg->audio.src_mod,
+ cfg->audio.src_dev, err);
+ return err;
+ }
+
+ return err;
+}
+
+
+static int audio_loop_alloc(struct audio_loop **alp)
+{
+ struct audio_loop *al;
+ int err;
+
+ al = mem_zalloc(sizeof(*al), auloop_destructor);
+ if (!al)
+ return ENOMEM;
+
+ tmr_start(&al->tmr, 100, tmr_handler, al);
+
+ err = auloop_reset(al);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(al);
+ else
+ *alp = al;
+
+ return err;
+}
+
+
+static int audio_loop_cycle(struct audio_loop *al)
+{
+ int err;
+
+ ++al->index;
+
+ if (al->index >= ARRAY_SIZE(configv)) {
+ gal = mem_deref(gal);
+ info("\nAudio-loop stopped\n");
+ return 0;
+ }
+
+ err = auloop_reset(al);
+ if (err)
+ return err;
+
+ info("\nAudio-loop started: %uHz, %dch\n", al->srate, al->ch);
+
+ return 0;
+}
+
+
+/**
+ * Start the audio loop (for testing)
+ */
+static int auloop_start(struct re_printf *pf, void *arg)
+{
+ int err;
+
+ (void)pf;
+ (void)arg;
+
+ if (gal) {
+ err = audio_loop_cycle(gal);
+ if (err) {
+ warning("auloop: loop cycle: %m\n", err);
+ }
+ }
+ else {
+ err = audio_loop_alloc(&gal);
+ if (err) {
+ warning("auloop: alloc failed %m\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+static int auloop_stop(struct re_printf *pf, void *arg)
+{
+ (void)arg;
+
+ if (gal) {
+ (void)re_hprintf(pf, "audio-loop stopped\n");
+ gal = mem_deref(gal);
+ }
+
+ return 0;
+}
+
+
+static const struct cmd cmdv[] = {
+ {"auloop", 0, 0, "Start audio-loop", auloop_start },
+ {"auloop_stop", 0, 0, "Stop audio-loop", auloop_stop },
+};
+
+
+static int module_init(void)
+{
+ conf_get_str(conf_cur(), "auloop_codec", aucodec, sizeof(aucodec));
+
+ return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ auloop_stop(NULL, NULL);
+ cmd_unregister(baresip_commands(), cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(auloop) = {
+ "auloop",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/auloop/module.mk b/modules/auloop/module.mk
new file mode 100644
index 0000000..9da52d5
--- /dev/null
+++ b/modules/auloop/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := auloop
+$(MOD)_SRCS += auloop.c
+
+include mk/mod.mk
diff --git a/modules/av1/av1.c b/modules/av1/av1.c
new file mode 100644
index 0000000..e0590e4
--- /dev/null
+++ b/modules/av1/av1.c
@@ -0,0 +1,51 @@
+/**
+ * @file av1.c AV1 Video Codec
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "av1.h"
+
+
+/**
+ * @defgroup av1 av1
+ *
+ * The AV1 video codec (Experimental)
+ *
+ * Reference: http://aomedia.org/
+ */
+
+
+static struct vidcodec av1 = {
+ .name = "AV1",
+ .encupdh = av1_encode_update,
+ .ench = av1_encode,
+ .decupdh = av1_decode_update,
+ .dech = av1_decode,
+};
+
+
+static int module_init(void)
+{
+ vidcodec_register(baresip_vidcodecl(), &av1);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&av1);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(av1) = {
+ "av1",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/av1/av1.h b/modules/av1/av1.h
new file mode 100644
index 0000000..7d5a32d
--- /dev/null
+++ b/modules/av1/av1.h
@@ -0,0 +1,20 @@
+/**
+ * @file av1.h Private AV1 Interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+/* Encode */
+int av1_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int av1_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+
+/* Decode */
+int av1_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int av1_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb);
diff --git a/modules/av1/decode.c b/modules/av1/decode.c
new file mode 100644
index 0000000..21ccc75
--- /dev/null
+++ b/modules/av1/decode.c
@@ -0,0 +1,293 @@
+/**
+ * @file av1/decode.c AV1 Decode
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <aom/aom.h>
+#include <aom/aom_decoder.h>
+#include <aom/aomdx.h>
+#include "av1.h"
+
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+/* XXX: re-using VP9 header format for now.. */
+struct hdr {
+ unsigned x:1;
+ unsigned noref:1;
+ unsigned start:1;
+ unsigned partid:4;
+ /* extension fields */
+ unsigned i:1;
+ unsigned l:1;
+ unsigned t:1;
+ unsigned k:1;
+ uint16_t picid;
+ uint8_t tl0picidx;
+ unsigned tid:2;
+ unsigned y:1;
+ unsigned keyidx:5;
+};
+
+struct viddec_state {
+ aom_codec_ctx_t ctx;
+ struct mbuf *mb;
+ bool ctxup;
+ bool started;
+ uint16_t seq;
+};
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->ctxup)
+ aom_codec_destroy(&vds->ctx);
+
+ mem_deref(vds->mb);
+}
+
+
+int av1_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ aom_codec_err_t res;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ vds->mb = mbuf_alloc(1024);
+ if (!vds->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ res = aom_codec_dec_init(&vds->ctx, &aom_codec_av1_dx_algo, NULL, 0);
+ if (res) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->ctxup = true;
+
+ out:
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->x = v>>7 & 0x1;
+ hdr->noref = v>>5 & 0x1;
+ hdr->start = v>>4 & 0x1;
+ hdr->partid = v & 0x07;
+
+ if (hdr->x) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->i = v>>7 & 0x1;
+ hdr->l = v>>6 & 0x1;
+ hdr->t = v>>5 & 0x1;
+ hdr->k = v>>4 & 0x1;
+ }
+
+ if (hdr->i) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ if (v>>7 & 0x1) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->picid = (v & 0x7f)<<8;
+ hdr->picid += mbuf_read_u8(mb);
+ }
+ else {
+ hdr->picid = v & 0x7f;
+ }
+ }
+
+ if (hdr->l) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->tl0picidx = mbuf_read_u8(mb);
+ }
+
+ if (hdr->t || hdr->k) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->tid = v>>6 & 0x3;
+ hdr->y = v>>5 & 0x1;
+ hdr->keyidx = v & 0x1f;
+ }
+
+ return 0;
+}
+
+
+/* XXX: check keyframe flag */
+static inline bool is_keyframe(struct mbuf *mb)
+{
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ if (mb->buf[mb->pos] & 0x01)
+ return false;
+
+ return true;
+}
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+int av1_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ aom_codec_iter_t iter = NULL;
+ aom_codec_err_t res;
+ aom_image_t *img;
+ struct hdr hdr;
+ int err, i;
+
+ if (!vds || !frame || !intra || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ err = hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+#if 1
+ debug("av1: header: x=%u noref=%u start=%u partid=%u "
+ "i=%u l=%u t=%u k=%u "
+ "picid=%u tl0picidx=%u tid=%u y=%u keyidx=%u\n",
+ hdr.x, hdr.noref, hdr.start, hdr.partid,
+ hdr.i, hdr.l, hdr.t, hdr.k,
+ hdr.picid, hdr.tl0picidx, hdr.tid, hdr.y, hdr.keyidx);
+#endif
+
+ if (hdr.start && hdr.partid == 0) {
+
+ if (is_keyframe(mb))
+ *intra = true;
+
+ mbuf_rewind(vds->mb);
+ vds->started = true;
+ }
+ else {
+ if (!vds->started)
+ return 0;
+
+ if (seq_diff(vds->seq, seq) != 1) {
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+ return 0;
+ }
+ }
+
+ vds->seq = seq;
+
+ err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (vds->mb->end > DECODE_MAXSZ) {
+ warning("av1: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ res = aom_codec_decode(&vds->ctx, vds->mb->buf,
+ (unsigned int)vds->mb->end, NULL, 1);
+ if (res) {
+ debug("av1: decode error: %s\n", aom_codec_err_to_string(res));
+ err = EPROTO;
+ goto out;
+ }
+
+ img = aom_codec_get_frame(&vds->ctx, &iter);
+ if (!img) {
+ debug("av1: no picture\n");
+ goto out;
+ }
+
+ if (img->fmt != AOM_IMG_FMT_I420) {
+ warning("av1: bad pixel format (%i)\n", img->fmt);
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = img->planes[i];
+ frame->linesize[i] = img->stride[i];
+ }
+
+ frame->size.w = img->d_w;
+ frame->size.h = img->d_h;
+ frame->fmt = VID_FMT_YUV420P;
+
+ out:
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+
+ return err;
+}
diff --git a/modules/av1/encode.c b/modules/av1/encode.c
new file mode 100644
index 0000000..9e24bcc
--- /dev/null
+++ b/modules/av1/encode.c
@@ -0,0 +1,259 @@
+/**
+ * @file av1/encode.c AV1 Encode
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <aom/aom.h>
+#include <aom/aom_encoder.h>
+#include <aom/aomcx.h>
+#include "av1.h"
+
+
+enum {
+ HDR_SIZE = 4,
+};
+
+
+struct videnc_state {
+ aom_codec_ctx_t ctx;
+ struct vidsz size;
+ aom_codec_pts_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ bool ctxup;
+ uint16_t picid;
+ videnc_packet_h *pkth;
+ void *arg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *ves = arg;
+
+ if (ves->ctxup)
+ aom_codec_destroy(&ves->ctx);
+}
+
+
+int av1_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *ves;
+
+ if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1))
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ ves->picid = rand_u16();
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->ctxup && (ves->bitrate != prm->bitrate ||
+ ves->fps != prm->fps)) {
+
+ aom_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *ves, const struct vidsz *size)
+{
+ aom_codec_enc_cfg_t cfg;
+ aom_codec_err_t res;
+
+ res = aom_codec_enc_config_default(&aom_codec_av1_cx_algo, &cfg, 0);
+ if (res)
+ return EPROTO;
+
+ cfg.g_w = size->w;
+ cfg.g_h = size->h;
+ cfg.g_timebase.num = 1;
+ cfg.g_timebase.den = ves->fps;
+ cfg.g_error_resilient = AOM_ERROR_RESILIENT_DEFAULT;
+ cfg.g_pass = AOM_RC_ONE_PASS;
+ cfg.g_lag_in_frames = 0;
+ cfg.rc_end_usage = AOM_VBR;
+ cfg.rc_target_bitrate = ves->bitrate;
+ cfg.kf_mode = AOM_KF_AUTO;
+
+ if (ves->ctxup) {
+ debug("av1: re-opening encoder\n");
+ aom_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+
+ res = aom_codec_enc_init(&ves->ctx, &aom_codec_av1_cx_algo, &cfg,
+ 0);
+ if (res) {
+ warning("av1: enc init: %s\n", aom_codec_err_to_string(res));
+ return EPROTO;
+ }
+
+ ves->ctxup = true;
+
+ res = aom_codec_control(&ves->ctx, AOME_SET_CPUUSED, 8);
+ if (res) {
+ warning("av1: codec ctrl C: %s\n",
+ aom_codec_err_to_string(res));
+ }
+
+ return 0;
+}
+
+
+static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool noref, bool start,
+ uint8_t partid, uint16_t picid)
+{
+ hdr[0] = 1<<7 | noref<<5 | start<<4 | (partid & 0x7);
+ hdr[1] = 1<<7;
+ hdr[2] = 1<<7 | (picid>>8 & 0x7f);
+ hdr[3] = picid & 0xff;
+}
+
+
+static inline int packetize(bool marker, uint32_t rtp_ts,
+ const uint8_t *buf, size_t len,
+ size_t maxlen, bool noref, uint8_t partid,
+ uint16_t picid, videnc_packet_h *pkth, void *arg)
+{
+ uint8_t hdr[HDR_SIZE];
+ bool start = true;
+ int err = 0;
+
+ maxlen -= sizeof(hdr);
+
+ while (len > maxlen) {
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(false, rtp_ts, hdr, sizeof(hdr), buf, maxlen, arg);
+
+ buf += maxlen;
+ len -= maxlen;
+ start = false;
+ }
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(marker, rtp_ts, hdr, sizeof(hdr), buf, len, arg);
+
+ return err;
+}
+
+
+int av1_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame)
+{
+ aom_enc_frame_flags_t flags = 0;
+ aom_codec_iter_t iter = NULL;
+ aom_codec_err_t res;
+ aom_image_t *img;
+ aom_img_fmt_t img_fmt;
+ int err = 0, i;
+
+ if (!ves || !frame || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) {
+
+ err = open_encoder(ves, &frame->size);
+ if (err)
+ return err;
+
+ ves->size = frame->size;
+ }
+
+ if (update) {
+ /* debug("av1: picture update\n"); */
+ flags |= AOM_EFLAG_FORCE_KF;
+ }
+
+ img_fmt = AOM_IMG_FMT_I420;
+
+ img = aom_img_wrap(NULL, img_fmt, frame->size.w, frame->size.h,
+ 16, NULL);
+ if (!img) {
+ warning("vp9: encoder: could not allocate image\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ img->stride[i] = frame->linesize[i];
+ img->planes[i] = frame->data[i];
+ }
+
+ res = aom_codec_encode(&ves->ctx, img, ves->pts++, 1,
+ flags, AOM_DL_REALTIME);
+ if (res) {
+ warning("av1: enc error: %s\n", aom_codec_err_to_string(res));
+ return ENOMEM;
+ }
+
+ ++ves->picid;
+
+ for (;;) {
+ bool keyframe = false, marker = true;
+ const aom_codec_cx_pkt_t *pkt;
+ uint8_t partid = 0;
+ uint32_t ts;
+
+ pkt = aom_codec_get_cx_data(&ves->ctx, &iter);
+ if (!pkt)
+ break;
+
+ if (pkt->kind != AOM_CODEC_CX_FRAME_PKT)
+ continue;
+
+ if (pkt->data.frame.flags & AOM_FRAME_IS_KEY)
+ keyframe = true;
+
+ if (pkt->data.frame.flags & AOM_FRAME_IS_FRAGMENT)
+ marker = false;
+
+ if (pkt->data.frame.partition_id >= 0)
+ partid = pkt->data.frame.partition_id;
+
+ ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps);
+
+ err = packetize(marker, ts,
+ pkt->data.frame.buf,
+ pkt->data.frame.sz,
+ ves->pktsize, !keyframe, partid, ves->picid,
+ ves->pkth, ves->arg);
+ if (err)
+ return err;
+ }
+
+ out:
+ if (img)
+ aom_img_free(img);
+
+ return err;
+}
diff --git a/modules/av1/module.mk b/modules/av1/module.mk
new file mode 100644
index 0000000..146583f
--- /dev/null
+++ b/modules/av1/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2016 Creytiv.com
+#
+
+MOD := av1
+$(MOD)_SRCS += av1.c
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_LFLAGS += -laom
+
+include mk/mod.mk
diff --git a/modules/avahi/avahi.c b/modules/avahi/avahi.c
new file mode 100644
index 0000000..035d51a
--- /dev/null
+++ b/modules/avahi/avahi.c
@@ -0,0 +1,389 @@
+/**
+ * @file avahi.c Avahi Zeroconf Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2017 Jonathan Sieber
+ */
+
+/**
+ * @defgroup avahi avahi
+ *
+ * This module implements DNS Service Discovery via Avahi Client API
+ * It does 2 things:
+ * 1) Announce _sipuri._udp resource for the main UA (under the local IP)
+ * 2) Fills contact list with discovered hosts
+ *
+ * NOTE: This module is experimental.
+ *
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <avahi-common/simple-watch.h>
+#include <avahi-client/lookup.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/error.h>
+
+/* for if_nametoindex */
+#include <net/if.h>
+
+/* gethostname, getaddrinfo */
+#define __USE_XOPEN2K
+#include <unistd.h>
+#include <netdb.h>
+
+
+struct avahi_st {
+ AvahiSimplePoll* poll;
+ AvahiClient* client;
+ AvahiEntryGroup* group;
+ AvahiServiceBrowser* browser;
+ struct ua* local_ua;
+ struct tmr poll_timer;
+};
+
+static struct avahi_st* avahi = NULL;
+
+
+static void group_callback(AvahiEntryGroup* group,
+ AvahiEntryGroupState state, void* userdata)
+{
+ (void)group;
+ (void)userdata;
+
+ switch (state) {
+
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ info ("avahi: Service Registration completed\n");
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ warning("avahi: Service Registration failed\n");
+ /* TODO: Think of smart way to handle collision? */
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ /* Do nothing */
+ break;
+ }
+}
+
+
+static void create_services(AvahiClient *client)
+{
+ int err;
+ char buf[128] = "";
+ char hostname[128] = "";
+
+ int if_idx = AVAHI_IF_UNSPEC;
+ int af = AVAHI_PROTO_INET;
+
+ struct sa laddr;
+
+ /* Build announced sipuri as username@hostname */
+ strncpy(hostname, avahi_client_get_host_name_fqdn(client),
+ sizeof (hostname));
+ re_snprintf(buf, sizeof(buf), "<sip:%s@%s>;regint=0",
+ sys_username(),
+ hostname);
+
+ info("avahi: Creating local UA %s\n", buf);
+ err = ua_alloc(&avahi->local_ua, buf);
+
+ if (err) {
+ warning("avahi: Could not create UA %s: %m\n", buf, err);
+ return;
+ }
+
+ re_snprintf(buf, sizeof(buf), "sip:%s@%s",
+ sys_username(),
+ hostname);
+
+ debug("avahi: Announcing URI: %s\n", buf);
+
+ /* Get interface number of baresip interface */
+ if (str_isset(conf_config()->net.ifname)) {
+ if_idx = if_nametoindex(conf_config()->net.ifname);
+ }
+
+ if (net_af(baresip_network()) == AF_INET6) {
+ af = AVAHI_PROTO_INET6;
+ }
+
+ err |= sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, 0);
+ if (err) {
+ warning("avahi: Can not find local SIP address\n");
+ }
+
+ /* TODO: Query enabled transports and register these */
+ avahi->group = avahi_entry_group_new(client, group_callback, NULL);
+ err = avahi_entry_group_add_service(avahi->group,
+ if_idx, af, 0,
+ buf, "_sipuri._udp",
+ NULL, NULL,
+ ntohs(laddr.u.in.sin_port), NULL);
+ err |= avahi_entry_group_commit(avahi->group);
+
+ if (err) {
+ warning("avahi: Error in registering service");
+ }
+}
+
+
+static void client_callback(AvahiClient *c, AvahiClientState state,
+ AVAHI_GCC_UNUSED void * userdata)
+{
+ (void)c;
+
+ switch (state) {
+
+ case AVAHI_CLIENT_S_RUNNING:
+ info("avahi: Avahi Daemon running\n", state);
+ break;
+ default:
+ warning("avahi: unknown client_callback: %d\n", state);
+ break;
+ }
+}
+
+
+static void add_contact(const char* uri,
+ const AvahiAddress *address, uint16_t port)
+{
+ int err;
+ struct pl addr;
+ char buf[128];
+ struct contact *c;
+ struct sa sa;
+ struct sip_addr sipaddr;
+
+ /* Parse SIPURI to get username and stuff... */
+ pl_set_str(&addr, uri);
+ if (sip_addr_decode(&sipaddr, &addr)) {
+ warning("avahi: could not decode sipuri %s\n", uri);
+ return;
+ }
+
+ if (address->proto == AVAHI_PROTO_INET6) {;
+ sa_set_in6(&sa, address->data.ipv6.address, port);
+ }
+ else {
+ sa_set_in(&sa, htonl(address->data.ipv4.address), port);
+ }
+
+ re_snprintf(buf, sizeof(buf),
+ "\"%r@%r\" <sip:%r@%J>;presence=p2p",
+ &sipaddr.uri.user, &sipaddr.uri.host,
+ &sipaddr.uri.user, &sa);
+ pl_set_str(&addr, buf);
+
+ err = contact_add(baresip_contacts(), &c, &addr);
+ if (err) {
+ warning("Could not add contact %s: %m\n", buf, err);
+ }
+}
+
+
+static void remove_contact_by_dname(const char* dname)
+{
+ const struct list *lst;
+ struct le *le;
+
+ /* remove sip: scheme for comparison */
+ if (0 != re_regex(dname, str_len(dname), "^sip:")) {
+ dname += 4;
+ }
+
+ lst = contact_list(baresip_contacts());
+
+ for (le = list_head(lst); le; le = le->next) {
+ struct contact *c = le->data;
+ const struct sip_addr* addr = contact_addr(c);
+
+ if (pl_strcmp(&addr->dname, dname) == 0) {
+ contact_remove(baresip_contacts(), c);
+ return;
+ }
+ }
+
+ warning("avahi: Could not remove contact %s\n", dname);
+}
+
+
+static void resolve_callback(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *hostname,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *userdata)
+{
+ (void)r;
+ (void)interface;
+ (void)txt;
+ (void)userdata;
+
+ info("avahi: resolve %s %s %s %s\n", name, type, domain, hostname);
+
+ if (event == AVAHI_RESOLVER_FOUND) {
+ if (protocol != address->proto) {
+ warning("avahi: Resolved address type ambiguous\n");
+ }
+
+ /* TODO: Process TXT field */
+ if (!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN)) {
+ add_contact(name, address, port);
+ }
+ }
+ else {
+ warning("avahi: Resolver Error on %s: %s\n", name,
+ avahi_strerror(avahi_client_errno(avahi->client)));
+ }
+
+ avahi_service_resolver_free(r);
+}
+
+
+static void browse_callback(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ void* userdata)
+{
+ int proto = AVAHI_PROTO_INET;
+ (void)b;
+ (void)userdata;
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+ debug("avahi: browse_callback if=%d proto=%d %s\n",
+ interface, protocol, name);
+ if (net_af(baresip_network()) == AF_INET6) {
+ proto = AVAHI_PROTO_INET6;
+ }
+
+ if (!(avahi_service_resolver_new(avahi->client,
+ interface, protocol,
+ name, type, domain,
+ proto, 0, resolve_callback,
+ avahi->client))) {
+ warning("avahi: Error resolving %s\n", name);
+ }
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ remove_contact_by_dname(name);
+ break;
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ debug("avahi: (Browser) %s\n",
+ event == AVAHI_BROWSER_CACHE_EXHAUSTED ?
+ "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ break;
+ default:
+ warning("avahi: browse_callback %d %s\n", event, name);
+ break;
+ }
+}
+
+
+static void avahi_update(void* arg)
+{
+ (void)arg;
+
+ avahi_simple_poll_iterate(avahi->poll, 0);
+ tmr_start(&avahi->poll_timer, 250, avahi_update, 0);
+}
+
+
+static void destructor(void* arg)
+{
+ struct avahi_st* a = arg;
+
+ tmr_cancel(&a->poll_timer);
+
+ mem_deref(a->local_ua);
+
+ /* Calling these destructor commands would be correct, but they
+ * spew out a lot of ugly D-Bus warning */
+ if (a->browser) {
+ avahi_service_browser_free(avahi->browser);
+ }
+
+ if (a->group) {
+ avahi_entry_group_free(avahi->group);
+ }
+
+ if (a->client) {
+ avahi_client_free(avahi->client);
+ }
+}
+
+
+static int module_init(void)
+{
+ int err;
+ avahi = mem_zalloc(sizeof(struct avahi_st), destructor);
+ if (!avahi) {
+ return ENOMEM;
+ }
+
+ avahi->poll = avahi_simple_poll_new();
+ avahi->client = avahi_client_new(
+ avahi_simple_poll_get(avahi->poll),
+ 0, client_callback, NULL, &err);
+
+ /* Check wether creating the client object succeeded */
+ if (!avahi->client) {
+ warning("Failed to create client: %s\n", avahi_strerror(err));
+ return err;
+ }
+
+ avahi->browser = avahi_service_browser_new(avahi->client,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_sipuri._udp", NULL,
+ 0, browse_callback, 0);
+
+ tmr_init(&avahi->poll_timer);
+ avahi_update(0);
+
+ /* Register services when UA is ready */
+ if (!avahi->group) {
+ create_services(avahi->client);
+ }
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ debug("avahi: module_close\n");
+
+ avahi = mem_deref(avahi);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(avahi) = {
+ "avahi",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/avahi/module.mk b/modules/avahi/module.mk
new file mode 100644
index 0000000..0d796e1
--- /dev/null
+++ b/modules/avahi/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := avahi
+$(MOD)_SRCS += avahi.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs avahi-client)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags avahi-client)
+
+include mk/mod.mk
diff --git a/modules/avcapture/avcapture.m b/modules/avcapture/avcapture.m
new file mode 100644
index 0000000..f276c96
--- /dev/null
+++ b/modules/avcapture/avcapture.m
@@ -0,0 +1,398 @@
+/**
+ * @file avcapture.m AVFoundation video capture
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <AVFoundation/AVFoundation.h>
+
+
+/**
+ * @defgroup avcapture avcapture
+ *
+ * Video source using OSX/iOS AVFoundation
+ */
+
+
+static struct vidsrc *vidsrc;
+
+
+@interface avcap : NSObject < AVCaptureVideoDataOutputSampleBufferDelegate >
+{
+ AVCaptureSession *sess;
+ AVCaptureDeviceInput *input;
+ AVCaptureVideoDataOutput *output;
+ struct vidsrc_st *vsrc;
+}
+- (void)setCamera:(const char *)name;
+@end
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs;
+ avcap *cap;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static void vidframe_set_pixbuf(struct vidframe *f, const CVImageBufferRef b)
+{
+ OSType type;
+ int i;
+
+ if (!f || !b)
+ return;
+
+ type = CVPixelBufferGetPixelFormatType(b);
+
+ switch (type) {
+
+ case kCVPixelFormatType_32BGRA:
+ f->fmt = VID_FMT_ARGB;
+ break;
+
+ case kCVPixelFormatType_422YpCbCr8:
+ f->fmt = VID_FMT_UYVY422;
+ break;
+
+ case kCVPixelFormatType_420YpCbCr8Planar:
+ f->fmt = VID_FMT_YUV420P;
+ break;
+
+ case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
+ f->fmt = VID_FMT_NV12;
+ break;
+
+ default:
+ warning("avcapture: unknown pixfmt %c%c%c%c\n",
+ type>>24, type>>16, type>>8, type>>0);
+ f->fmt = -1;
+ f->data[0] = NULL;
+ return;
+ }
+
+ f->size.w = (int)CVPixelBufferGetWidth(b);
+ f->size.h = (int)CVPixelBufferGetHeight(b);
+
+ if (!CVPixelBufferIsPlanar(b)) {
+
+ f->data[0] = CVPixelBufferGetBaseAddress(b);
+ f->linesize[0] = (int)CVPixelBufferGetBytesPerRow(b);
+ f->data[1] = f->data[2] = f->data[3] = NULL;
+ f->linesize[1] = f->linesize[2] = f->linesize[3] = 0;
+
+ return;
+ }
+
+ for (i=0; i<4; i++) {
+ f->data[i] = CVPixelBufferGetBaseAddressOfPlane(b, i);
+ f->linesize[i] = CVPixelBufferGetBytesPerRowOfPlane(b, i);
+ }
+}
+
+
+@implementation avcap
+
+
+- (NSString *)map_preset:(AVCaptureDevice *)dev sz:(const struct vidsz *)sz
+{
+ static const struct {
+ struct vidsz sz;
+ NSString * const * preset;
+ } mapv[] = {
+ {{ 192, 144}, &AVCaptureSessionPresetLow },
+ {{ 480, 360}, &AVCaptureSessionPresetMedium },
+ {{ 640, 480}, &AVCaptureSessionPresetHigh },
+ {{1280, 720}, &AVCaptureSessionPreset1280x720}
+ };
+ int i, best = -1;
+
+ for (i=ARRAY_SIZE(mapv)-1; i>=0; i--) {
+
+ NSString *preset = *mapv[i].preset;
+
+ if (![sess canSetSessionPreset:preset] ||
+ ![dev supportsAVCaptureSessionPreset:preset])
+ continue;
+
+ if (mapv[i].sz.w >= sz->w && mapv[i].sz.h >= sz->h)
+ best = i;
+ else
+ break;
+ }
+
+ if (best >= 0)
+ return *mapv[best].preset;
+ else {
+ NSLog(@"no suitable preset found for %d x %d", sz->w, sz->h);
+ return AVCaptureSessionPresetHigh;
+ }
+}
+
+
++ (AVCaptureDevicePosition)get_position:(const char *)name
+{
+ if (0 == str_casecmp(name, "back"))
+ return AVCaptureDevicePositionBack;
+ else if (0 == str_casecmp(name, "front"))
+ return AVCaptureDevicePositionFront;
+ else
+ return -1;
+}
+
+
++ (AVCaptureDevice *)get_device:(AVCaptureDevicePosition)pos
+{
+ AVCaptureDevice *dev;
+
+ for (dev in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
+ if (dev.position == pos)
+ return dev;
+ }
+
+ return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+}
+
+
+- (void)start:(id)unused
+{
+ (void)unused;
+
+ [sess startRunning];
+}
+
+
+- (id)init:(struct vidsrc_st *)st
+ dev:(const char *)name
+ size:(const struct vidsz *)sz
+{
+ dispatch_queue_t queue;
+ AVCaptureDevice *dev;
+
+ self = [super init];
+ if (!self)
+ return nil;
+
+ vsrc = st;
+
+ dev = [avcap get_device:[avcap get_position:name]];
+ if (!dev)
+ return nil;
+
+ input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:nil];
+ output = [[AVCaptureVideoDataOutput alloc] init];
+ sess = [[AVCaptureSession alloc] init];
+ if (!input || !output || !sess)
+ return nil;
+
+ output.alwaysDiscardsLateVideoFrames = YES;
+
+ queue = dispatch_queue_create("avcapture", NULL);
+ [output setSampleBufferDelegate:self queue:queue];
+ dispatch_release(queue);
+
+ sess.sessionPreset = [self map_preset:dev sz:sz];
+
+ [sess addInput:input];
+ [sess addOutput:output];
+
+ [self start:nil];
+
+ return self;
+}
+
+
+- (void)stop:(id)unused
+{
+ (void)unused;
+
+ [sess stopRunning];
+
+ if (output) {
+ AVCaptureConnection *conn;
+
+ for (conn in output.connections)
+ conn.enabled = NO;
+ }
+
+ [sess beginConfiguration];
+ if (input)
+ [sess removeInput:input];
+ if (output)
+ [sess removeOutput:output];
+ [sess commitConfiguration];
+
+ [sess release];
+}
+
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput
+didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
+ fromConnection:(AVCaptureConnection *)conn
+{
+ const CVImageBufferRef b = CMSampleBufferGetImageBuffer(sampleBuffer);
+ struct vidframe vf;
+
+ (void)captureOutput;
+ (void)conn;
+
+ if (!vsrc->frameh)
+ return;
+
+ CVPixelBufferLockBaseAddress(b, 0);
+
+ vidframe_set_pixbuf(&vf, b);
+
+ if (vidframe_isvalid(&vf))
+ vsrc->frameh(&vf, vsrc->arg);
+
+ CVPixelBufferUnlockBaseAddress(b, 0);
+}
+
+
+- (void)setCamera:(const char *)name
+{
+ AVCaptureDevicePosition pos;
+ AVCaptureDevice *dev;
+
+ pos = [avcap get_position:name];
+
+ if (pos == input.device.position)
+ return;
+
+ dev = [avcap get_device:pos];
+ if (!dev)
+ return;
+
+ [sess beginConfiguration];
+ [sess removeInput:input];
+ input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:nil];
+ [sess addInput:input];
+ [sess commitConfiguration];
+}
+
+
+@end
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ st->frameh = NULL;
+
+ [st->cap performSelectorOnMainThread:@selector(stop:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ [st->cap release];
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ NSAutoreleasePool *pool;
+ struct vidsrc_st *st;
+ int err = 0;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !size)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ pool = [NSAutoreleasePool new];
+
+ st->vs = vs;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ st->cap = [[avcap alloc] init:st
+ dev:dev ? dev : "front"
+ size:size];
+ if (!st->cap) {
+ err = ENODEV;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ [pool release];
+
+ return err;
+}
+
+
+static void update(struct vidsrc_st *st, struct vidsrc_prm *prm,
+ const char *dev)
+{
+ (void)prm;
+
+ if (!st)
+ return;
+
+ if (dev)
+ [st->cap setCamera:dev];
+}
+
+
+static int module_init(void)
+{
+ AVCaptureDevice *dev = nil;
+ NSAutoreleasePool *pool;
+ Class cls = NSClassFromString(@"AVCaptureDevice");
+ int err = 0;
+ if (!cls)
+ return ENOSYS;
+
+ pool = [NSAutoreleasePool new];
+
+ /* populate devices */
+ for (dev in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
+
+ const char *name = [[dev localizedName] UTF8String];
+
+ debug("avcapture: found video device '%s'\n", name);
+ }
+
+ err = vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "avcapture", alloc, update);
+
+ [pool drain];
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(avcapture) = {
+ "avcapture",
+ "vidsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/avcapture/module.mk b/modules/avcapture/module.mk
new file mode 100644
index 0000000..d5d688e
--- /dev/null
+++ b/modules/avcapture/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := avcapture
+$(MOD)_SRCS += avcapture.m
+$(MOD)_LFLAGS += -framework AVFoundation
+
+include mk/mod.mk
diff --git a/modules/avcodec/avcodec.c b/modules/avcodec/avcodec.c
new file mode 100644
index 0000000..bfcde82
--- /dev/null
+++ b/modules/avcodec/avcodec.c
@@ -0,0 +1,258 @@
+/**
+ * @file avcodec.c Video codecs using libavcodec
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libavcodec/avcodec.h>
+#ifdef USE_X264
+#include <x264.h>
+#endif
+#include "h26x.h"
+#include "avcodec.h"
+
+
+/**
+ * @defgroup avcodec avcodec
+ *
+ * Video codecs using libavcodec
+ *
+ * This module implements H.263, H.264 and MPEG4 video codecs
+ * using libavcodec from FFmpeg or libav projects, and libx264.
+ *
+ * Build options:
+ *
+ \verbatim
+ $ make USE_X264=1 ; enable direct usage of libx264
+ $ make USE_X264= ; use H.264 encoder from libavcodec
+ \endverbatim
+ *
+ * Config options:
+ *
+ \verbatim
+ avcodec_h264enc <NAME> ; e.g. h264_nvenc, h264_videotoolbox
+ avcodec_h264dec <NAME> ; e.g. h264_cuvid, h264_vda, h264_qsv
+ \endverbatim
+ *
+ * References:
+ *
+ * http://ffmpeg.org
+ *
+ * https://libav.org
+ *
+ * RTP Payload Format for H.264 Video
+ * https://tools.ietf.org/html/rfc6184
+ */
+
+
+const uint8_t h264_level_idc = 0x1f;
+AVCodec *avcodec_h264enc; /* optional; specified H.264 encoder */
+AVCodec *avcodec_h264dec; /* optional; specified H.264 decoder */
+
+
+int avcodec_resolve_codecid(const char *s)
+{
+ if (0 == str_casecmp(s, "H263"))
+ return AV_CODEC_ID_H263;
+ else if (0 == str_casecmp(s, "H264"))
+ return AV_CODEC_ID_H264;
+ else if (0 == str_casecmp(s, "MP4V-ES"))
+ return AV_CODEC_ID_MPEG4;
+ else
+ return AV_CODEC_ID_NONE;
+}
+
+
+static uint32_t packetization_mode(const char *fmtp)
+{
+ struct pl pl, mode;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "packetization-mode", &mode))
+ return pl_u32(&mode);
+
+ return 0;
+}
+
+
+static int h264_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ struct vidcodec *vc = arg;
+ const uint8_t profile_idc = 0x42; /* baseline profile */
+ const uint8_t profile_iop = 0x80;
+ (void)offer;
+
+ if (!mb || !fmt || !vc)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s"
+ " packetization-mode=0"
+ ";profile-level-id=%02x%02x%02x"
+ "\r\n",
+ fmt->id, profile_idc, profile_iop, h264_level_idc);
+}
+
+
+static bool h264_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data)
+{
+ (void)data;
+
+ return packetization_mode(fmtp1) == packetization_mode(fmtp2);
+}
+
+
+static int h263_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ (void)offer;
+ (void)arg;
+
+ if (!mb || !fmt)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s CIF=1;CIF4=1\r\n", fmt->id);
+}
+
+
+static int mpg4_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ (void)offer;
+ (void)arg;
+
+ if (!mb || !fmt)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s profile-level-id=3\r\n", fmt->id);
+}
+
+
+static struct vidcodec h264 = {
+ LE_INIT,
+ NULL,
+ "H264",
+ "packetization-mode=0",
+ NULL,
+ encode_update,
+#ifdef USE_X264
+ encode_x264,
+#else
+ encode,
+#endif
+ decode_update,
+ decode_h264,
+ h264_fmtp_enc,
+ h264_fmtp_cmp,
+};
+
+static struct vidcodec h263 = {
+ LE_INIT,
+ "34",
+ "H263",
+ NULL,
+ NULL,
+ encode_update,
+ encode,
+ decode_update,
+ decode_h263,
+ h263_fmtp_enc,
+ NULL,
+};
+
+static struct vidcodec mpg4 = {
+ LE_INIT,
+ NULL,
+ "MP4V-ES",
+ NULL,
+ NULL,
+ encode_update,
+ encode,
+ decode_update,
+ decode_mpeg4,
+ mpg4_fmtp_enc,
+ NULL,
+};
+
+
+static int module_init(void)
+{
+ struct list *vidcodecl = baresip_vidcodecl();
+ char h264enc[64];
+ char h264dec[64];
+
+#ifdef USE_X264
+ debug("avcodec: x264 build %d\n", X264_BUILD);
+#else
+ debug("avcodec: using libavcodec H.264 encoder\n");
+#endif
+
+#if LIBAVCODEC_VERSION_INT < ((53<<16)+(10<<8)+0)
+ avcodec_init();
+#endif
+
+ avcodec_register_all();
+
+ if (0 == conf_get_str(conf_cur(), "avcodec_h264dec",
+ h264dec, sizeof(h264dec))) {
+
+ info("avcodec: using h264 decoder by name (%s)\n", h264dec);
+
+ avcodec_h264dec = avcodec_find_decoder_by_name(h264dec);
+ if (!avcodec_h264dec) {
+ warning("avcodec: h264 decoder not found (%s)\n",
+ h264dec);
+ return ENOENT;
+ }
+ vidcodec_register(vidcodecl, &h264);
+ }
+ else {
+ if (avcodec_find_decoder(AV_CODEC_ID_H264))
+ vidcodec_register(vidcodecl, &h264);
+ }
+
+ if (avcodec_find_decoder(AV_CODEC_ID_H263))
+ vidcodec_register(vidcodecl, &h263);
+
+ if (avcodec_find_decoder(AV_CODEC_ID_MPEG4))
+ vidcodec_register(vidcodecl, &mpg4);
+
+ if (0 == conf_get_str(conf_cur(), "avcodec_h264enc",
+ h264enc, sizeof(h264enc))) {
+
+ info("avcodec: using h264 encoder by name (%s)\n", h264enc);
+
+ avcodec_h264enc = avcodec_find_encoder_by_name(h264enc);
+ if (!avcodec_h264enc) {
+ warning("avcodec: h264 encoder not found (%s)\n",
+ h264enc);
+ return ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&mpg4);
+ vidcodec_unregister(&h263);
+ vidcodec_unregister(&h264);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(avcodec) = {
+ "avcodec",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/avcodec/avcodec.h b/modules/avcodec/avcodec.h
new file mode 100644
index 0000000..f3a2b70
--- /dev/null
+++ b/modules/avcodec/avcodec.h
@@ -0,0 +1,60 @@
+/**
+ * @file avcodec.h Video codecs using libavcodec -- internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#if LIBAVCODEC_VERSION_INT < ((54<<16)+(25<<8)+0)
+#define AVCodecID CodecID
+
+#define AV_CODEC_ID_NONE CODEC_ID_NONE
+#define AV_CODEC_ID_H263 CODEC_ID_H263
+#define AV_CODEC_ID_H264 CODEC_ID_H264
+#define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4
+
+#endif
+
+
+extern const uint8_t h264_level_idc;
+extern AVCodec *avcodec_h264enc;
+extern AVCodec *avcodec_h264dec;
+
+
+/*
+ * Encode
+ */
+
+struct videnc_state;
+
+int encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int encode(struct videnc_state *st, bool update, const struct vidframe *frame);
+#ifdef USE_X264
+int encode_x264(struct videnc_state *st, bool update,
+ const struct vidframe *frame);
+#endif
+
+
+/*
+ * Decode
+ */
+
+struct viddec_state;
+
+int decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int decode_h263(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool eof, uint16_t seq, struct mbuf *src);
+int decode_h264(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool eof, uint16_t seq, struct mbuf *src);
+int decode_mpeg4(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool eof, uint16_t seq, struct mbuf *src);
+
+
+int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name,
+ const struct pl *val);
+
+
+int avcodec_resolve_codecid(const char *s);
diff --git a/modules/avcodec/decode.c b/modules/avcodec/decode.c
new file mode 100644
index 0000000..77e6e77
--- /dev/null
+++ b/modules/avcodec/decode.c
@@ -0,0 +1,556 @@
+/**
+ * @file avcodec/decode.c Video codecs using libavcodec -- decoder
+ *
+ * Copyright (C) 2010 - 2013 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libavutil/mem.h>
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0)
+#include <libavutil/pixdesc.h>
+#endif
+#include "h26x.h"
+#include "avcodec.h"
+
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
+#define AV_PIX_FMT_NV12 PIX_FMT_NV12
+#endif
+
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+struct viddec_state {
+ AVCodec *codec;
+ AVCodecContext *ctx;
+ AVFrame *pict;
+ struct mbuf *mb;
+ bool got_keyframe;
+ size_t frag_start;
+ bool frag;
+ uint16_t frag_seq;
+
+ struct {
+ unsigned n_key;
+ unsigned n_lost;
+ } stats;
+};
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+static inline void fragment_rewind(struct viddec_state *vds)
+{
+ vds->mb->pos = vds->frag_start;
+ vds->mb->end = vds->frag_start;
+}
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *st = arg;
+
+ debug("avcodec: decoder stats"
+ " (keyframes:%u, lost_fragments:%u)\n",
+ st->stats.n_key, st->stats.n_lost);
+
+ mem_deref(st->mb);
+
+ if (st->ctx) {
+ if (st->ctx->codec)
+ avcodec_close(st->ctx);
+ av_free(st->ctx);
+ }
+
+ if (st->pict)
+ av_free(st->pict);
+}
+
+
+static int init_decoder(struct viddec_state *st, const char *name)
+{
+ enum AVCodecID codec_id;
+
+ codec_id = avcodec_resolve_codecid(name);
+ if (codec_id == AV_CODEC_ID_NONE)
+ return EINVAL;
+
+ /*
+ * Special handling of H.264 decoder
+ */
+ if (codec_id == AV_CODEC_ID_H264 && avcodec_h264dec) {
+ st->codec = avcodec_h264dec;
+ info("avcodec: h264 decoder activated\n");
+ }
+ else {
+ st->codec = avcodec_find_decoder(codec_id);
+ if (!st->codec)
+ return ENOENT;
+ }
+
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(92<<8)+0)
+ st->ctx = avcodec_alloc_context3(st->codec);
+#else
+ st->ctx = avcodec_alloc_context();
+#endif
+
+#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100)
+ st->pict = av_frame_alloc();
+#else
+ st->pict = avcodec_alloc_frame();
+#endif
+
+ if (!st->ctx || !st->pict)
+ return ENOMEM;
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0)
+ if (avcodec_open2(st->ctx, st->codec, NULL) < 0)
+ return ENOENT;
+#else
+ if (avcodec_open(st->ctx, st->codec) < 0)
+ return ENOENT;
+#endif
+
+ return 0;
+}
+
+
+int decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *st;
+ int err = 0;
+
+ if (!vdsp || !vc)
+ return EINVAL;
+
+ if (*vdsp)
+ return 0;
+
+ (void)fmtp;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->mb = mbuf_alloc(1024);
+ if (!st->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = init_decoder(st, vc->name);
+ if (err) {
+ warning("avcodec: %s: could not init decoder\n", vc->name);
+ goto out;
+ }
+
+ debug("avcodec: video decoder %s (%s)\n", vc->name, fmtp);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *vdsp = st;
+
+ return err;
+}
+
+
+static int ffdecode(struct viddec_state *st, struct vidframe *frame)
+{
+ int i, got_picture, ret;
+ int err = 0;
+
+ st->mb->pos = 0;
+
+ if (!st->got_keyframe) {
+ debug("avcodec: waiting for key frame ..\n");
+ return 0;
+ }
+
+#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100)
+
+ do {
+ AVPacket avpkt;
+
+ av_init_packet(&avpkt);
+ avpkt.data = st->mb->buf;
+ avpkt.size = (int)st->mb->end;
+
+ ret = avcodec_send_packet(st->ctx, &avpkt);
+ if (ret < 0) {
+ warning("avcodec_send_packet error ret=%d\n", ret);
+ err = EBADMSG;
+ goto out;
+ }
+
+ ret = avcodec_receive_frame(st->ctx, st->pict);
+ if (ret == AVERROR(EAGAIN)) {
+ goto out;
+ }
+ else if (ret < 0) {
+ warning("avcodec_receive_frame error ret=%d\n", ret);
+ err = EBADMSG;
+ goto out;
+ }
+
+ got_picture = true;
+
+ } while (0);
+
+#elif LIBAVCODEC_VERSION_INT <= ((52<<16)+(23<<8)+0)
+ ret = avcodec_decode_video(st->ctx, st->pict, &got_picture,
+ st->mb->buf,
+ (int)st->mb->end);
+#else
+ do {
+ AVPacket avpkt;
+
+ av_init_packet(&avpkt);
+ avpkt.data = st->mb->buf;
+ avpkt.size = (int)st->mb->end;
+
+ ret = avcodec_decode_video2(st->ctx, st->pict,
+ &got_picture, &avpkt);
+ } while (0);
+#endif
+
+ if (ret < 0) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ if (got_picture) {
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0)
+ switch (st->pict->format) {
+
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_YUVJ420P:
+ frame->fmt = VID_FMT_YUV420P;
+ break;
+
+ case AV_PIX_FMT_NV12:
+ frame->fmt = VID_FMT_NV12;
+ break;
+
+ default:
+ warning("avcodec: decode: bad pixel format"
+ " (%i) (%s)\n",
+ st->pict->format,
+ av_get_pix_fmt_name(st->pict->format));
+ goto out;
+ }
+#else
+ frame->fmt = VID_FMT_YUV420P;
+#endif
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = st->pict->data[i];
+ frame->linesize[i] = st->pict->linesize[i];
+ }
+ frame->size.w = st->ctx->width;
+ frame->size.h = st->ctx->height;
+ }
+
+ out:
+ return err;
+}
+
+
+int decode_h264(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *src)
+{
+ struct h264_hdr h264_hdr;
+ const uint8_t nal_seq[3] = {0, 0, 1};
+ int err;
+
+ *intra = false;
+
+ err = h264_hdr_decode(&h264_hdr, src);
+ if (err)
+ return err;
+
+#if 0
+ re_printf("avcodec: decode: %s %s type=%2d \n",
+ marker ? "[M]" : " ",
+ h264_is_keyframe(h264_hdr.type) ? "<KEY>" : " ",
+ h264_hdr.type);
+#endif
+
+ if (h264_hdr.f) {
+ info("avcodec: H264 forbidden bit set!\n");
+ return EBADMSG;
+ }
+
+ if (st->frag && h264_hdr.type != H264_NAL_FU_A) {
+ debug("avcodec: lost fragments; discarding previous NAL\n");
+ fragment_rewind(st);
+ st->frag = false;
+ ++st->stats.n_lost;
+ }
+
+ /* handle NAL types */
+ if (1 <= h264_hdr.type && h264_hdr.type <= 23) {
+
+ if (h264_is_keyframe(h264_hdr.type))
+ *intra = true;
+
+ --src->pos;
+
+ /* prepend H.264 NAL start sequence */
+ err = mbuf_write_mem(st->mb, nal_seq, 3);
+
+ err |= mbuf_write_mem(st->mb, mbuf_buf(src),
+ mbuf_get_left(src));
+ if (err)
+ goto out;
+ }
+ else if (H264_NAL_FU_A == h264_hdr.type) {
+ struct h264_fu fu;
+
+ err = h264_fu_hdr_decode(&fu, src);
+ if (err)
+ return err;
+ h264_hdr.type = fu.type;
+
+ if (fu.s) {
+ if (st->frag) {
+ debug("avcodec: lost fragments;"
+ " ignoring NAL\n");
+ fragment_rewind(st);
+ ++st->stats.n_lost;
+ }
+
+ st->frag_start = st->mb->pos;
+ st->frag = true;
+
+ if (h264_is_keyframe(fu.type))
+ *intra = true;
+
+ /* prepend H.264 NAL start sequence */
+ mbuf_write_mem(st->mb, nal_seq, 3);
+
+ /* encode NAL header back to buffer */
+ err = h264_hdr_encode(&h264_hdr, st->mb);
+ }
+ else {
+ if (!st->frag) {
+ debug("avcodec: ignoring fragment\n");
+ ++st->stats.n_lost;
+ return 0;
+ }
+
+ if (seq_diff(st->frag_seq, seq) != 1) {
+ debug("avcodec: lost fragments detected\n");
+ fragment_rewind(st);
+ st->frag = false;
+ ++st->stats.n_lost;
+ return 0;
+ }
+ }
+
+ err = mbuf_write_mem(st->mb, mbuf_buf(src),
+ mbuf_get_left(src));
+ if (err)
+ goto out;
+
+ if (fu.e)
+ st->frag = false;
+
+ st->frag_seq = seq;
+ }
+ else {
+ warning("avcodec: unknown NAL type %u\n", h264_hdr.type);
+ return EBADMSG;
+ }
+
+ if (*intra) {
+ st->got_keyframe = true;
+ ++st->stats.n_key;
+ }
+
+ if (!marker) {
+
+ if (st->mb->end > DECODE_MAXSZ) {
+ warning("avcodec: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ if (st->frag) {
+ err = EPROTO;
+ goto out;
+ }
+
+ err = ffdecode(st, frame);
+ if (err)
+ goto out;
+
+ out:
+ mbuf_rewind(st->mb);
+ st->frag = false;
+
+ return err;
+}
+
+
+int decode_mpeg4(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *src)
+{
+ int err;
+
+ if (!src)
+ return 0;
+
+ (void)seq;
+
+ *intra = false; /* XXX */
+
+ /* let the decoder handle this */
+ st->got_keyframe = true;
+
+ err = mbuf_write_mem(st->mb, mbuf_buf(src),
+ mbuf_get_left(src));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (st->mb->end > DECODE_MAXSZ) {
+ warning("avcodec: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ err = ffdecode(st, frame);
+ if (err)
+ goto out;
+
+ out:
+ mbuf_rewind(st->mb);
+
+ return err;
+}
+
+
+int decode_h263(struct viddec_state *st, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *src)
+{
+ struct h263_hdr hdr;
+ int err;
+
+ if (!st || !frame || !intra)
+ return EINVAL;
+
+ *intra = false;
+
+ if (!src)
+ return 0;
+
+ (void)seq;
+
+ err = h263_hdr_decode(&hdr, src);
+ if (err)
+ return err;
+
+#if 0
+ debug(".....[%s seq=%5u ] MODE %s -"
+ " SBIT=%u EBIT=%u I=%s"
+ " (%5u/%5u bytes)\n",
+ marker ? "M" : " ", seq,
+ h263_hdr_mode(&hdr) == H263_MODE_A ? "A" : "B",
+ hdr.sbit, hdr.ebit, hdr.i ? "Inter" : "Intra",
+ mbuf_get_left(src), st->mb->end);
+#endif
+
+ if (!hdr.i) {
+ st->got_keyframe = true;
+ if (st->mb->pos == 0) {
+ *intra = true;
+ }
+ }
+
+#if 0
+ if (st->mb->pos == 0) {
+ uint8_t *p = mbuf_buf(src);
+
+ if (p[0] != 0x00 || p[1] != 0x00) {
+ warning("invalid PSC detected (%02x %02x)\n",
+ p[0], p[1]);
+ return EPROTO;
+ }
+ }
+#endif
+
+ /*
+ * The H.263 Bit-stream can be fragmented on bit-level,
+ * indicated by SBIT and EBIT. Example:
+ *
+ * 8 bit 2 bit
+ * .--------.--.
+ * Packet 1 | | |
+ * SBIT=0 '--------'--'
+ * EBIT=6
+ * .------.--------.--------.
+ * Packet 2 | | | |
+ * SBIT=2 '------'--------'--------'
+ * EBIT=0 6bit 8bit 8bit
+ *
+ */
+
+ if (hdr.sbit > 0) {
+ const uint8_t mask = (1 << (8 - hdr.sbit)) - 1;
+ const uint8_t sbyte = mbuf_read_u8(src) & mask;
+
+ st->mb->buf[st->mb->end - 1] |= sbyte;
+ }
+
+ err = mbuf_write_mem(st->mb, mbuf_buf(src),
+ mbuf_get_left(src));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (st->mb->end > DECODE_MAXSZ) {
+ warning("avcodec: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ if (!hdr.i) {
+ ++st->stats.n_key;
+ }
+
+ err = ffdecode(st, frame);
+ if (err)
+ goto out;
+
+ out:
+ mbuf_rewind(st->mb);
+
+ return err;
+}
diff --git a/modules/avcodec/encode.c b/modules/avcodec/encode.c
new file mode 100644
index 0000000..d0d5d83
--- /dev/null
+++ b/modules/avcodec/encode.c
@@ -0,0 +1,805 @@
+/**
+ * @file avcodec/encode.c Video codecs using libavcodec -- encoder
+ *
+ * Copyright (C) 2010 - 2013 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/mem.h>
+#if LIBAVUTIL_VERSION_INT >= ((50<<16)+(29<<8)+0)
+#include <libavutil/opt.h>
+#else
+#include <libavcodec/opt.h>
+#endif
+#ifdef USE_X264
+#include <x264.h>
+#endif
+#include "h26x.h"
+#include "avcodec.h"
+
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#define AV_PIX_FMT_NV12 PIX_FMT_NV12
+#endif
+
+
+enum {
+ DEFAULT_GOP_SIZE = 10,
+};
+
+
+struct picsz {
+ enum h263_fmt fmt; /**< Picture size */
+ uint8_t mpi; /**< Minimum Picture Interval (1-32) */
+};
+
+
+struct videnc_state {
+ AVCodec *codec;
+ AVCodecContext *ctx;
+ AVFrame *pict;
+ struct mbuf *mb;
+ size_t sz_max; /* todo: figure out proper buffer size */
+ int64_t pts;
+ struct mbuf *mb_frag;
+ struct videnc_param encprm;
+ struct vidsz encsize;
+ enum AVCodecID codec_id;
+ videnc_packet_h *pkth;
+ void *arg;
+
+ union {
+ struct {
+ struct picsz picszv[8];
+ uint32_t picszn;
+ } h263;
+
+ struct {
+ uint32_t packetization_mode;
+ uint32_t profile_idc;
+ uint32_t profile_iop;
+ uint32_t level_idc;
+ uint32_t max_fs;
+ uint32_t max_smbps;
+ } h264;
+ } u;
+
+#ifdef USE_X264
+ x264_t *x264;
+#endif
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *st = arg;
+
+ mem_deref(st->mb);
+ mem_deref(st->mb_frag);
+
+#ifdef USE_X264
+ if (st->x264)
+ x264_encoder_close(st->x264);
+#endif
+
+ if (st->ctx) {
+ if (st->ctx->codec)
+ avcodec_close(st->ctx);
+#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0)
+ av_opt_free(st->ctx);
+#endif
+ av_free(st->ctx);
+ }
+
+ if (st->pict)
+ av_free(st->pict);
+}
+
+
+static enum h263_fmt h263_fmt(const struct pl *name)
+{
+ if (0 == pl_strcasecmp(name, "sqcif")) return H263_FMT_SQCIF;
+ if (0 == pl_strcasecmp(name, "qcif")) return H263_FMT_QCIF;
+ if (0 == pl_strcasecmp(name, "cif")) return H263_FMT_CIF;
+ if (0 == pl_strcasecmp(name, "cif4")) return H263_FMT_4CIF;
+ if (0 == pl_strcasecmp(name, "cif16")) return H263_FMT_16CIF;
+ return H263_FMT_OTHER;
+}
+
+
+static int decode_sdpparam_h263(struct videnc_state *st, const struct pl *name,
+ const struct pl *val)
+{
+ enum h263_fmt fmt = h263_fmt(name);
+ const int mpi = pl_u32(val);
+
+ if (fmt == H263_FMT_OTHER) {
+ info("h263: unknown param '%r'\n", name);
+ return 0;
+ }
+ if (mpi < 1 || mpi > 32) {
+ info("h263: %r: MPI out of range %d\n", name, mpi);
+ return 0;
+ }
+
+ if (st->u.h263.picszn >= ARRAY_SIZE(st->u.h263.picszv)) {
+ info("h263: picszv overflow: %r\n", name);
+ return 0;
+ }
+
+ st->u.h263.picszv[st->u.h263.picszn].fmt = fmt;
+ st->u.h263.picszv[st->u.h263.picszn].mpi = mpi;
+
+ ++st->u.h263.picszn;
+
+ return 0;
+}
+
+
+static int init_encoder(struct videnc_state *st)
+{
+ /*
+ * Special handling of H.264 encoder
+ */
+ if (st->codec_id == AV_CODEC_ID_H264 && avcodec_h264enc) {
+
+#ifdef USE_X264
+ warning("avcodec: h264enc specified, but using libx264\n");
+ return EINVAL;
+#else
+ st->codec = avcodec_h264enc;
+
+ info("avcodec: h264 encoder activated\n");
+
+ return 0;
+#endif
+ }
+
+ st->codec = avcodec_find_encoder(st->codec_id);
+ if (!st->codec)
+ return ENOENT;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *st,
+ const struct videnc_param *prm,
+ const struct vidsz *size,
+ int pix_fmt)
+{
+ int err = 0;
+
+ if (st->ctx) {
+ if (st->ctx->codec)
+ avcodec_close(st->ctx);
+#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0)
+ av_opt_free(st->ctx);
+#endif
+ av_free(st->ctx);
+ }
+
+ if (st->pict)
+ av_free(st->pict);
+
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(92<<8)+0)
+ st->ctx = avcodec_alloc_context3(st->codec);
+#else
+ st->ctx = avcodec_alloc_context();
+#endif
+
+#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100)
+ st->pict = av_frame_alloc();
+#else
+ st->pict = avcodec_alloc_frame();
+#endif
+
+ if (!st->ctx || !st->pict) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ av_opt_set_defaults(st->ctx);
+
+ st->ctx->bit_rate = prm->bitrate;
+ st->ctx->width = size->w;
+ st->ctx->height = size->h;
+ st->ctx->gop_size = DEFAULT_GOP_SIZE;
+ st->ctx->pix_fmt = pix_fmt;
+ st->ctx->time_base.num = 1;
+ st->ctx->time_base.den = prm->fps;
+
+ /* params to avoid libavcodec/x264 default preset error */
+ if (st->codec_id == AV_CODEC_ID_H264) {
+ st->ctx->me_range = 16;
+ st->ctx->qmin = 10;
+ st->ctx->qmax = 51;
+ st->ctx->max_qdiff = 4;
+
+#ifndef USE_X264
+ if (st->codec == avcodec_find_encoder_by_name("nvenc_h264") ||
+ st->codec == avcodec_find_encoder_by_name("h264_nvenc")) {
+
+#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(21<<8)+0)
+ err = av_opt_set(st->ctx->priv_data,
+ "preset", "llhp", 0);
+
+ if (err < 0) {
+ debug("avcodec: h264 nvenc setting preset "
+ "\"llhp\" failed; error: %u\n", err);
+ }
+ else {
+ debug("avcodec: h264 nvenc preset "
+ "\"llhp\" selected\n");
+ }
+ err = av_opt_set_int(st->ctx->priv_data,
+ "2pass", 1, 0);
+
+ if (err < 0) {
+ debug("avcodec: h264 nvenc option "
+ "\"2pass\" failed; error: %u\n", err);
+ }
+ else {
+ debug("avcodec: h264 nvenc option "
+ "\"2pass\" selected\n");
+ }
+#endif
+ }
+#endif
+ }
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0)
+ if (avcodec_open2(st->ctx, st->codec, NULL) < 0) {
+ err = ENOENT;
+ goto out;
+ }
+#else
+ if (avcodec_open(st->ctx, st->codec) < 0) {
+ err = ENOENT;
+ goto out;
+ }
+#endif
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0)
+ st->pict->format = pix_fmt;
+ st->pict->width = size->w;
+ st->pict->height = size->h;
+#endif
+
+ out:
+ if (err) {
+ if (st->ctx) {
+ if (st->ctx->codec)
+ avcodec_close(st->ctx);
+#if LIBAVUTIL_VERSION_INT >= ((51<<16)+(8<<8)+0)
+ av_opt_free(st->ctx);
+#endif
+ av_free(st->ctx);
+ st->ctx = NULL;
+ }
+
+ if (st->pict) {
+ av_free(st->pict);
+ st->pict = NULL;
+ }
+ }
+ else
+ st->encsize = *size;
+
+ return err;
+}
+
+
+int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name,
+ const struct pl *val)
+{
+ if (0 == pl_strcasecmp(name, "packetization-mode")) {
+ st->u.h264.packetization_mode = pl_u32(val);
+
+ if (st->u.h264.packetization_mode != 0) {
+ warning("avcodec: illegal packetization-mode %u\n",
+ st->u.h264.packetization_mode);
+ return EPROTO;
+ }
+ }
+ else if (0 == pl_strcasecmp(name, "profile-level-id")) {
+ struct pl prof = *val;
+ if (prof.l != 6) {
+ warning("avcodec: invalid profile-level-id (%r)\n",
+ val);
+ return EPROTO;
+ }
+
+ prof.l = 2;
+ st->u.h264.profile_idc = pl_x32(&prof); prof.p += 2;
+ st->u.h264.profile_iop = pl_x32(&prof); prof.p += 2;
+ st->u.h264.level_idc = pl_x32(&prof);
+ }
+ else if (0 == pl_strcasecmp(name, "max-fs")) {
+ st->u.h264.max_fs = pl_u32(val);
+ }
+ else if (0 == pl_strcasecmp(name, "max-smbps")) {
+ st->u.h264.max_smbps = pl_u32(val);
+ }
+
+ return 0;
+}
+
+
+static void param_handler(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct videnc_state *st = arg;
+
+ if (st->codec_id == AV_CODEC_ID_H263)
+ (void)decode_sdpparam_h263(st, name, val);
+ else if (st->codec_id == AV_CODEC_ID_H264)
+ (void)decode_sdpparam_h264(st, name, val);
+}
+
+
+static int general_packetize(uint32_t rtp_ts, struct mbuf *mb, size_t pktsize,
+ videnc_packet_h *pkth, void *arg)
+{
+ int err = 0;
+
+ /* Assemble frame into smaller packets */
+ while (!err) {
+ size_t sz, left = mbuf_get_left(mb);
+ bool last = (left < pktsize);
+ if (!left)
+ break;
+
+ sz = last ? left : pktsize;
+
+ err = pkth(last, rtp_ts, NULL, 0, mbuf_buf(mb), sz,
+ arg);
+
+ mbuf_advance(mb, sz);
+ }
+
+ return err;
+}
+
+
+static int h263_packetize(struct videnc_state *st,
+ uint32_t rtp_ts, struct mbuf *mb,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct h263_strm h263_strm;
+ struct h263_hdr h263_hdr;
+ size_t pos;
+ int err;
+
+ /* Decode bit-stream header, used by packetizer */
+ err = h263_strm_decode(&h263_strm, mb);
+ if (err)
+ return err;
+
+ h263_hdr_copy_strm(&h263_hdr, &h263_strm);
+
+ st->mb_frag->pos = st->mb_frag->end = 0;
+ err = h263_hdr_encode(&h263_hdr, st->mb_frag);
+ pos = st->mb_frag->pos;
+
+ /* Assemble frame into smaller packets */
+ while (!err) {
+ size_t sz, left = mbuf_get_left(mb);
+ bool last = (left < st->encprm.pktsize);
+ if (!left)
+ break;
+
+ sz = last ? left : st->encprm.pktsize;
+
+ st->mb_frag->pos = st->mb_frag->end = pos;
+ err = mbuf_write_mem(st->mb_frag, mbuf_buf(mb), sz);
+ if (err)
+ break;
+
+ st->mb_frag->pos = 0;
+
+ err = pkth(last, rtp_ts, NULL, 0, mbuf_buf(st->mb_frag),
+ mbuf_get_left(st->mb_frag), arg);
+
+ mbuf_advance(mb, sz);
+ }
+
+ return err;
+}
+
+
+#ifdef USE_X264
+static int open_encoder_x264(struct videnc_state *st, struct videnc_param *prm,
+ const struct vidsz *size, int csp)
+{
+ x264_param_t xprm;
+
+ if (x264_param_default_preset(&xprm, "ultrafast", "zerolatency"))
+ return ENOSYS;
+
+ x264_param_apply_profile(&xprm, "baseline");
+
+ xprm.i_level_idc = h264_level_idc;
+ xprm.i_width = size->w;
+ xprm.i_height = size->h;
+ xprm.i_csp = csp;
+ xprm.i_fps_num = prm->fps;
+ xprm.i_fps_den = 1;
+ xprm.rc.i_bitrate = prm->bitrate / 1000; /* kbit/s */
+ xprm.rc.i_rc_method = X264_RC_ABR;
+ xprm.i_log_level = X264_LOG_WARNING;
+
+ /* ultrafast preset */
+ xprm.i_frame_reference = 1;
+ xprm.i_scenecut_threshold = 0;
+ xprm.b_deblocking_filter = 0;
+ xprm.b_cabac = 0;
+ xprm.i_bframe = 0;
+ xprm.analyse.intra = 0;
+ xprm.analyse.inter = 0;
+ xprm.analyse.b_transform_8x8 = 0;
+ xprm.analyse.i_me_method = X264_ME_DIA;
+ xprm.analyse.i_subpel_refine = 0;
+#if X264_BUILD >= 59
+ xprm.rc.i_aq_mode = 0;
+#endif
+ xprm.analyse.b_mixed_references = 0;
+ xprm.analyse.i_trellis = 0;
+#if X264_BUILD >= 63
+ xprm.i_bframe_adaptive = X264_B_ADAPT_NONE;
+#endif
+#if X264_BUILD >= 70
+ xprm.rc.b_mb_tree = 0;
+#endif
+
+ /* slice-based threading (--tune=zerolatency) */
+#if X264_BUILD >= 80
+ xprm.rc.i_lookahead = 0;
+ xprm.i_sync_lookahead = 0;
+ xprm.i_bframe = 0;
+#endif
+
+ /* put SPS/PPS before each keyframe */
+ xprm.b_repeat_headers = 1;
+
+#if X264_BUILD >= 82
+ /* needed for x264_encoder_intra_refresh() */
+ xprm.b_intra_refresh = 1;
+#endif
+
+ if (st->x264)
+ x264_encoder_close(st->x264);
+
+ st->x264 = x264_encoder_open(&xprm);
+ if (!st->x264) {
+ warning("avcodec: x264_encoder_open() failed\n");
+ return ENOENT;
+ }
+
+ st->encsize = *size;
+
+ return 0;
+}
+#endif
+
+
+int encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *st;
+ int err = 0;
+
+ if (!vesp || !vc || !prm || !pkth)
+ return EINVAL;
+
+ if (*vesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->encprm = *prm;
+ st->pkth = pkth;
+ st->arg = arg;
+
+ st->codec_id = avcodec_resolve_codecid(vc->name);
+ if (st->codec_id == AV_CODEC_ID_NONE) {
+ err = EINVAL;
+ goto out;
+ }
+
+ st->mb = mbuf_alloc(FF_MIN_BUFFER_SIZE * 20);
+ st->mb_frag = mbuf_alloc(1024);
+ if (!st->mb || !st->mb_frag) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->sz_max = st->mb->size;
+
+ if (st->codec_id == AV_CODEC_ID_H264) {
+#ifndef USE_X264
+ err = init_encoder(st);
+#endif
+ }
+ else
+ err = init_encoder(st);
+ if (err) {
+ warning("avcodec: %s: could not init encoder\n", vc->name);
+ goto out;
+ }
+
+ if (str_isset(fmtp)) {
+ struct pl sdp_fmtp;
+
+ pl_set_str(&sdp_fmtp, fmtp);
+
+ fmt_param_apply(&sdp_fmtp, param_handler, st);
+ }
+
+ debug("avcodec: video encoder %s: %d fps, %d bit/s, pktsize=%u\n",
+ vc->name, prm->fps, prm->bitrate, prm->pktsize);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *vesp = st;
+
+ return err;
+}
+
+
+#ifdef USE_X264
+int encode_x264(struct videnc_state *st, bool update,
+ const struct vidframe *frame)
+{
+ x264_picture_t pic_in, pic_out;
+ x264_nal_t *nal;
+ int i_nal;
+ int i, err, ret;
+ int csp, pln;
+ uint32_t ts;
+
+ if (!st || !frame)
+ return EINVAL;
+
+ switch (frame->fmt) {
+
+ case VID_FMT_YUV420P:
+ csp = X264_CSP_I420;
+ pln = 3;
+ break;
+
+ case VID_FMT_NV12:
+ csp = X264_CSP_NV12;
+ pln = 2;
+ break;
+
+ default:
+ warning("avcodec: pixel format not supported (%s)\n",
+ vidfmt_name(frame->fmt));
+ return ENOTSUP;
+ }
+
+ if (!st->x264 || !vidsz_cmp(&st->encsize, &frame->size)) {
+
+ err = open_encoder_x264(st, &st->encprm, &frame->size, csp);
+ if (err)
+ return err;
+ }
+
+ if (update) {
+#if X264_BUILD >= 95
+ x264_encoder_intra_refresh(st->x264);
+#endif
+ debug("avcodec: x264 picture update\n");
+ }
+
+ x264_picture_init(&pic_in);
+
+ pic_in.i_type = update ? X264_TYPE_IDR : X264_TYPE_AUTO;
+ pic_in.i_qpplus1 = 0;
+ pic_in.i_pts = ++st->pts;
+
+ pic_in.img.i_csp = csp;
+ pic_in.img.i_plane = pln;
+ for (i=0; i<pln; i++) {
+ pic_in.img.i_stride[i] = frame->linesize[i];
+ pic_in.img.plane[i] = frame->data[i];
+ }
+
+ ret = x264_encoder_encode(st->x264, &nal, &i_nal, &pic_in, &pic_out);
+ if (ret < 0) {
+ warning("avcodec: x264 [error]: x264_encoder_encode failed\n");
+ }
+ if (i_nal == 0)
+ return 0;
+
+ ts = video_calc_rtp_timestamp(pic_out.i_pts, st->encprm.fps);
+
+ err = 0;
+ for (i=0; i<i_nal && !err; i++) {
+ const uint8_t hdr = nal[i].i_ref_idc<<5 | nal[i].i_type<<0;
+ int offset = 0;
+
+#if X264_BUILD >= 76
+ const uint8_t *p = nal[i].p_payload;
+
+ /* Find the NAL Escape code [00 00 01] */
+ if (nal[i].i_payload > 4 && p[0] == 0x00 && p[1] == 0x00) {
+ if (p[2] == 0x00 && p[3] == 0x01)
+ offset = 4 + 1;
+ else if (p[2] == 0x01)
+ offset = 3 + 1;
+ }
+#endif
+
+ /* skip Supplemental Enhancement Information (SEI) */
+ if (nal[i].i_type == H264_NAL_SEI)
+ continue;
+
+ err = h264_nal_send(true, true, (i+1)==i_nal, hdr, ts,
+ nal[i].p_payload + offset,
+ nal[i].i_payload - offset,
+ st->encprm.pktsize,
+ st->pkth, st->arg);
+ }
+
+ return err;
+}
+#endif
+
+
+int encode(struct videnc_state *st, bool update, const struct vidframe *frame)
+{
+ int i, err, ret;
+ int pix_fmt;
+ uint32_t ts;
+
+ if (!st || !frame)
+ return EINVAL;
+
+ switch (frame->fmt) {
+
+ case VID_FMT_YUV420P:
+ pix_fmt = AV_PIX_FMT_YUV420P;
+ break;
+
+ case VID_FMT_NV12:
+ pix_fmt = AV_PIX_FMT_NV12;
+ break;
+
+ default:
+ warning("avcodec: pixel format not supported (%s)\n",
+ vidfmt_name(frame->fmt));
+ return ENOTSUP;
+ }
+
+ if (!st->ctx || !vidsz_cmp(&st->encsize, &frame->size)) {
+
+ err = open_encoder(st, &st->encprm, &frame->size, pix_fmt);
+ if (err) {
+ warning("avcodec: open_encoder: %m\n", err);
+ return err;
+ }
+ }
+
+ for (i=0; i<4; i++) {
+ st->pict->data[i] = frame->data[i];
+ st->pict->linesize[i] = frame->linesize[i];
+ }
+ st->pict->pts = st->pts++;
+ if (update) {
+ debug("avcodec: encoder picture update\n");
+ st->pict->key_frame = 1;
+#ifdef FF_I_TYPE
+ st->pict->pict_type = FF_I_TYPE; /* Infra Frame */
+#else
+ st->pict->pict_type = AV_PICTURE_TYPE_I;
+#endif
+ }
+ else {
+ st->pict->key_frame = 0;
+ st->pict->pict_type = 0;
+ }
+
+ mbuf_rewind(st->mb);
+
+#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100)
+ do {
+ AVPacket *pkt;
+
+ ret = avcodec_send_frame(st->ctx, st->pict);
+ if (ret < 0)
+ return EBADMSG;
+
+ pkt = av_packet_alloc();
+ if (!pkt)
+ return ENOMEM;
+
+ ret = avcodec_receive_packet(st->ctx, pkt);
+ if (ret < 0) {
+ av_packet_free(&pkt);
+ return 0;
+ }
+
+ ts = video_calc_rtp_timestamp(pkt->dts, st->encprm.fps);
+
+ err = mbuf_write_mem(st->mb, pkt->data, pkt->size);
+ st->mb->pos = 0;
+
+ av_packet_free(&pkt);
+
+ if (err)
+ return err;
+ } while (0);
+#elif LIBAVCODEC_VERSION_INT >= ((54<<16)+(1<<8)+0)
+ do {
+ AVPacket avpkt;
+ int got_packet;
+
+ av_init_packet(&avpkt);
+
+ avpkt.data = st->mb->buf;
+ avpkt.size = (int)st->mb->size;
+
+ ret = avcodec_encode_video2(st->ctx, &avpkt,
+ st->pict, &got_packet);
+ if (ret < 0)
+ return EBADMSG;
+ if (!got_packet)
+ return 0;
+
+ mbuf_set_end(st->mb, avpkt.size);
+
+ ts = video_calc_rtp_timestamp(avpkt.dts, st->encprm.fps);
+
+ } while (0);
+#else
+ ret = avcodec_encode_video(st->ctx, st->mb->buf,
+ (int)st->mb->size, st->pict);
+ if (ret < 0 )
+ return EBADMSG;
+
+ /* todo: figure out proper buffer size */
+ if (ret > (int)st->sz_max) {
+ debug("avcodec: grow encode buffer %u --> %d\n",
+ st->sz_max, ret);
+ st->sz_max = ret;
+ }
+
+ mbuf_set_end(st->mb, ret);
+
+ ts = video_calc_rtp_timestamp(st->pict->pts, st->encprm.fps);
+#endif
+
+ switch (st->codec_id) {
+
+ case AV_CODEC_ID_H263:
+ err = h263_packetize(st, ts, st->mb, st->pkth, st->arg);
+ break;
+
+ case AV_CODEC_ID_H264:
+ err = h264_packetize(ts, st->mb->buf, st->mb->end,
+ st->encprm.pktsize,
+ st->pkth, st->arg);
+ break;
+
+ case AV_CODEC_ID_MPEG4:
+ err = general_packetize(ts, st->mb, st->encprm.pktsize,
+ st->pkth, st->arg);
+ break;
+
+ default:
+ err = EPROTO;
+ break;
+ }
+
+ return err;
+}
diff --git a/modules/avcodec/h263.c b/modules/avcodec/h263.c
new file mode 100644
index 0000000..5ee016e
--- /dev/null
+++ b/modules/avcodec/h263.c
@@ -0,0 +1,188 @@
+/**
+ * @file h263.c H.263 video codec (RFC 4629)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libavcodec/avcodec.h>
+#ifdef USE_X264
+#include <x264.h>
+#endif
+#include "h26x.h"
+#include "avcodec.h"
+
+
+int h263_hdr_encode(const struct h263_hdr *hdr, struct mbuf *mb)
+{
+ uint32_t v; /* host byte order */
+
+ v = hdr->f<<31 | hdr->p<<30 | hdr->sbit<<27 | hdr->ebit<<24;
+ v |= hdr->src<<21 | hdr->i<<20 | hdr->u<<19 | hdr->s<<18 | hdr->a<<17;
+ v |= hdr->r<<13 | hdr->dbq<<11 | hdr->trb<<8 | hdr->tr<<0;
+
+ return mbuf_write_u32(mb, htonl(v));
+}
+
+
+enum h263_mode h263_hdr_mode(const struct h263_hdr *hdr)
+{
+ if (!hdr->f) {
+ return H263_MODE_A;
+ }
+ else {
+ if (!hdr->p)
+ return H263_MODE_B;
+ else
+ return H263_MODE_C;
+ }
+}
+
+
+int h263_hdr_decode(struct h263_hdr *hdr, struct mbuf *mb)
+{
+ uint32_t v;
+
+ if (!hdr)
+ return EINVAL;
+ if (mbuf_get_left(mb) < H263_HDR_SIZE_MODEA)
+ return EBADMSG;
+
+ v = ntohl(mbuf_read_u32(mb));
+
+ /* Common */
+ hdr->f = v>>31 & 0x1;
+ hdr->p = v>>30 & 0x1;
+ hdr->sbit = v>>27 & 0x7;
+ hdr->ebit = v>>24 & 0x7;
+ hdr->src = v>>21 & 0x7;
+
+ switch (h263_hdr_mode(hdr)) {
+
+ case H263_MODE_A:
+ hdr->i = v>>20 & 0x1;
+ hdr->u = v>>19 & 0x1;
+ hdr->s = v>>18 & 0x1;
+ hdr->a = v>>17 & 0x1;
+ hdr->r = v>>13 & 0xf;
+ hdr->dbq = v>>11 & 0x3;
+ hdr->trb = v>>8 & 0x7;
+ hdr->tr = v>>0 & 0xff;
+ break;
+
+ case H263_MODE_B:
+ hdr->quant = v>>16 & 0x1f;
+ hdr->gobn = v>>11 & 0x1f;
+ hdr->mba = v>>2 & 0x1ff;
+
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ v = ntohl(mbuf_read_u32(mb));
+
+ hdr->i = v>>31 & 0x1;
+ hdr->u = v>>30 & 0x1;
+ hdr->s = v>>29 & 0x1;
+ hdr->a = v>>28 & 0x1;
+ hdr->hmv1 = v>>21 & 0x7f;
+ hdr->vmv1 = v>>14 & 0x7f;
+ hdr->hmv2 = v>>7 & 0x7f;
+ hdr->vmv2 = v>>0 & 0x7f;
+ break;
+
+ case H263_MODE_C:
+ /* NOTE: Mode C is optional, only parts decoded */
+ if (mbuf_get_left(mb) < 8)
+ return EBADMSG;
+
+ v = ntohl(mbuf_read_u32(mb));
+ hdr->i = v>>31 & 0x1;
+ hdr->u = v>>30 & 0x1;
+ hdr->s = v>>29 & 0x1;
+ hdr->a = v>>28 & 0x1;
+
+ (void)mbuf_read_u32(mb); /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Find PSC (Picture Start Code) in bit-stream
+ *
+ * @param p Input bit-stream
+ * @param size Number of bytes in bit-stream
+ *
+ * @return Pointer to PSC if found, otherwise NULL
+ */
+const uint8_t *h263_strm_find_psc(const uint8_t *p, uint32_t size)
+{
+ const uint8_t *end = p + size - 1;
+
+ for (; p < end; p++) {
+ if (p[0] == 0x00 && p[1] == 0x00)
+ return p;
+ }
+
+ return NULL;
+}
+
+
+int h263_strm_decode(struct h263_strm *s, struct mbuf *mb)
+{
+ const uint8_t *p;
+
+ if (mbuf_get_left(mb) < 6)
+ return EINVAL;
+
+ p = mbuf_buf(mb);
+
+ s->psc[0] = p[0];
+ s->psc[1] = p[1];
+
+ s->temp_ref = (p[2]<<6 & 0xc0) | (p[3]>>2 & 0x3f);
+
+ s->split_scr = p[4]>>7 & 0x1;
+ s->doc_camera = p[4]>>6 & 0x1;
+ s->pic_frz_rel = p[4]>>5 & 0x1;
+ s->src_fmt = p[4]>>2 & 0x7;
+ s->pic_type = p[4]>>1 & 0x1;
+ s->umv = p[4]>>0 & 0x1;
+
+ s->sac = p[5]>>7 & 0x1;
+ s->apm = p[5]>>6 & 0x1;
+ s->pb = p[5]>>5 & 0x1;
+ s->pquant = p[5]>>0 & 0x1f;
+
+ s->cpm = p[6]>>7 & 0x1;
+ s->pei = p[6]>>6 & 0x1;
+
+ return 0;
+}
+
+
+/**
+ * Copy H.263 bit-stream to H.263 RTP payload header
+ *
+ * @param hdr H.263 header to be written to
+ * @param s H.263 stream header
+ */
+void h263_hdr_copy_strm(struct h263_hdr *hdr, const struct h263_strm *s)
+{
+ hdr->f = 0; /* Mode A */
+ hdr->p = 0;
+ hdr->sbit = 0;
+ hdr->ebit = 0;
+ hdr->src = s->src_fmt;
+ hdr->i = s->pic_type;
+ hdr->u = s->umv;
+ hdr->s = s->sac;
+ hdr->a = s->apm;
+ hdr->r = 0;
+ hdr->dbq = 0; /* No PB-frames */
+ hdr->trb = 0; /* No PB-frames */
+ hdr->tr = s->temp_ref;
+}
diff --git a/modules/avcodec/h26x.h b/modules/avcodec/h26x.h
new file mode 100644
index 0000000..faff489
--- /dev/null
+++ b/modules/avcodec/h26x.h
@@ -0,0 +1,104 @@
+/**
+ * @file h26x.h Interface to H.26x video codecs
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/*
+ * H.263
+ */
+
+
+enum h263_mode {
+ H263_MODE_A,
+ H263_MODE_B,
+ H263_MODE_C
+};
+
+enum {
+ H263_HDR_SIZE_MODEA = 4,
+ H263_HDR_SIZE_MODEB = 8,
+ H263_HDR_SIZE_MODEC = 12
+};
+
+/** H.263 picture size format */
+enum h263_fmt {
+ H263_FMT_SQCIF = 1, /**< 128 x 96 */
+ H263_FMT_QCIF = 2, /**< 176 x 144 */
+ H263_FMT_CIF = 3, /**< 352 x 288 */
+ H263_FMT_4CIF = 4, /**< 704 x 576 */
+ H263_FMT_16CIF = 5, /**< 1408 x 1152 */
+ H263_FMT_OTHER = 7,
+};
+
+/**
+ * H.263 Header defined in RFC 2190
+ */
+struct h263_hdr {
+
+ /* common */
+ unsigned f:1; /**< 1 bit - Flag; 0=mode A, 1=mode B/C */
+ unsigned p:1; /**< 1 bit - PB-frames, 0=mode B, 1=mode C */
+ unsigned sbit:3; /**< 3 bits - Start Bit Position (SBIT) */
+ unsigned ebit:3; /**< 3 bits - End Bit Position (EBIT) */
+ unsigned src:3; /**< 3 bits - Source format */
+
+ /* mode A */
+ unsigned i:1; /**< 1 bit - 0=intra-coded, 1=inter-coded */
+ unsigned u:1; /**< 1 bit - Unrestricted Motion Vector */
+ unsigned s:1; /**< 1 bit - Syntax-based Arithmetic Coding */
+ unsigned a:1; /**< 1 bit - Advanced Prediction option */
+ unsigned r:4; /**< 4 bits - Reserved (zero) */
+ unsigned dbq:2; /**< 2 bits - DBQUANT */
+ unsigned trb:3; /**< 3 bits - Temporal Reference for B-frame */
+ unsigned tr:8; /**< 8 bits - Temporal Reference for P-frame */
+
+ /* mode B */
+ unsigned quant:5; //=0 for GOB header
+ unsigned gobn:5; // gob number
+ unsigned mba:9; // address
+ unsigned hmv1:7; // horizontal motion vector
+ unsigned vmv1:7; // vertical motion vector
+ unsigned hmv2:7;
+ unsigned vmv2:7;
+
+
+};
+
+enum {I_FRAME=0, P_FRAME=1};
+
+/** H.263 bit-stream header */
+struct h263_strm {
+ uint8_t psc[2]; /**< Picture Start Code (PSC) */
+
+ uint8_t temp_ref; /**< Temporal Reference */
+ unsigned split_scr:1; /**< Split Screen Indicator */
+ unsigned doc_camera:1; /**< Document Camera Indicator */
+ unsigned pic_frz_rel:1; /**< Full Picture Freeze Release */
+ unsigned src_fmt:3; /**< Source Format. 3=CIF */
+ unsigned pic_type:1; /**< Picture Coding Type. 0=I, 1=P */
+ unsigned umv:1; /**< Unrestricted Motion Vector mode */
+ unsigned sac:1; /**< Syntax-based Arithmetic Coding */
+ unsigned apm:1; /**< Advanced Prediction mode */
+ unsigned pb:1; /**< PB-frames mode */
+ unsigned pquant:5; /**< Quantizer Information */
+ unsigned cpm:1; /**< Continuous Presence Multipoint */
+ unsigned pei:1; /**< Extra Insertion Information */
+ /* H.263 bit-stream ... */
+};
+
+int h263_hdr_encode(const struct h263_hdr *hdr, struct mbuf *mb);
+int h263_hdr_decode(struct h263_hdr *hdr, struct mbuf *mb);
+enum h263_mode h263_hdr_mode(const struct h263_hdr *hdr);
+
+const uint8_t *h263_strm_find_psc(const uint8_t *p, uint32_t size);
+int h263_strm_decode(struct h263_strm *s, struct mbuf *mb);
+void h263_hdr_copy_strm(struct h263_hdr *hdr, const struct h263_strm *s);
+
+
+/*
+ * H.264
+ */
+
+int h264_decode_sprop_params(AVCodecContext *codec, struct pl *pl);
diff --git a/modules/avcodec/module.mk b/modules/avcodec/module.mk
new file mode 100644
index 0000000..9e5f25c
--- /dev/null
+++ b/modules/avcodec/module.mk
@@ -0,0 +1,21 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+USE_X264 := $(shell [ -f $(SYSROOT)/include/x264.h ] || \
+ [ -f $(SYSROOT)/local/include/x264.h ] || \
+ [ -f $(SYSROOT_ALT)/include/x264.h ] && echo "yes")
+
+MOD := avcodec
+$(MOD)_SRCS += avcodec.c h263.c encode.c decode.c
+$(MOD)_LFLAGS += -lavcodec -lavutil
+CFLAGS += -DUSE_AVCODEC
+ifneq ($(USE_X264),)
+CFLAGS += -DUSE_X264
+$(MOD)_LFLAGS += -lx264
+endif
+$(MOD)_CFLAGS += -isystem /usr/local/include
+
+include mk/mod.mk
diff --git a/modules/avformat/avformat.c b/modules/avformat/avformat.c
new file mode 100644
index 0000000..68ef088
--- /dev/null
+++ b/modules/avformat/avformat.c
@@ -0,0 +1,448 @@
+/**
+ * @file avformat.c libavformat video-source
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <string.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#define FF_API_OLD_METADATA 0
+#include <libavformat/avformat.h>
+#include <libavdevice/avdevice.h>
+#include <libavcodec/avcodec.h>
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0)
+#include <libavutil/pixdesc.h>
+#endif
+
+
+/**
+ * @defgroup avformat avformat
+ *
+ * Video source using FFmpeg/libav libavformat
+ *
+ *
+ * Example config:
+ \verbatim
+ video_source avformat,/tmp/testfile.mp4
+ \endverbatim
+ */
+
+
+/* backward compat */
+#if LIBAVCODEC_VERSION_MAJOR>52 || LIBAVCODEC_VERSION_INT>=((52<<16)+(64<<8))
+#define LIBAVCODEC_HAVE_AVMEDIA_TYPES 1
+#endif
+#ifndef LIBAVCODEC_HAVE_AVMEDIA_TYPES
+#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
+#endif
+
+
+#if LIBAVCODEC_VERSION_INT < ((54<<16)+(25<<8)+0)
+#define AVCodecID CodecID
+#define AV_CODEC_ID_NONE CODEC_ID_NONE
+#endif
+
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
+#endif
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+ pthread_t thread;
+ bool run;
+ AVFormatContext *ic;
+ AVCodec *codec;
+ AVCodecContext *ctx;
+ AVRational time_base;
+ struct vidsz sz;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ int sindex;
+ int fps;
+};
+
+
+static struct vidsrc *mod_avf;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->ctx && st->ctx->codec)
+ avcodec_close(st->ctx);
+
+ if (st->ic) {
+#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (21<<8) + 0)
+ avformat_close_input(&st->ic);
+#else
+ av_close_input_file(st->ic);
+#endif
+ }
+}
+
+
+static void handle_packet(struct vidsrc_st *st, AVPacket *pkt)
+{
+ AVFrame *frame = NULL;
+ struct vidframe vf;
+ struct vidsz sz;
+ unsigned i;
+
+ if (st->codec) {
+ int got_pict, ret;
+
+#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100)
+ frame = av_frame_alloc();
+#else
+ frame = avcodec_alloc_frame();
+#endif
+
+#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100)
+
+ ret = avcodec_send_packet(st->ctx, pkt);
+ if (ret < 0)
+ goto out;
+
+ ret = avcodec_receive_frame(st->ctx, frame);
+ if (ret < 0)
+ goto out;
+
+ got_pict = true;
+
+#elif LIBAVCODEC_VERSION_INT <= ((52<<16)+(23<<8)+0)
+ ret = avcodec_decode_video(st->ctx, frame, &got_pict,
+ pkt->data, pkt->size);
+#else
+ ret = avcodec_decode_video2(st->ctx, frame,
+ &got_pict, pkt);
+#endif
+ if (ret < 0 || !got_pict)
+ goto out;
+
+ sz.w = st->ctx->width;
+ sz.h = st->ctx->height;
+
+ /* check if size changed */
+ if (!vidsz_cmp(&sz, &st->sz)) {
+ info("avformat: size changed: %d x %d ---> %d x %d\n",
+ st->sz.w, st->sz.h, sz.w, sz.h);
+ st->sz = sz;
+ }
+ }
+ else {
+ /* No-codec option is not supported */
+ return;
+ }
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(5<<8)+0)
+ switch (frame->format) {
+
+ case AV_PIX_FMT_YUV420P:
+ case AV_PIX_FMT_YUVJ420P:
+ vf.fmt = VID_FMT_YUV420P;
+ break;
+
+ default:
+ warning("avformat: decode: bad pixel format"
+ " (%i) (%s)\n",
+ frame->format,
+ av_get_pix_fmt_name(frame->format));
+ goto out;
+ }
+#else
+ vf.fmt = VID_FMT_YUV420P;
+#endif
+
+ vf.size = sz;
+ for (i=0; i<4; i++) {
+ vf.data[i] = frame->data[i];
+ vf.linesize[i] = frame->linesize[i];
+ }
+
+ st->frameh(&vf, st->arg);
+
+ out:
+ if (frame) {
+#if LIBAVUTIL_VERSION_INT >= ((52<<16)+(20<<8)+100)
+ av_frame_free(&frame);
+#else
+ av_free(frame);
+#endif
+ }
+}
+
+
+static void *read_thread(void *data)
+{
+ struct vidsrc_st *st = data;
+
+ uint64_t now, ts = tmr_jiffies();
+
+ while (st->run) {
+ AVPacket pkt;
+ int ret;
+
+ sys_msleep(4);
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+
+ av_init_packet(&pkt);
+
+ ret = av_read_frame(st->ic, &pkt);
+ if (ret < 0) {
+ debug("avformat: rewind stream (ret=%d)\n", ret);
+ sys_msleep(1000);
+ av_seek_frame(st->ic, -1, 0, 0);
+ continue;
+ }
+
+ if (pkt.stream_index != st->sindex)
+ goto out;
+
+ handle_packet(st, &pkt);
+
+ ts += (uint64_t) 1000 * pkt.duration * av_q2d(st->time_base);
+
+ out:
+#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(12<<8)+100)
+ av_packet_unref(&pkt);
+#else
+ av_free_packet(&pkt);
+#endif
+ }
+
+ return NULL;
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **mctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+#if LIBAVFORMAT_VERSION_INT < ((52<<16) + (110<<8) + 0)
+ AVFormatParameters prms;
+#endif
+ struct vidsrc_st *st;
+ bool found_stream = false;
+ uint32_t i;
+ int ret, err = 0;
+ int input_fps = 0;
+
+ (void)mctx;
+ (void)errorh;
+
+ if (!stp || !vs || !prm || !size || !frameh)
+ return EINVAL;
+
+ debug("avformat: alloc dev='%s'\n", dev);
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->sz = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+ st->fps = prm->fps;
+
+ /*
+ * avformat_open_input() was added in lavf 53.2.0 according to
+ * ffmpeg/doc/APIchanges
+ */
+
+#if LIBAVFORMAT_VERSION_INT >= ((52<<16) + (110<<8) + 0)
+ (void)fmt;
+ ret = avformat_open_input(&st->ic, dev, NULL, NULL);
+ if (ret < 0) {
+ warning("avformat: avformat_open_input(%s) failed (ret=%d)\n",
+ dev, ret);
+ err = ENOENT;
+ goto out;
+ }
+#else
+
+ /* Params */
+ memset(&prms, 0, sizeof(prms));
+
+ prms.time_base = (AVRational){1, prm->fps};
+ prms.channels = 1;
+ prms.width = size->w;
+ prms.height = size->h;
+ prms.pix_fmt = AV_PIX_FMT_YUV420P;
+ prms.channel = 0;
+
+ ret = av_open_input_file(&st->ic, dev, av_find_input_format(fmt),
+ 0, &prms);
+ if (ret < 0) {
+ warning("avformat: av_open_input_file(%s) failed (ret=%d)\n",
+ dev, ret);
+ err = ENOENT;
+ goto out;
+ }
+#endif
+
+
+#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (4<<8) + 0)
+ ret = avformat_find_stream_info(st->ic, NULL);
+#else
+ ret = av_find_stream_info(st->ic);
+#endif
+
+ if (ret < 0) {
+ warning("avformat: %s: no stream info\n", dev);
+ err = ENOENT;
+ goto out;
+ }
+
+#if 0
+ dump_format(st->ic, 0, dev, 0);
+#endif
+
+ for (i=0; i<st->ic->nb_streams; i++) {
+ const struct AVStream *strm = st->ic->streams[i];
+ AVCodecContext *ctx;
+ double dfps;
+
+#if LIBAVFORMAT_VERSION_INT >= ((57<<16) + (33<<8) + 100)
+
+ ctx = avcodec_alloc_context3(NULL);
+ if (!ctx) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ret = avcodec_parameters_to_context(ctx, strm->codecpar);
+ if (ret < 0) {
+ warning("avformat: avcodec_parameters_to_context\n");
+ err = EPROTO;
+ goto out;
+ }
+
+#else
+ ctx = strm->codec;
+#endif
+
+ if (ctx->codec_type != AVMEDIA_TYPE_VIDEO)
+ continue;
+
+ debug("avformat: stream %u: %u x %u "
+ " time_base=%d/%d\n",
+ i, ctx->width, ctx->height,
+ strm->time_base.num, strm->time_base.den);
+
+ st->sz.w = ctx->width;
+ st->sz.h = ctx->height;
+ st->ctx = ctx;
+ st->sindex = strm->index;
+ st->time_base = strm->time_base;
+
+ dfps = av_q2d(strm->avg_frame_rate);
+ input_fps = (int)dfps;
+ if (st->fps != input_fps) {
+ info("avformat: updating %i fps from config to native "
+ "input material fps %i\n", st->fps, input_fps);
+ st->fps = input_fps;
+#if LIBAVFORMAT_VERSION_INT < ((52<<16) + (110<<8) + 0)
+ prms.time_base = (AVRational){1, st->fps};
+#endif
+ }
+
+ if (ctx->codec_id != AV_CODEC_ID_NONE) {
+
+ st->codec = avcodec_find_decoder(ctx->codec_id);
+ if (!st->codec) {
+ err = ENOENT;
+ goto out;
+ }
+
+#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0)
+ ret = avcodec_open2(ctx, st->codec, NULL);
+#else
+ ret = avcodec_open(ctx, st->codec);
+#endif
+ if (ret < 0) {
+ err = ENOENT;
+ goto out;
+ }
+ }
+
+ found_stream = true;
+ break;
+ }
+
+ if (!found_stream) {
+ err = ENOENT;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ /* register all codecs, demux and protocols */
+ avcodec_register_all();
+ avdevice_register_all();
+
+#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (13<<8) + 0)
+ avformat_network_init();
+#endif
+
+ av_register_all();
+
+ return vidsrc_register(&mod_avf, baresip_vidsrcl(),
+ "avformat", alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ mod_avf = mem_deref(mod_avf);
+
+#if LIBAVFORMAT_VERSION_INT >= ((53<<16) + (13<<8) + 0)
+ avformat_network_deinit();
+#endif
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(avformat) = {
+ "avformat",
+ "vidsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/avformat/module.mk b/modules/avformat/module.mk
new file mode 100644
index 0000000..07d9c9e
--- /dev/null
+++ b/modules/avformat/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := avformat
+$(MOD)_SRCS += avformat.c
+$(MOD)_LFLAGS += -lavdevice -lavformat -lavcodec -lavutil
+CFLAGS += -DUSE_AVFORMAT
+
+include mk/mod.mk
diff --git a/modules/b2bua/b2bua.c b/modules/b2bua/b2bua.c
new file mode 100644
index 0000000..7386a29
--- /dev/null
+++ b/modules/b2bua/b2bua.c
@@ -0,0 +1,246 @@
+/**
+ * @file b2bua.c Back-to-Back User-Agent (B2BUA) module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup b2bua b2bua
+ *
+ * Back-to-Back User-Agent (B2BUA) module
+ *
+ * NOTE: This module is experimental.
+ *
+ * N session objects
+ * 1 session object has 2 call objects (left, right leg)
+ */
+
+
+struct session {
+ struct le le;
+ struct call *call_in, *call_out;
+};
+
+
+static struct list sessionl;
+static struct ua *ua_in, *ua_out;
+
+
+static struct call *other_call(struct session *sess, const struct call *call)
+{
+ if (sess->call_in == call) return sess->call_out;
+ if (sess->call_out == call) return sess->call_in;
+
+ return NULL;
+}
+
+
+static void destructor(void *arg)
+{
+ struct session *sess = arg;
+
+ debug("b2bua: session destroyed (in=%p, out=%p)\n",
+ sess->call_in, sess->call_out);
+
+ list_unlink(&sess->le);
+ mem_deref(sess->call_out);
+ mem_deref(sess->call_in);
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *str, void *arg)
+{
+ struct session *sess = arg;
+ struct call *call2 = other_call(sess, call);
+
+ switch (ev) {
+
+ case CALL_EVENT_ESTABLISHED:
+ debug("b2bua: CALL_ESTABLISHED: peer_uri=%s\n",
+ call_peeruri(call));
+ ua_answer(call_get_ua(call2), call2);
+ break;
+
+ case CALL_EVENT_CLOSED:
+ debug("b2bua: CALL_CLOSED: %s\n", str);
+
+ mem_ref(call2);
+
+ ua_hangup(call_get_ua(call2), call2, call_scode(call), "");
+ mem_deref(sess);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void call_dtmf_handler(struct call *call, char key, void *arg)
+{
+ struct session *sess = arg;
+
+ debug("b2bua: relaying DTMF event: key = '%c'\n", key ? key : '.');
+
+ call_send_digit(other_call(sess, call), key);
+}
+
+
+static int new_session(struct call *call)
+{
+ struct session *sess;
+ char a[64], b[64];
+ int err;
+
+ sess = mem_zalloc(sizeof(*sess), destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->call_in = call;
+ err = ua_connect(ua_out, &sess->call_out, call_peeruri(call),
+ call_localuri(call), NULL,
+ call_has_video(call) ? VIDMODE_ON : VIDMODE_OFF);
+ if (err) {
+ warning("b2bua: ua_connect failed (%m)\n", err);
+ goto out;
+ }
+
+ re_snprintf(a, sizeof(a), "A-%x", sess);
+ re_snprintf(b, sizeof(b), "B-%x", sess);
+
+ /* connect the audio/video-bridge devices */
+ audio_set_devicename(call_audio(sess->call_in), a, b);
+ audio_set_devicename(call_audio(sess->call_out), b, a);
+ video_set_devicename(call_video(sess->call_in), a, b);
+ video_set_devicename(call_video(sess->call_out), b, a);
+
+ call_set_handlers(sess->call_in, call_event_handler,
+ call_dtmf_handler, sess);
+ call_set_handlers(sess->call_out, call_event_handler,
+ call_dtmf_handler, sess);
+
+ list_append(&sessionl, &sess->le, sess);
+
+ out:
+ if (err)
+ mem_deref(sess);
+
+ return err;
+}
+
+
+static void ua_event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ int err;
+ (void)prm;
+ (void)arg;
+
+ switch (ev) {
+
+ case UA_EVENT_CALL_INCOMING:
+ debug("b2bua: CALL_INCOMING: peer=%s --> local=%s\n",
+ call_peeruri(call), call_localuri(call));
+
+ err = new_session(call);
+ if (err) {
+ ua_hangup(ua, call, 500, "Server Error");
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int b2bua_status(struct re_printf *pf, void *arg)
+{
+ struct le *le;
+ int err = 0;
+ (void)arg;
+
+ err |= re_hprintf(pf, "B2BUA status:\n");
+ err |= re_hprintf(pf, " inbound: %s\n", ua_aor(ua_in));
+ err |= re_hprintf(pf, " outbound: %s\n", ua_aor(ua_out));
+
+ err |= re_hprintf(pf, "sessions:\n");
+
+ for (le = sessionl.head; le; le = le->next) {
+
+ struct session *sess = le->data;
+
+ err |= re_hprintf(pf, "%-42s ---> %42s\n",
+ call_peeruri(sess->call_in),
+ call_peeruri(sess->call_out));
+
+ err |= re_hprintf(pf, " %H\n", call_status, sess->call_in);
+ err |= re_hprintf(pf, " %H\n", call_status, sess->call_out);
+ }
+
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+ {"b2bua", 0, 0, "b2bua status", b2bua_status },
+};
+
+
+static int module_init(void)
+{
+ int err;
+
+ ua_in = uag_find_param("b2bua", "inbound");
+ ua_out = uag_find_param("b2bua", "outbound");
+
+ if (!ua_in) {
+ warning("b2bua: inbound UA not found\n");
+ return ENOENT;
+ }
+ if (!ua_out) {
+ warning("b2bua: outbound UA not found\n");
+ return ENOENT;
+ }
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ return err;
+
+ err = uag_event_register(ua_event_handler, 0);
+ if (err)
+ return err;
+
+ debug("b2bua: module loaded\n");
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ debug("b2bua: module closing..\n");
+
+ if (!list_isempty(&sessionl)) {
+
+ info("b2bua: flushing %u sessions\n", list_count(&sessionl));
+ list_flush(&sessionl);
+ }
+
+ uag_event_unregister(ua_event_handler);
+ cmd_unregister(baresip_commands(), cmdv);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(b2bua) = {
+ "b2bua",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/b2bua/module.mk b/modules/b2bua/module.mk
new file mode 100644
index 0000000..8afe230
--- /dev/null
+++ b/modules/b2bua/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := b2bua
+$(MOD)_SRCS += b2bua.c
+
+include mk/mod.mk
diff --git a/modules/bv32/bv32.c b/modules/bv32/bv32.c
new file mode 100644
index 0000000..d3dd45c
--- /dev/null
+++ b/modules/bv32/bv32.c
@@ -0,0 +1,180 @@
+/**
+ * @file bv32.c BroadVoice32 audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <bv32/bv32.h>
+#include <bv32/bitpack.h>
+
+
+/*
+ * BroadVoice32 Wideband Audio codec (RFC 4298)
+ *
+ * http://www.broadcom.com/support/broadvoice/downloads.php
+ * http://files.freeswitch.org/downloads/libs/libbv32-0.1.tar.gz
+ */
+
+
+enum {
+ NSAMP = 80,
+ CODED_OCTETS = 20
+};
+
+
+struct auenc_state {
+ struct BV32_Encoder_State cs;
+ struct BV32_Bit_Stream bsc;
+};
+
+struct audec_state {
+ struct BV32_Decoder_State ds;
+ struct BV32_Bit_Stream bsd;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ Reset_BV32_Coder(&st->cs);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ Reset_BV32_Decoder(&st->ds);
+}
+
+
+static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ Reset_BV32_Coder(&st->cs);
+
+ *aesp = st;
+
+ return 0;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ Reset_BV32_Decoder(&st->ds);
+
+ *adsp = st;
+
+ return 0;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ size_t i, nframe;
+
+ nframe = sampc / NSAMP;
+
+ if (*len < nframe * CODED_OCTETS)
+ return ENOMEM;
+
+ for (i=0; i<nframe; i++) {
+ BV32_Encode(&st->bsc, &st->cs, (short *)&sampv[i*NSAMP]);
+ BV32_BitPack((void *)&buf[i*CODED_OCTETS], &st->bsc);
+ }
+
+ *len = CODED_OCTETS * nframe;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ size_t i, nframe;
+
+ nframe = len / CODED_OCTETS;
+
+ if (*sampc < NSAMP*nframe)
+ return ENOMEM;
+
+ for (i=0; i<nframe; i++) {
+ BV32_BitUnPack((void *)&buf[i*CODED_OCTETS], &st->bsd);
+ BV32_Decode(&st->bsd, &st->ds, (short *)&sampv[i*NSAMP]);
+ }
+
+ *sampc = NSAMP * nframe;
+
+ return 0;
+}
+
+
+static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ BV32_PLC(&st->ds, sampv);
+ *sampc = NSAMP;
+
+ return 0;
+}
+
+
+static struct aucodec bv32 = {
+ LE_INIT, 0, "BV32", 16000, 16000, 1, NULL,
+ encode_update, encode,
+ decode_update, decode, plc,
+ NULL, NULL
+};
+
+
+static int module_init(void)
+{
+ aucodec_register(baresip_aucodecl(), &bv32);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&bv32);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(bv32) = {
+ "bv32",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/bv32/module.mk b/modules/bv32/module.mk
new file mode 100644
index 0000000..2e842ff
--- /dev/null
+++ b/modules/bv32/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := bv32
+$(MOD)_SRCS += bv32.c
+$(MOD)_LFLAGS += -lbv32 -lm
+
+include mk/mod.mk
diff --git a/modules/cairo/cairo.c b/modules/cairo/cairo.c
new file mode 100644
index 0000000..2af2a12
--- /dev/null
+++ b/modules/cairo/cairo.c
@@ -0,0 +1,345 @@
+/**
+ * @file cairo.c Cairo module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <pthread.h>
+#include <math.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <cairo/cairo.h>
+
+
+#if !defined (M_PI)
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+
+/**
+ * @defgroup cairo cairo
+ *
+ * Cairo video-source module is a video generator for testing
+ * and demo purposes.
+ *
+ * Note: This module is very experimental!
+ *
+ * Use Cairo library to draw graphics into a frame buffer
+ */
+
+
+enum {
+ FONT_SIZE = 18
+};
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ struct vidsrc_prm prm;
+ struct vidsz size;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ cairo_surface_t *surface_logo;
+ cairo_t *cr_logo;
+ double logo_width;
+ double logo_height;
+ double step;
+ bool run;
+ pthread_t thread;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->cr)
+ cairo_destroy(st->cr);
+ if (st->surface)
+ cairo_surface_destroy(st->surface);
+
+ if (st->cr_logo)
+ cairo_destroy(st->cr_logo);
+ if (st->surface_logo)
+ cairo_surface_destroy(st->surface_logo);
+}
+
+
+static void draw_background(cairo_t *cr, double color_step,
+ int width, int height)
+{
+ cairo_pattern_t *pat;
+ double grey, r, g, b;
+
+ grey = 0.1 + fabs(sin(3 * color_step));
+ r = grey;
+ g = grey;
+ b = grey;
+
+ pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
+ cairo_pattern_add_color_stop_rgba (pat, 1, r, g, b, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 0, 0, 0, 0, 1);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_set_source (cr, pat);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+}
+
+
+static void draw_text(struct vidsrc_st *st, int x, int y,
+ const char *fmt, ...)
+{
+ char buf[4096] = "";
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ cairo_set_source_rgb(st->cr, 1.0, 1.0, 1.0); /* white */
+
+ cairo_set_font_size(st->cr, FONT_SIZE);
+ cairo_move_to(st->cr, x, y);
+ cairo_show_text(st->cr, buf);
+}
+
+
+static void draw_logo(struct vidsrc_st *st)
+{
+ double x, y;
+
+ x = (st->size.w - st->logo_width) * (sin(10 * st->step) + 1)/2;
+ y = (st->size.h - st->logo_height)* (1 - fabs(sin(30 * st->step)));
+
+ cairo_set_source_surface(st->cr, st->surface_logo, x, y);
+ cairo_paint(st->cr);
+}
+
+
+static void process(struct vidsrc_st *st)
+{
+ struct vidframe f;
+ unsigned xoffs = 2, yoffs = 24;
+
+ draw_background(st->cr, st->step, st->size.w, st->size.h);
+
+ draw_text(st, xoffs, yoffs + FONT_SIZE, "%H", fmt_gmtime, NULL);
+
+ draw_text(st, xoffs, yoffs + FONT_SIZE*2, "%u x %u @ %d fps",
+ st->size.w, st->size.h, st->prm.fps);
+
+ draw_logo(st);
+
+ st->step += 0.02 / st->prm.fps;
+
+ vidframe_init_buf(&f, VID_FMT_RGB32, &st->size,
+ cairo_image_surface_get_data(st->surface));
+
+ st->frameh(&f, st->arg);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ uint64_t ts = 0;
+
+ while (st->run) {
+
+ uint64_t now;
+
+ sys_msleep(2);
+
+ now = tmr_jiffies();
+ if (!ts)
+ ts = now;
+
+ if (ts > now)
+ continue;
+
+ process(st);
+
+ ts += 1000/st->prm.fps;
+ }
+
+ return NULL;
+}
+
+
+static int load_logo(struct vidsrc_st *st, const char *filename)
+{
+ cairo_surface_t *logo;
+ double lw;
+ double scale;
+ int err = 0;
+
+ logo = cairo_image_surface_create_from_png(filename);
+ if (!logo) {
+ warning("cairo: failed to load PNG logo\n");
+ err = ENOENT;
+ goto out;
+ }
+
+ if (!cairo_image_surface_get_width(logo) ||
+ !cairo_image_surface_get_height(logo)) {
+ warning("cairo: invalid logo (%s)\n", filename);
+ err = ENOENT;
+ goto out;
+ }
+
+ st->logo_width = st->size.w / 2;
+ lw = cairo_image_surface_get_width(logo);
+ scale = (double)st->logo_width / (double)lw;
+
+ st->logo_height = cairo_image_surface_get_height(logo) * scale;
+
+ /* create a scaled-down logo with same aspect ratio */
+
+ st->surface_logo = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ st->logo_width,
+ st->logo_height);
+ if (!st->surface_logo) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->cr_logo = cairo_create(st->surface_logo);
+ if (!st->cr_logo) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ cairo_scale(st->cr_logo, scale, scale);
+
+ cairo_set_source_surface(st->cr_logo, logo, 0, 0);
+ cairo_paint(st->cr_logo);
+
+ info("cairo: scaling logo '%s' from %d x %d to %f x %f\n",
+ filename,
+ cairo_image_surface_get_width(logo),
+ cairo_image_surface_get_height(logo),
+ st->logo_width,
+ st->logo_height);
+
+ out:
+ if (logo)
+ cairo_surface_destroy(logo);
+ return err;
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct config *cfg;
+ struct vidsrc_st *st;
+ char logo[256];
+ int err = 0;
+
+ (void)ctx;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !prm || !size || !frameh)
+ return EINVAL;
+
+ cfg = conf_config();
+ if (!cfg)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->frameh = frameh;
+ st->arg = arg;
+ st->prm = *prm;
+ st->size = *size;
+
+ st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ size->w, size->h);
+ if (!st->surface) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->cr = cairo_create(st->surface);
+ if (!st->cr) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ cairo_select_font_face(st->cr, "Sans",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+
+ info("cairo: surface with format %d (%d x %d) stride=%d\n",
+ cairo_image_surface_get_format(st->surface),
+ cairo_image_surface_get_width(st->surface),
+ cairo_image_surface_get_height(st->surface),
+ cairo_image_surface_get_stride(st->surface));
+
+ st->step = rand_u16() / 1000.0;
+
+ re_snprintf(logo, sizeof(logo), "%s/logo.png", cfg->audio.audio_path);
+
+ err = load_logo(st, logo);
+ if (err) {
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "cairo", alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(cairo) = {
+ "cairo",
+ "vidsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/cairo/module.mk b/modules/cairo/module.mk
new file mode 100644
index 0000000..13f1632
--- /dev/null
+++ b/modules/cairo/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := cairo
+$(MOD)_SRCS += cairo.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs cairo)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags cairo)
+
+include mk/mod.mk
diff --git a/modules/codec2/codec2.c b/modules/codec2/codec2.c
new file mode 100644
index 0000000..b6911dc
--- /dev/null
+++ b/modules/codec2/codec2.c
@@ -0,0 +1,202 @@
+/**
+ * @file codec2.c CODEC2 audio codec
+ *
+ * Copyright (C) 2015 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <codec2/codec2.h>
+
+
+/**
+ * @defgroup codec2 codec2
+ *
+ * The CODEC2 audio codec
+ *
+ * https://en.wikipedia.org/wiki/Codec2
+ */
+
+
+enum {
+ CODEC2_MODE = CODEC2_MODE_2400
+};
+
+
+struct auenc_state {
+ struct CODEC2 *c2;
+};
+
+struct audec_state {
+ struct CODEC2 *c2;
+};
+
+
+static void encode_destructor(void *data)
+{
+ struct auenc_state *st = data;
+
+ if (st->c2)
+ codec2_destroy(st->c2);
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->c2 = codec2_create(CODEC2_MODE);
+ if (!st->c2) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ info("codec2: %d samples per frame, %d bits per frame\n",
+ codec2_samples_per_frame(st->c2),
+ codec2_bits_per_frame(st->c2));
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static void decode_destructor(void *data)
+{
+ struct audec_state *st = data;
+
+ if (st->c2)
+ codec2_destroy(st->c2);
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->c2 = codec2_create(CODEC2_MODE);
+ if (!st->c2) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *aes, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < (size_t)codec2_bits_per_frame(aes->c2)/8)
+ return ENOMEM;
+ if (sampc != (size_t)codec2_samples_per_frame(aes->c2))
+ return EPROTO;
+
+ codec2_encode(aes->c2, buf, (short *)sampv);
+
+ *len = codec2_bits_per_frame(aes->c2)/8;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *ads, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ if (!sampv || !sampc || !buf)
+ return EINVAL;
+
+ if (*sampc < (size_t)codec2_samples_per_frame(ads->c2))
+ return ENOMEM;
+ if (len < (size_t)codec2_bits_per_frame(ads->c2)/8)
+ return EPROTO;
+
+ codec2_decode(ads->c2, sampv, buf);
+
+ *sampc = codec2_samples_per_frame(ads->c2);
+
+ return 0;
+}
+
+
+static struct aucodec codec2 = {
+ LE_INIT,
+ NULL,
+ "CODEC2",
+ 8000,
+ 8000,
+ 1,
+ NULL,
+ encode_update,
+ encode,
+ decode_update,
+ decode,
+ NULL,
+ NULL,
+ NULL
+};
+
+
+static int module_init(void)
+{
+ aucodec_register(baresip_aucodecl(), &codec2);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&codec2);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(codec2) = {
+ "codec2",
+ "audio codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/codec2/module.mk b/modules/codec2/module.mk
new file mode 100644
index 0000000..2d4d5d8
--- /dev/null
+++ b/modules/codec2/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := codec2
+$(MOD)_SRCS += codec2.c
+$(MOD)_LFLAGS += -lcodec2
+
+include mk/mod.mk
diff --git a/modules/cons/cons.c b/modules/cons/cons.c
new file mode 100644
index 0000000..fc80f81
--- /dev/null
+++ b/modules/cons/cons.c
@@ -0,0 +1,274 @@
+/**
+ * @file cons.c Socket-based command-line console
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup cons cons
+ *
+ * Console User-Interface (UI) using UDP/TCP sockets
+ *
+ *
+ * This module implements a simple console for connecting to Baresip via
+ * UDP or TCP-based sockets. You can use programs like telnet or netcat to
+ * connect to the command-line interface.
+ *
+ * Example, with the cons-module listening on default port 5555:
+ *
+ \verbatim
+ $ netcat -u 127.0.0.1 5555
+ \endverbatim
+ *
+ * The following options can be configured:
+ *
+ \verbatim
+ cons_listen 0.0.0.0:5555 # IP-address and port to listen on
+ \endverbatim
+ */
+
+
+enum {CONS_PORT = 5555};
+
+struct ui_st {
+ struct udp_sock *us;
+ struct tcp_sock *ts;
+ struct tcp_conn *tc;
+ struct sa udp_peer;
+};
+
+
+static struct ui_st *cons = NULL; /* allow only one instance */
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ return mbuf_write_mem(arg, (uint8_t *)p, size);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct ui_st *st = arg;
+ struct mbuf *mbr = mbuf_alloc(64);
+ struct re_printf pf;
+
+ st->udp_peer = *src;
+
+ pf.vph = print_handler;
+ pf.arg = mbr;
+
+ while (mbuf_get_left(mb)) {
+ char ch = mbuf_read_u8(mb);
+
+ if (ch == '\r')
+ ch = '\n';
+
+ ui_input_key(baresip_uis(), ch, &pf);
+ }
+
+ if (mbr->end > 0) {
+ mbr->pos = 0;
+ (void)udp_send(st->us, src, mbr);
+ }
+
+ mem_deref(mbr);
+}
+
+
+static void cons_destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ mem_deref(st->us);
+ mem_deref(st->tc);
+ mem_deref(st->ts);
+}
+
+
+static int tcp_write_handler(const char *p, size_t size, void *arg)
+{
+ struct mbuf mb;
+
+ mb.buf = (uint8_t *)p;
+ mb.pos = 0;
+ mb.end = mb.size = size;
+
+ return tcp_send(arg, &mb);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct ui_st *st = arg;
+ struct re_printf pf;
+
+ pf.vph = tcp_write_handler;
+ pf.arg = st->tc;
+
+ while (mbuf_get_left(mb) > 0) {
+
+ char ch = mbuf_read_u8(mb);
+
+ if (ch == '\r')
+ ch = '\n';
+
+ ui_input_key(baresip_uis(), ch, &pf);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct ui_st *st = arg;
+
+ (void)err;
+
+ st->tc = mem_deref(st->tc);
+}
+
+
+static void tcp_conn_handler(const struct sa *peer, void *arg)
+{
+ struct ui_st *st = arg;
+
+ (void)peer;
+
+ /* only one connection allowed */
+ st->tc = mem_deref(st->tc);
+ (void)tcp_accept(&st->tc, st->ts, NULL, tcp_recv_handler,
+ tcp_close_handler, st);
+}
+
+
+static int cons_alloc(struct ui_st **stp, const struct sa *laddr)
+{
+ struct ui_st *st;
+ int err;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), cons_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = udp_listen(&st->us, laddr, udp_recv, st);
+ if (err) {
+ warning("cons: failed to listen on UDP %J (%m)\n",
+ laddr, err);
+ goto out;
+ }
+
+ err = tcp_listen(&st->ts, laddr, tcp_conn_handler, st);
+ if (err) {
+ warning("cons: failed to listen on TCP %J (%m)\n",
+ laddr, err);
+ goto out;
+ }
+
+ debug("cons: UI console listening on %J\n", laddr);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int output_handler(const char *str)
+{
+ struct mbuf *mb;
+ int err = 0;
+
+ if (!str)
+ return EINVAL;
+
+ mb = mbuf_alloc(256);
+ if (!mb)
+ return ENOMEM;
+
+ mbuf_write_str(mb, str);
+
+ if (sa_isset(&cons->udp_peer, SA_ALL)) {
+ mb->pos = 0;
+ err |= udp_send(cons->us, &cons->udp_peer, mb);
+ }
+
+ if (cons->tc) {
+ mb->pos = 0;
+ err |= tcp_send(cons->tc, mb);
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/*
+ * Relay log-messages to all active UDP/TCP connections
+ */
+static void log_handler(uint32_t level, const char *msg)
+{
+ (void)level;
+
+ output_handler(msg);
+}
+
+
+static struct ui ui_cons = {
+ LE_INIT,
+ "cons",
+ output_handler
+};
+
+
+static struct log lg = {
+ .h = log_handler,
+};
+
+
+static int cons_init(void)
+{
+ struct sa laddr;
+ int err;
+
+ if (conf_get_sa(conf_cur(), "cons_listen", &laddr)) {
+ sa_set_str(&laddr, "0.0.0.0", CONS_PORT);
+ }
+
+ err = cons_alloc(&cons, &laddr);
+ if (err)
+ return err;
+
+ ui_register(baresip_uis(), &ui_cons);
+
+ log_register_handler(&lg);
+
+ return 0;
+}
+
+
+static int cons_close(void)
+{
+ log_unregister_handler(&lg);
+
+ ui_unregister(&ui_cons);
+ cons = mem_deref(cons);
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(cons) = {
+ "cons",
+ "ui",
+ cons_init,
+ cons_close
+};
diff --git a/modules/cons/module.mk b/modules/cons/module.mk
new file mode 100644
index 0000000..1857dee
--- /dev/null
+++ b/modules/cons/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := cons
+$(MOD)_SRCS += cons.c
+
+include mk/mod.mk
diff --git a/modules/contact/contact.c b/modules/contact/contact.c
new file mode 100644
index 0000000..2bb506d
--- /dev/null
+++ b/modules/contact/contact.c
@@ -0,0 +1,255 @@
+/**
+ * @file modules/contact/contact.c Contacts module
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup contact contact
+ *
+ * Contact module reading contacts from a file
+ *
+ * - read contact entries from ~/.baresip/contacts
+ * - populate local database of contacts
+ */
+
+
+static const char *chat_peer; /**< Selected chat peer */
+static char cmd_desc[128] = "Send MESSAGE to peer";
+
+
+static int confline_handler(const struct pl *addr, void *arg)
+{
+ struct contacts *contacts = arg;
+ return contact_add(contacts, NULL, addr);
+}
+
+
+static int cmd_contact(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct contacts *contacts = baresip_contacts();
+ struct contact *cnt = NULL;
+ struct pl dname, user, pl;
+ struct le *le;
+ int err = 0;
+
+ pl_set_str(&pl, carg->prm);
+
+ dname.l = user.l = pl.l;
+
+ err |= re_hprintf(pf, "\n");
+
+ for (le = list_head(contact_list(contacts)); le; le = le->next) {
+
+ struct contact *c = le->data;
+
+ dname.p = contact_addr(c)->dname.p;
+ user.p = contact_addr(c)->uri.user.p;
+
+ /* if displayname is set, try to match the displayname
+ * otherwise we try to match the username only
+ */
+ if (dname.p) {
+
+ if (0 == pl_casecmp(&dname, &pl)) {
+ err |= re_hprintf(pf, "%s\n", contact_str(c));
+ cnt = c;
+ }
+ }
+ else if (user.p) {
+
+ if (0 == pl_casecmp(&user, &pl)) {
+ err |= re_hprintf(pf, "%s\n", contact_str(c));
+ cnt = c;
+ }
+ }
+ }
+
+ if (!cnt)
+ err |= re_hprintf(pf, "(no matches)\n");
+
+ if (carg->complete && cnt) {
+
+ switch (carg->key) {
+
+ case '|':
+ err = ua_connect(uag_current(), NULL, NULL,
+ contact_str(cnt), NULL, VIDMODE_ON);
+ if (err) {
+ warning("contact: ua_connect failed: %m\n",
+ err);
+ }
+ break;
+
+ case '=':
+ chat_peer = contact_str(cnt);
+ (void)re_hprintf(pf, "Selected chat peer: %s\n",
+ chat_peer);
+ re_snprintf(cmd_desc, sizeof(cmd_desc),
+ "Send MESSAGE to %s", chat_peer);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static int print_contacts(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return contacts_print(pf, baresip_contacts());
+}
+
+
+static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ (void)arg;
+
+ if (err) {
+ (void)re_fprintf(stderr, " \x1b[31m%m\x1b[;m\n", err);
+ return;
+ }
+
+ if (msg->scode >= 300) {
+ (void)re_fprintf(stderr, " \x1b[31m%u %r\x1b[;m\n",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+static int cmd_message(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ int err;
+
+ if (!str_isset(chat_peer)) {
+ return re_hprintf(pf, "contact: chat peer is not set\n");
+ }
+
+ err = message_send(uag_current(), chat_peer, carg->prm,
+ send_resp_handler, NULL);
+ if (err) {
+ (void)re_hprintf(pf, "contact: message_send() failed (%m)\n",
+ err);
+ }
+
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+{"dialcontact", '|', CMD_IPRM, "Dial from contacts", cmd_contact },
+{"chatpeer", '=', CMD_IPRM, "Select chat peer", cmd_contact },
+{"contacts", 'C', 0, "List contacts", print_contacts },
+{"message", '-', CMD_PRM, cmd_desc, cmd_message },
+};
+
+
+static int write_template(const char *file)
+{
+ const char *user, *domain;
+ FILE *f = NULL;
+
+ info("contact: creating contacts template %s\n", file);
+
+ f = fopen(file, "w");
+ if (!f)
+ return errno;
+
+ user = sys_username();
+ if (!user)
+ user = "user";
+ domain = net_domain(baresip_network());
+ if (!domain)
+ domain = "domain";
+
+ (void)re_fprintf(f,
+ "#\n"
+ "# SIP contacts\n"
+ "#\n"
+ "# Displayname <sip:user@domain>;addr-params\n"
+ "#\n"
+ "# addr-params:\n"
+ "# ;presence={none,p2p}\n"
+ "# ;access={allow,block}\n"
+ "#\n"
+ "\n"
+ "\n"
+ "\"Echo Server\" <sip:echo@creytiv.com>\n"
+ "\"%s\" <sip:%s@%s>;presence=p2p\n"
+ "\n"
+ "# Access rules\n"
+ "#\"Catch All\" <sip:*@*>;access=block\n"
+ "\"Good Friend\" <sip:good@friend.com>;access=allow\n"
+ "\n"
+ ,
+ user, user, domain);
+
+ if (f)
+ (void)fclose(f);
+
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ struct contacts *contacts = baresip_contacts();
+ char path[256] = "", file[256] = "";
+ int err;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err)
+ return err;
+
+ if (re_snprintf(file, sizeof(file), "%s/contacts", path) < 0)
+ return ENOMEM;
+
+ if (!conf_fileexist(file)) {
+
+ (void)fs_mkdir(path, 0700);
+
+ err = write_template(file);
+ if (err)
+ return err;
+ }
+
+ err = conf_parse(file, confline_handler, contacts);
+ if (err)
+ return err;
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ return err;
+
+ info("Populated %u contacts\n",
+ list_count(contact_list(contacts)));
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ cmd_unregister(baresip_commands(), cmdv);
+ list_flush(contact_list(baresip_contacts()));
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(contact) = {
+ "contact",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/contact/module.mk b/modules/contact/module.mk
new file mode 100644
index 0000000..e9361c8
--- /dev/null
+++ b/modules/contact/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := contact
+$(MOD)_SRCS += contact.c
+
+include mk/mod.mk
diff --git a/modules/coreaudio/coreaudio.c b/modules/coreaudio/coreaudio.c
new file mode 100644
index 0000000..e1af629
--- /dev/null
+++ b/modules/coreaudio/coreaudio.c
@@ -0,0 +1,111 @@
+/**
+ * @file coreaudio.c Apple Coreaudio sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioToolbox/AudioToolbox.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "coreaudio.h"
+
+
+/**
+ * @defgroup coreaudio coreaudio
+ *
+ * Audio driver module for OSX CoreAudio
+ */
+
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_2_0
+static void interruptionListener(void *data, UInt32 inInterruptionState)
+{
+ (void)data;
+
+ /* XXX: implement this properly */
+
+ if (inInterruptionState == kAudioSessionBeginInterruption) {
+ debug("coreaudio: player interrupt: Begin\n");
+ }
+ else if (inInterruptionState == kAudioSessionEndInterruption) {
+ debug("coreaudio: player interrupt: End\n");
+ }
+}
+
+
+int audio_session_enable(void)
+{
+ OSStatus res;
+ UInt32 category;
+
+ res = AudioSessionInitialize(NULL, NULL, interruptionListener, 0);
+ if (res && res != 1768843636)
+ return ENODEV;
+
+ category = kAudioSessionCategory_PlayAndRecord;
+ res = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
+ sizeof(category), &category);
+ if (res) {
+ warning("coreaudio: Audio Category: %d\n", res);
+ return ENODEV;
+ }
+
+ res = AudioSessionSetActive(true);
+ if (res) {
+ warning("coreaudio: AudioSessionSetActive: %d\n", res);
+ return ENODEV;
+ }
+
+ return 0;
+}
+
+
+void audio_session_disable(void)
+{
+ AudioSessionSetActive(false);
+}
+#else
+int audio_session_enable(void)
+{
+ return 0;
+}
+
+
+void audio_session_disable(void)
+{
+}
+#endif
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = auplay_register(&auplay, baresip_auplayl(),
+ "coreaudio", coreaudio_player_alloc);
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "coreaudio", coreaudio_recorder_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ auplay = mem_deref(auplay);
+ ausrc = mem_deref(ausrc);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(coreaudio) = {
+ "coreaudio",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/coreaudio/coreaudio.h b/modules/coreaudio/coreaudio.h
new file mode 100644
index 0000000..a4d4edd
--- /dev/null
+++ b/modules/coreaudio/coreaudio.h
@@ -0,0 +1,18 @@
+/**
+ * @file coreaudio.h Apple Coreaudio sound driver -- internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+int audio_session_enable(void);
+void audio_session_disable(void);
+
+
+int coreaudio_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int coreaudio_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/coreaudio/module.mk b/modules/coreaudio/module.mk
new file mode 100644
index 0000000..35d51cf
--- /dev/null
+++ b/modules/coreaudio/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := coreaudio
+$(MOD)_SRCS += coreaudio.c
+$(MOD)_SRCS += player.c
+$(MOD)_SRCS += recorder.c
+$(MOD)_LFLAGS += -framework CoreAudio -framework AudioToolbox
+
+include mk/mod.mk
diff --git a/modules/coreaudio/player.c b/modules/coreaudio/player.c
new file mode 100644
index 0000000..7d247cd
--- /dev/null
+++ b/modules/coreaudio/player.c
@@ -0,0 +1,165 @@
+/**
+ * @file coreaudio/player.c Apple Coreaudio sound driver - player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioToolbox/AudioQueue.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "coreaudio.h"
+
+
+/* This value can be tuned */
+#if TARGET_OS_IPHONE
+#define BUFC 20
+#else
+#define BUFC 6
+#endif
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ AudioQueueRef queue;
+ AudioQueueBufferRef buf[BUFC];
+ pthread_mutex_t mutex;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+ uint32_t i;
+
+ pthread_mutex_lock(&st->mutex);
+ st->wh = NULL;
+ pthread_mutex_unlock(&st->mutex);
+
+ audio_session_disable();
+
+ if (st->queue) {
+ AudioQueuePause(st->queue);
+ AudioQueueStop(st->queue, true);
+
+ for (i=0; i<ARRAY_SIZE(st->buf); i++)
+ if (st->buf[i])
+ AudioQueueFreeBuffer(st->queue, st->buf[i]);
+
+ AudioQueueDispose(st->queue, true);
+ }
+
+ pthread_mutex_destroy(&st->mutex);
+}
+
+
+static void play_handler(void *userData, AudioQueueRef outQ,
+ AudioQueueBufferRef outQB)
+{
+ struct auplay_st *st = userData;
+ auplay_write_h *wh;
+ void *arg;
+
+ pthread_mutex_lock(&st->mutex);
+ wh = st->wh;
+ arg = st->arg;
+ pthread_mutex_unlock(&st->mutex);
+
+ if (!wh)
+ return;
+
+ wh(outQB->mAudioData, outQB->mAudioDataByteSize/2, arg);
+
+ AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL);
+}
+
+
+int coreaudio_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ AudioStreamBasicDescription fmt;
+ struct auplay_st *st;
+ uint32_t sampc, bytc, i;
+ OSStatus status;
+ int err;
+
+ (void)device;
+
+ if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ err = audio_session_enable();
+ if (err)
+ goto out;
+
+ fmt.mSampleRate = (Float64)prm->srate;
+ fmt.mFormatID = kAudioFormatLinearPCM;
+ fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
+ kAudioFormatFlagIsPacked;
+#ifdef __BIG_ENDIAN__
+ fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian;
+#endif
+ fmt.mFramesPerPacket = 1;
+ fmt.mBytesPerFrame = prm->ch * 2;
+ fmt.mBytesPerPacket = prm->ch * 2;
+ fmt.mChannelsPerFrame = prm->ch;
+ fmt.mBitsPerChannel = 16;
+
+ status = AudioQueueNewOutput(&fmt, play_handler, st, NULL,
+ kCFRunLoopCommonModes, 0, &st->queue);
+ if (status) {
+ warning("coreaudio: AudioQueueNewOutput error: %i\n", status);
+ err = ENODEV;
+ goto out;
+ }
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ bytc = sampc * 2;
+
+ for (i=0; i<ARRAY_SIZE(st->buf); i++) {
+
+ status = AudioQueueAllocateBuffer(st->queue, bytc,
+ &st->buf[i]);
+ if (status) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->buf[i]->mAudioDataByteSize = bytc;
+
+ memset(st->buf[i]->mAudioData, 0,
+ st->buf[i]->mAudioDataByteSize);
+
+ (void)AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL);
+ }
+
+ status = AudioQueueStart(st->queue, NULL);
+ if (status) {
+ warning("coreaudio: AudioQueueStart error %i\n", status);
+ err = ENODEV;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/coreaudio/recorder.c b/modules/coreaudio/recorder.c
new file mode 100644
index 0000000..9ceae60
--- /dev/null
+++ b/modules/coreaudio/recorder.c
@@ -0,0 +1,176 @@
+/**
+ * @file coreaudio/recorder.c Apple Coreaudio sound driver - recorder
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <AudioToolbox/AudioQueue.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "coreaudio.h"
+
+
+#define BUFC 3
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ AudioQueueRef queue;
+ AudioQueueBufferRef buf[BUFC];
+ pthread_mutex_t mutex;
+ ausrc_read_h *rh;
+ void *arg;
+ unsigned int ptime;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+ uint32_t i;
+
+ pthread_mutex_lock(&st->mutex);
+ st->rh = NULL;
+ pthread_mutex_unlock(&st->mutex);
+
+ audio_session_disable();
+
+ if (st->queue) {
+ AudioQueuePause(st->queue);
+ AudioQueueStop(st->queue, true);
+
+ for (i=0; i<ARRAY_SIZE(st->buf); i++)
+ if (st->buf[i])
+ AudioQueueFreeBuffer(st->queue, st->buf[i]);
+
+ AudioQueueDispose(st->queue, true);
+ }
+
+ pthread_mutex_destroy(&st->mutex);
+}
+
+
+static void record_handler(void *userData, AudioQueueRef inQ,
+ AudioQueueBufferRef inQB,
+ const AudioTimeStamp *inStartTime,
+ UInt32 inNumPackets,
+ const AudioStreamPacketDescription *inPacketDesc)
+{
+ struct ausrc_st *st = userData;
+ unsigned int ptime;
+ ausrc_read_h *rh;
+ void *arg;
+ (void)inStartTime;
+ (void)inNumPackets;
+ (void)inPacketDesc;
+
+ pthread_mutex_lock(&st->mutex);
+ ptime = st->ptime;
+ rh = st->rh;
+ arg = st->arg;
+ pthread_mutex_unlock(&st->mutex);
+
+ if (!rh)
+ return;
+
+ rh(inQB->mAudioData, inQB->mAudioDataByteSize/2, arg);
+
+ AudioQueueEnqueueBuffer(inQ, inQB, 0, NULL);
+
+ /* Force a sleep here, coreaudio's timing is too fast */
+#if !TARGET_OS_IPHONE
+#define ENCODE_TIME 1000
+ usleep((ptime * 1000) - ENCODE_TIME);
+#endif
+}
+
+
+int coreaudio_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ AudioStreamBasicDescription fmt;
+ struct ausrc_st *st;
+ uint32_t sampc, bytc, i;
+ OSStatus status;
+ int err;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ptime = prm->ptime;
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ bytc = sampc * 2;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ err = audio_session_enable();
+ if (err)
+ goto out;
+
+ fmt.mSampleRate = (Float64)prm->srate;
+ fmt.mFormatID = kAudioFormatLinearPCM;
+ fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
+ kAudioFormatFlagIsPacked;
+#ifdef __BIG_ENDIAN__
+ fmt.mFormatFlags |= kAudioFormatFlagIsBigEndian;
+#endif
+
+ fmt.mFramesPerPacket = 1;
+ fmt.mBytesPerFrame = prm->ch * 2;
+ fmt.mBytesPerPacket = prm->ch * 2;
+ fmt.mChannelsPerFrame = prm->ch;
+ fmt.mBitsPerChannel = 16;
+
+ status = AudioQueueNewInput(&fmt, record_handler, st, NULL,
+ kCFRunLoopCommonModes, 0, &st->queue);
+ if (status) {
+ warning("coreaudio: AudioQueueNewInput error: %i\n", status);
+ err = ENODEV;
+ goto out;
+ }
+
+ for (i=0; i<ARRAY_SIZE(st->buf); i++) {
+
+ status = AudioQueueAllocateBuffer(st->queue, bytc,
+ &st->buf[i]);
+ if (status) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ AudioQueueEnqueueBuffer(st->queue, st->buf[i], 0, NULL);
+ }
+
+ status = AudioQueueStart(st->queue, NULL);
+ if (status) {
+ warning("coreaudio: AudioQueueStart error %i\n", status);
+ err = ENODEV;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/daala/daala.c b/modules/daala/daala.c
new file mode 100644
index 0000000..3ea7e27
--- /dev/null
+++ b/modules/daala/daala.c
@@ -0,0 +1,63 @@
+/**
+ * @file daala.c Experimental video-codec using Daala
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <daala/codec.h>
+#include "daala.h"
+
+
+/**
+ * @defgroup daala daala
+ *
+ * Very experimental video-codec using Daala
+ *
+ *
+ * External libraries:
+ *
+ * daala version 0.0-1564-g79787c7 (or later)
+ *
+ * References:
+ *
+ * https://wiki.xiph.org/Daala
+ *
+ * NOTE! Now deprecated in favour of AV1 video codec
+ */
+
+
+static struct vidcodec daala = {
+ .name = "daala",
+ .encupdh = daala_encode_update,
+ .ench = daala_encode,
+ .decupdh = daala_decode_update,
+ .dech = daala_decode,
+};
+
+
+static int module_init(void)
+{
+ info("daala: using version '%s'\n", daala_version_string());
+
+ vidcodec_register(baresip_vidcodecl(), &daala);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&daala);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(daala) = {
+ "daala",
+ "video codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/daala/daala.h b/modules/daala/daala.h
new file mode 100644
index 0000000..323ba88
--- /dev/null
+++ b/modules/daala/daala.h
@@ -0,0 +1,20 @@
+/**
+ * @file daala.h Experimental video-codec using Daala -- internal api
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+
+/* Encode */
+int daala_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int daala_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+
+/* Decode */
+int daala_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int daala_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb);
diff --git a/modules/daala/decode.c b/modules/daala/decode.c
new file mode 100644
index 0000000..062fb60
--- /dev/null
+++ b/modules/daala/decode.c
@@ -0,0 +1,181 @@
+/**
+ * @file daala/decode.c Experimental video-codec using Daala -- decoder
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <daala/daaladec.h>
+#include "daala.h"
+
+
+struct viddec_state {
+ daala_dec_ctx *dec;
+
+ bool got_headers;
+
+ daala_info di;
+ daala_comment dc;
+ daala_setup_info *ds;
+
+ struct {
+ bool valid;
+ size_t n_frame;
+ size_t n_header;
+ size_t n_keyframe;
+ size_t n_packet;
+ } stats;
+};
+
+
+static void dump_stats(const struct viddec_state *vds)
+{
+ re_printf("~~~~~ Daala Decoder stats ~~~~~\n");
+ re_printf("num frames: %zu\n", vds->stats.n_frame);
+ re_printf("num headers: %zu\n", vds->stats.n_header);
+ re_printf("key-frames packets: %zu\n", vds->stats.n_keyframe);
+ re_printf("total packets: %zu\n", vds->stats.n_packet);
+}
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->stats.valid)
+ dump_stats(vds);
+
+ if (vds->dec)
+ daala_decode_free(vds->dec);
+
+ if (vds->ds)
+ daala_setup_free(vds->ds);
+ daala_comment_clear(&vds->dc);
+ daala_info_clear(&vds->di);
+}
+
+
+int daala_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ daala_info_init(&vds->di);
+ daala_comment_init(&vds->dc);
+
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+int daala_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ daala_packet dp;
+ bool ishdr;
+ int i, r, err = 0;
+ (void)seq;
+
+ if (!vds || !frame || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ ++vds->stats.n_packet;
+ ++vds->stats.valid;
+
+ memset(&dp, 0, sizeof(dp));
+
+ dp.packet = mbuf_buf(mb);
+ dp.bytes = mbuf_get_left(mb);
+ dp.b_o_s = marker;
+
+ ishdr = daala_packet_isheader(&dp);
+
+ if (ishdr)
+ ++vds->stats.n_header;
+ else if (daala_packet_iskeyframe(&dp) > 0) {
+ ++vds->stats.n_keyframe;
+ *intra = true;
+ }
+
+ if (daala_packet_isheader(&dp)) {
+
+ r = daala_decode_header_in(&vds->di, &vds->dc, &vds->ds,
+ &dp);
+ if (r < 0) {
+ warning("daala: decoder: decode_header_in failed"
+ " (ret = %d)\n",
+ r);
+ return EPROTO;
+ }
+ else if (r == 0) {
+ vds->got_headers = true;
+ info("daala: all headers received\n");
+
+ vds->dec = daala_decode_create(&vds->di, vds->ds);
+ if (!vds->dec) {
+ warning("daala: decoder: alloc failed\n");
+ return ENOMEM;
+ }
+ }
+ else {
+ /* waiting for more headers */
+ }
+ }
+ else {
+ daala_image img;
+
+ if (!vds->got_headers) {
+ warning("daala: decode: still waiting for headers\n");
+ return EPROTO;
+ }
+
+ r = daala_decode_packet_in(vds->dec, &dp);
+ if (r < 0) {
+ warning("daala: decode: packet_in error (%d)\n", r);
+ return EPROTO;
+ }
+
+ r = daala_decode_img_out(vds->dec, &img);
+ if (r != 1) {
+ warning("daala: decode: img_out error (%d)\n", r);
+ return EPROTO;
+ }
+
+ for (i=0; i<3; i++) {
+ frame->data[i] = img.planes[i].data;
+ frame->linesize[i] = img.planes[i].ystride;
+ }
+
+ frame->size.w = img.width;
+ frame->size.h = img.height;
+ frame->fmt = VID_FMT_YUV420P;
+
+ ++vds->stats.n_frame;
+ }
+
+ return err;
+}
diff --git a/modules/daala/encode.c b/modules/daala/encode.c
new file mode 100644
index 0000000..d8abe4d
--- /dev/null
+++ b/modules/daala/encode.c
@@ -0,0 +1,292 @@
+/**
+ * @file daala/encode.c Experimental video-codec using Daala -- encoder
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <daala/daalaenc.h>
+#include "daala.h"
+
+
+struct videnc_state {
+ struct vidsz size;
+ daala_enc_ctx *enc;
+ int64_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ videnc_packet_h *pkth;
+ void *arg;
+
+ struct {
+ bool valid;
+ size_t n_frame;
+ size_t n_header;
+ size_t n_keyframe;
+ size_t n_packet;
+ } stats;
+};
+
+
+static void dump_stats(const struct videnc_state *ves)
+{
+ re_printf("~~~~~ Daala Encoder stats ~~~~~\n");
+ re_printf("num frames: %zu\n", ves->stats.n_frame);
+ re_printf("num headers: %zu\n", ves->stats.n_header);
+ re_printf("key-frames packets: %zu\n", ves->stats.n_keyframe);
+ re_printf("total packets: %zu\n", ves->stats.n_packet);
+}
+
+
+static int send_packet(struct videnc_state *ves, bool marker,
+ const uint8_t *pld, size_t pld_len)
+{
+ daala_packet dp;
+ int err;
+
+ memset(&dp, 0, sizeof(dp));
+
+ dp.packet = (uint8_t *)pld;
+ dp.bytes = pld_len;
+ dp.b_o_s = marker;
+
+ err = ves->pkth(marker, NULL, 0, pld, pld_len, ves->arg);
+ if (err)
+ return err;
+
+ ++ves->stats.n_packet;
+ ++ves->stats.valid;
+
+ if (daala_packet_isheader(&dp))
+ ++ves->stats.n_header;
+ else if (daala_packet_iskeyframe(&dp) > 0)
+ ++ves->stats.n_keyframe;
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *ves = arg;
+
+ if (ves->stats.valid)
+ dump_stats(ves);
+
+ if (ves->enc)
+ daala_encode_free(ves->enc);
+}
+
+
+int daala_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *ves;
+ (void)fmtp;
+
+ if (!vesp || !vc || !prm || prm->pktsize < 3 || !pkth)
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->enc && (ves->bitrate != prm->bitrate ||
+ ves->pktsize != prm->pktsize ||
+ ves->fps != prm->fps)) {
+
+ info("daala: encoder: params changed\n");
+
+ daala_encode_free(ves->enc);
+ ves->enc = NULL;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *ves, const struct vidsz *size)
+{
+ daala_info di;
+ daala_comment dc;
+ daala_packet dp;
+ int err = 0;
+ int complexity = 7;
+ int video_q = 30;
+ int bitrate = ves->bitrate;
+
+ info("daala: open encoder (%d x %d, %d bps)\n",
+ size->w, size->h, bitrate);
+
+ if (ves->enc) {
+ debug("daala: re-opening encoder\n");
+ daala_encode_free(ves->enc);
+ }
+
+ daala_info_init(&di);
+ daala_comment_init(&dc);
+
+ di.pic_width = size->w;
+ di.pic_height = size->h;
+ di.timebase_numerator = 1;
+ di.timebase_denominator = ves->fps;
+ di.frame_duration = 1;
+ di.pixel_aspect_numerator = -1;
+ di.pixel_aspect_denominator = -1;
+ di.nplanes = 3;
+ di.plane_info[0].xdec = 0; /* YUV420P */
+ di.plane_info[0].ydec = 0;
+ di.plane_info[1].xdec = 1;
+ di.plane_info[1].ydec = 1;
+ di.plane_info[2].xdec = 1;
+ di.plane_info[2].ydec = 1;
+
+ di.keyframe_rate = 100;
+
+ info("daala: open encoder with bitstream version %u.%u.%u\n",
+ di.version_major, di.version_minor, di.version_sub);
+
+ ves->enc = daala_encode_create(&di);
+ if (!ves->enc) {
+ warning("daala: failed to open DAALA encoder\n");
+ return ENOMEM;
+ }
+
+ daala_encode_ctl(ves->enc, OD_SET_QUANT,
+ &video_q, sizeof(video_q));
+
+ daala_encode_ctl(ves->enc, OD_SET_COMPLEXITY,
+ &complexity, sizeof(complexity));
+
+ daala_encode_ctl(ves->enc, OD_SET_BITRATE,
+ &bitrate, sizeof(bitrate));
+
+ for (;;) {
+ int r;
+
+ r = daala_encode_flush_header(ves->enc, &dc, &dp);
+ if (r < 0) {
+ warning("daala: flush_header returned %d\n", r);
+ break;
+ }
+ else if (r == 0)
+ break;
+
+ debug("daala: header: %lld bytes header=%d key=%d\n",
+ dp.bytes,
+ daala_packet_isheader(&dp),
+ daala_packet_iskeyframe(&dp));
+
+#if 0
+ re_printf("bos=%lld, eos=%lld, granule=%lld, packetno=%lld\n",
+ dp.b_o_s,
+ dp.e_o_s,
+ dp.granulepos,
+ dp.packetno);
+#endif
+
+ err = send_packet(ves, dp.b_o_s, dp.packet, dp.bytes);
+ if (err)
+ break;
+ }
+
+ daala_info_clear(&di);
+ daala_comment_clear(&dc);
+
+ return err;
+}
+
+
+int daala_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame)
+{
+ int r, err = 0;
+ daala_image img;
+ unsigned i;
+ (void)update; /* XXX: how to force a KEY-frame? */
+
+ if (!ves || !frame || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ ++ves->stats.n_frame;
+
+ if (!ves->enc || !vidsz_cmp(&ves->size, &frame->size)) {
+
+ err = open_encoder(ves, &frame->size);
+ if (err)
+ return err;
+
+ ves->size = frame->size;
+ }
+
+ img.planes[0].data = frame->data[0];
+ img.planes[0].xdec = 0;
+ img.planes[0].ydec = 0;
+ img.planes[0].xstride = 1;
+ img.planes[0].ystride = frame->linesize[0];
+
+ img.planes[1].data = frame->data[1];
+ img.planes[1].xdec = 1;
+ img.planes[1].ydec = 1;
+ img.planes[1].xstride = 1;
+ img.planes[1].ystride = frame->linesize[1];
+
+ img.planes[2].data = frame->data[2];
+ img.planes[2].xdec = 1;
+ img.planes[2].ydec = 1;
+ img.planes[2].xstride = 1;
+ img.planes[2].ystride = frame->linesize[2];
+
+ for (i=0; i<3; i++)
+ img.planes[i].bitdepth = 8;
+
+ img.nplanes = 3;
+
+ img.width = frame->size.w;
+ img.height = frame->size.h;
+
+ r = daala_encode_img_in(ves->enc, &img, 0);
+ if (r != 0) {
+ warning("daala: encoder: encode_img_in failed (ret = %d)\n",
+ r);
+ return EPROTO;
+ }
+
+ for (;;) {
+ daala_packet dp;
+
+ r = daala_encode_packet_out(ves->enc, 0, &dp);
+ if (r < 0) {
+ warning("daala: encoder: packet_out ret=%d\n", r);
+ break;
+ }
+ else if (r == 0) {
+ break;
+ }
+
+ err = send_packet(ves, dp.b_o_s, dp.packet, dp.bytes);
+ if (err)
+ break;
+ }
+
+ return 0;
+}
diff --git a/modules/daala/module.mk b/modules/daala/module.mk
new file mode 100644
index 0000000..93f88ac
--- /dev/null
+++ b/modules/daala/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := daala
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += daala.c
+$(MOD)_LFLAGS += -ldaalaenc -ldaaladec -ldaalabase
+
+include mk/mod.mk
diff --git a/modules/debug_cmd/debug_cmd.c b/modules/debug_cmd/debug_cmd.c
new file mode 100644
index 0000000..faee4ee
--- /dev/null
+++ b/modules/debug_cmd/debug_cmd.c
@@ -0,0 +1,164 @@
+/**
+ * @file debug_cmd.c Debug commands
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <stdlib.h>
+#include <time.h>
+#ifdef USE_OPENSSL
+#include <openssl/crypto.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup debug_cmd debug_cmd
+ *
+ * Advanced debug commands
+ */
+
+
+static uint64_t start_ticks; /**< Ticks when app started */
+static time_t start_time; /**< Start time of application */
+
+
+static int cmd_net_debug(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return net_debug(pf, baresip_network());
+}
+
+
+static int print_system_info(struct re_printf *pf, void *arg)
+{
+ uint32_t uptime;
+ int err = 0;
+
+ (void)arg;
+
+ uptime = (uint32_t)((long long)(tmr_jiffies() - start_ticks)/1000);
+
+ err |= re_hprintf(pf, "\n--- System info: ---\n");
+
+ err |= re_hprintf(pf, " Machine: %s/%s\n", sys_arch_get(),
+ sys_os_get());
+ err |= re_hprintf(pf, " Version: %s (libre v%s)\n",
+ BARESIP_VERSION, sys_libre_version_get());
+ err |= re_hprintf(pf, " Build: %H\n", sys_build_get, NULL);
+ err |= re_hprintf(pf, " Kernel: %H\n", sys_kernel_get, NULL);
+ err |= re_hprintf(pf, " Uptime: %H\n", fmt_human_time, &uptime);
+ err |= re_hprintf(pf, " Started: %s", ctime(&start_time));
+
+#ifdef __VERSION__
+ err |= re_hprintf(pf, " Compiler: %s\n", __VERSION__);
+#endif
+
+#ifdef USE_OPENSSL
+ err |= re_hprintf(pf, " OpenSSL: %s\n",
+ SSLeay_version(SSLEAY_VERSION));
+#endif
+
+ return err;
+}
+
+
+static int cmd_config_print(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return config_print(pf, conf_config());
+}
+
+
+static int cmd_ua_debug(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return ua_debug(pf, uag_current());
+}
+
+
+static int cmd_play_file(struct re_printf *pf, void *arg)
+{
+ struct cmd_arg *carg = arg;
+ const char *filename = carg->prm;
+ int err;
+
+ err = re_hprintf(pf, "playing audio file \"%s\" ..\n", filename);
+ if (err)
+ return err;
+
+ err = play_file(NULL, baresip_player(), filename, 0);
+ if (err) {
+ warning("debug_cmd: play_file(%s) failed (%m)\n",
+ filename, err);
+ return err;
+ }
+
+ return err;
+}
+
+
+static int reload_config(struct re_printf *pf, void *arg)
+{
+ int err;
+ (void)arg;
+
+ err = re_hprintf(pf, "reloading config file ..\n");
+ if (err)
+ return err;
+
+ err = conf_configure();
+ if (err) {
+ (void)re_hprintf(pf, "reload_config failed: %m\n", err);
+ return err;
+ }
+
+ (void)re_hprintf(pf, "done\n");
+
+ return 0;
+}
+
+
+static const struct cmd debugcmdv[] = {
+{"main", 0, 0, "Main loop debug", re_debug },
+{"config", 0, 0, "Print configuration", cmd_config_print },
+{"sipstat", 'i', 0, "SIP debug", ua_print_sip_status },
+{"modules", 0, 0, "Module debug", mod_debug },
+{"netstat", 'n', 0, "Network debug", cmd_net_debug },
+{"sysinfo", 's', 0, "System info", print_system_info },
+{"timers", 0, 0, "Timer debug", tmr_status },
+{"uastat", 'u', 0, "UA debug", cmd_ua_debug },
+{"memstat", 'y', 0, "Memory status", mem_status },
+{"play", 0, CMD_PRM, "Play audio file", cmd_play_file },
+{"conf_reload",0, 0, "Reload config file", reload_config },
+};
+
+
+static int module_init(void)
+{
+ int err;
+
+ start_ticks = tmr_jiffies();
+ (void)time(&start_time);
+
+ err = cmd_register(baresip_commands(),
+ debugcmdv, ARRAY_SIZE(debugcmdv));
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ cmd_unregister(baresip_commands(), debugcmdv);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(debug_cmd) = {
+ "debug_cmd",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/debug_cmd/module.mk b/modules/debug_cmd/module.mk
new file mode 100644
index 0000000..3680314
--- /dev/null
+++ b/modules/debug_cmd/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := debug_cmd
+$(MOD)_SRCS += debug_cmd.c
+
+include mk/mod.mk
diff --git a/modules/directfb/directfb.c b/modules/directfb/directfb.c
new file mode 100644
index 0000000..0dfb122
--- /dev/null
+++ b/modules/directfb/directfb.c
@@ -0,0 +1,190 @@
+/**
+ * @file directfb.c DirectFB video display module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de>
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <directfb.h>
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /**< Inheritance (1st) */
+ struct vidsz size; /**< Current size */
+ IDirectFBWindow *window; /**< DirectFB Window */
+ IDirectFBSurface *surface; /**< Surface for pixels */
+ IDirectFBDisplayLayer *layer; /**< Display layer */
+};
+
+
+static IDirectFB *dfb;
+static struct vidisp *vid;
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ if (st->surface)
+ st->surface->Release(st->surface);
+ if (st->window)
+ st->window->Release(st->window);
+ if (st->layer)
+ st->layer->Release(st->layer);
+}
+
+
+static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+
+ /* Not used by DirectFB */
+ (void) prm;
+ (void) dev;
+ (void) resizeh;
+ (void) arg;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &st->layer);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ void *pixels;
+ int pitch, i;
+ unsigned h;
+ uint8_t *p;
+ (void) title;
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ if (st->size.w && st->size.h) {
+ info("directfb: reset: %u x %u ---> %u x %u\n",
+ st->size.w, st->size.h,
+ frame->size.w, frame->size.h);
+ }
+
+ if (st->surface) {
+ st->surface->Release(st->surface);
+ st->surface = NULL;
+ }
+ if (st->window) {
+ st->window->Release(st->window);
+ st->window = NULL;
+ }
+ }
+
+ if (!st->window) {
+ DFBWindowDescription desc;
+
+ desc.flags = DWDESC_WIDTH|DWDESC_HEIGHT|DWDESC_PIXELFORMAT;
+ desc.width = frame->size.w;
+ desc.height = frame->size.h;
+ desc.pixelformat = DSPF_I420;
+
+ st->layer->CreateWindow(st->layer, &desc, &st->window);
+
+ st->size = frame->size;
+ st->window->SetOpacity(st->window, 0xff);
+ st->window->GetSurface(st->window, &st->surface);
+ }
+
+ st->surface->Lock(st->surface, DSLF_WRITE, &pixels, &pitch);
+
+ p = pixels;
+ for (i=0; i<3; i++) {
+
+ const uint8_t *s = frame->data[i];
+ const unsigned stp = frame->linesize[0] / frame->linesize[i];
+ const unsigned sz = frame->size.w / stp;
+
+ for (h = 0; h < frame->size.h; h += stp) {
+
+ memcpy(p, s, sz);
+
+ s += frame->linesize[i];
+ p += (pitch / stp);
+ }
+ }
+
+ st->surface->Unlock(st->surface);
+
+ /* Update the screen! */
+ st->surface->Flip(st->surface, 0, 0);
+
+ return 0;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st || !st->window)
+ return;
+
+ st->window->SetOpacity(st->window, 0x00);
+}
+
+
+static int module_init(void)
+{
+ int err = 0;
+ DFBResult ret;
+
+ ret = DirectFBInit(NULL, NULL);
+ if (ret) {
+ DirectFBError("DirectFBInit() failed", ret);
+ return (int) ret;
+ }
+
+ ret = DirectFBCreate(&dfb);
+ if (ret) {
+ DirectFBError("DirectFBCreate() failed", ret);
+ return (int) ret;
+ }
+
+ err = vidisp_register(&vid, baresip_vidispl(),
+ "directfb", alloc, NULL, display, hide);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ if (dfb) {
+ dfb->Release(dfb);
+ dfb = NULL;
+ }
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(directfb) = {
+ "directfb",
+ "vidisp",
+ module_init,
+ module_close
+};
diff --git a/modules/directfb/module.mk b/modules/directfb/module.mk
new file mode 100644
index 0000000..57fa045
--- /dev/null
+++ b/modules/directfb/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk - DirectFB video display module
+#
+# Copyright (C) 2010 Creytiv.com
+# Copyright (C) 2013 Andreas Shimokawa <andi@fischlustig.de>.
+#
+
+MOD := directfb
+$(MOD)_SRCS += directfb.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs directfb)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags directfb \
+ | sed -e 's/-I/-isystem/g')
+
+include mk/mod.mk
diff --git a/modules/dshow/dshow.cpp b/modules/dshow/dshow.cpp
new file mode 100644
index 0000000..d081f69
--- /dev/null
+++ b/modules/dshow/dshow.cpp
@@ -0,0 +1,536 @@
+/**
+ * @file dshow.cpp Windows DirectShow video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2010 Dusan Stevanovic
+ */
+
+#include <stdio.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <commctrl.h>
+#include <dshow.h>
+
+#pragma comment(lib, "strmiids.lib")
+
+
+/**
+ * @defgroup dshow dshow
+ *
+ * Windows DirectShow video-source
+ *
+ *
+ * References:
+ *
+ * http://www.alsa-project.org/main/index.php/Main_Page
+ */
+
+
+/* a piece from Google WebM's qedit.h:
+ *
+ * https://code.google.com/p/webm/source/browse/qedit.h?repo=udpsample
+ */
+static const
+IID IID_ISampleGrabber = {
+ 0x6b652fff, 0x11fe, 0x4fce,
+ { 0x92, 0xad, 0x02, 0x66, 0xb5, 0xd7, 0xc7, 0x8f }
+};
+
+static const
+IID IID_ISampleGrabberCB = {
+ 0x0579154a, 0x2b53, 0x4994,
+ { 0xb0, 0xd0, 0xe7, 0x73, 0x14, 0x8e, 0xff, 0x85 }
+};
+
+#include "qedit.h"
+
+/*
+const CLSID CLSID_SampleGrabber = { 0xc1f400a0, 0x3f08, 0x11d3,
+ { 0x9f, 0x0b, 0x00, 0x60, 0x08, 0x03, 0x9e, 0x37 }
+};
+*/
+
+class Grabber;
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ ICaptureGraphBuilder2 *capture;
+ IBaseFilter *grabber_filter;
+ IBaseFilter *dev_filter;
+ ISampleGrabber *grabber;
+ IMoniker *dev_moniker;
+ IGraphBuilder *graph;
+ IMediaControl *mc;
+
+ Grabber *grab;
+
+ struct vidsz size;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+class Grabber : public ISampleGrabberCB {
+public:
+ Grabber(struct vidsrc_st *st) : src(st) { }
+
+ STDMETHOD(QueryInterface)(REFIID InterfaceIdentifier,
+ VOID** ppvObject) throw()
+ {
+ if (InterfaceIdentifier == __uuidof(ISampleGrabberCB)) {
+ *ppvObject = (ISampleGrabberCB**) this;
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ STDMETHOD_(ULONG, AddRef)() throw()
+ {
+ return 2;
+ }
+
+ STDMETHOD_(ULONG, Release)() throw()
+ {
+ return 1;
+ }
+
+ STDMETHOD(BufferCB) (double sample_time, BYTE *buf, long buf_len)
+ {
+ struct vidframe vidframe;
+
+ /* XXX: should be VID_FMT_BGR24 */
+ vidframe_init_buf(&vidframe, VID_FMT_RGB32, &src->size, buf);
+
+ if (src->frameh)
+ src->frameh(&vidframe, src->arg);
+
+ return S_OK;
+ }
+
+ STDMETHOD(SampleCB) (double sample_time, IMediaSample *samp)
+ {
+ return S_OK;
+ }
+
+private:
+ struct vidsrc_st *src;
+};
+
+
+static struct vidsrc *vsrc;
+
+
+static int get_device(struct vidsrc_st *st, const char *name)
+{
+ ICreateDevEnum *dev_enum;
+ IEnumMoniker *enum_mon;
+ IMoniker *mon;
+ ULONG fetched;
+ HRESULT res;
+ int id = 0;
+ bool found = false;
+
+ if (!st)
+ return EINVAL;
+
+ res = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_ICreateDevEnum, (void**)&dev_enum);
+ if (res != NOERROR)
+ return ENOENT;
+
+ res = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
+ &enum_mon, 0);
+ if (res != NOERROR)
+ return ENOENT;
+
+ enum_mon->Reset();
+ while (enum_mon->Next(1, &mon, &fetched) == S_OK && !found) {
+
+ IPropertyBag *bag;
+ VARIANT var;
+ char dev_name[256];
+ int len = 0;
+
+ res = mon->BindToStorage(0, 0, IID_IPropertyBag,
+ (void **)&bag);
+ if (!SUCCEEDED(res))
+ continue;
+
+ var.vt = VT_BSTR;
+ res = bag->Read(L"FriendlyName", &var, NULL);
+ if (NOERROR != res)
+ continue;
+
+ len = WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1,
+ dev_name, sizeof(dev_name),
+ NULL, NULL);
+
+ if (len > 0) {
+ found = !str_isset(name) ||
+ !str_casecmp(dev_name, name);
+
+ if (found) {
+ info("dshow: got device '%s' id=%d\n",
+ name, id);
+ st->dev_moniker = mon;
+ }
+ }
+
+ SysFreeString(var.bstrVal);
+ bag->Release();
+ if (!found) {
+ mon->Release();
+ ++id;
+ }
+ }
+
+ return found ? 0 : ENOENT;
+}
+
+
+static int add_sample_grabber(struct vidsrc_st *st)
+{
+ AM_MEDIA_TYPE mt;
+ HRESULT hr;
+
+ hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
+ IID_IBaseFilter, (void**) &st->grabber_filter);
+ if (FAILED(hr))
+ return ENOMEM;
+
+ hr = st->graph->AddFilter(st->grabber_filter, L"Sample Grabber");
+ if (FAILED(hr))
+ return ENOMEM;
+
+ hr = st->grabber_filter->QueryInterface(IID_ISampleGrabber,
+ (void**)&st->grabber);
+ if (FAILED(hr))
+ return ENODEV;
+
+ hr = st->grabber->SetCallback(st->grab, 1);
+ if (FAILED(hr))
+ return ENOSYS;
+
+ memset(&mt, 0, sizeof(mt));
+ mt.majortype = MEDIATYPE_Video;
+ mt.subtype = MEDIASUBTYPE_RGB24; /* XXX: try YUV420P */
+ hr = st->grabber->SetMediaType(&mt);
+ if (FAILED(hr))
+ return ENODEV;
+
+ st->grabber->SetOneShot(FALSE);
+ st->grabber->SetBufferSamples(FALSE);
+
+ return 0;
+}
+
+
+static AM_MEDIA_TYPE *free_mt(AM_MEDIA_TYPE *mt)
+{
+ if (!mt)
+ return NULL;
+
+ if (mt->cbFormat) {
+ CoTaskMemFree((PVOID)mt->pbFormat);
+ }
+ if (mt->pUnk != NULL) {
+ mt->pUnk->Release();
+ mt->pUnk = NULL;
+ }
+
+ CoTaskMemFree((PVOID)mt);
+
+ return NULL;
+}
+
+
+static int config_pin(struct vidsrc_st *st, IPin *pin)
+{
+ AM_MEDIA_TYPE *mt;
+ AM_MEDIA_TYPE *best_mt = NULL;
+ IEnumMediaTypes *media_enum = NULL;
+ IAMStreamConfig *stream_conf = NULL;
+ VIDEOINFOHEADER *vih;
+ HRESULT hr;
+ int h = st->size.h;
+ int w = st->size.w;
+ int rh, rw;
+ int wh, rwrh;
+ int best_match = 0;
+ int err = 0;
+
+ if (!pin || !st)
+ return EINVAL;
+
+ hr = pin->EnumMediaTypes(&media_enum);
+ if (FAILED(hr))
+ return ENODATA;
+
+ while ((hr = media_enum->Next(1, &mt, NULL)) == S_OK) {
+ if (mt->formattype != FORMAT_VideoInfo)
+ continue;
+
+ vih = (VIDEOINFOHEADER *) mt->pbFormat;
+ rw = vih->bmiHeader.biWidth;
+ rh = vih->bmiHeader.biHeight;
+
+ wh = w * h;
+ rwrh = rw * rh;
+ if (wh == rwrh) {
+ best_mt = free_mt(best_mt);
+ break;
+ }
+ else {
+ int diff = abs(rwrh - wh);
+
+ if (best_match != 0 && diff >= best_match)
+ mt = free_mt(mt);
+ else {
+ best_match = diff;
+ free_mt(best_mt);
+ best_mt = mt;
+ }
+ }
+ }
+ if (hr != S_OK)
+ mt = free_mt(mt);
+
+ if (mt == NULL && best_mt == NULL) {
+ err = ENODATA;
+ goto out;
+ }
+ if (mt == NULL)
+ mt = best_mt;
+
+ hr = pin->QueryInterface(IID_IAMStreamConfig,
+ (void **) &stream_conf);
+ if (FAILED(hr)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ vih = (VIDEOINFOHEADER *) mt->pbFormat;
+ hr = stream_conf->SetFormat(mt);
+ mt = free_mt(mt);
+ if (FAILED(hr)) {
+ err = ERANGE;
+ goto out;
+ }
+
+ hr = stream_conf->GetFormat(&mt);
+ if (FAILED(hr)) {
+ err = EINVAL;
+ goto out;
+ }
+ if (mt->formattype != FORMAT_VideoInfo) {
+ err = EINVAL;
+ goto out;
+ }
+
+ vih = (VIDEOINFOHEADER *)mt->pbFormat;
+ rw = vih->bmiHeader.biWidth;
+ rh = vih->bmiHeader.biHeight;
+
+ if (w != rw || h != rh) {
+ warning("dshow: config_pin: picture size missmatch: "
+ "wanted %d x %d, got %d x %d\n",
+ w, h, rw, rh);
+ }
+ st->size.w = rw;
+ st->size.h = rh;
+
+ out:
+ if (media_enum)
+ media_enum->Release();
+ if (stream_conf)
+ stream_conf->Release();
+ free_mt(mt);
+
+ return err;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = (struct vidsrc_st *)arg;
+
+ if (st->mc) {
+ st->mc->Stop();
+ st->mc->Release();
+ }
+
+ if (st->grabber) {
+ st->grabber->SetCallback(NULL, 1);
+ st->grabber->Release();
+ }
+ if (st->grabber_filter)
+ st->grabber_filter->Release();
+ if (st->dev_moniker)
+ st->dev_moniker->Release();
+ if (st->dev_filter)
+ st->dev_filter->Release();
+ if (st->capture) {
+ st->capture->RenderStream(&PIN_CATEGORY_CAPTURE,
+ &MEDIATYPE_Video,
+ NULL, NULL, NULL);
+ st->capture->Release();
+ }
+ if (st->graph)
+ st->graph->Release();
+
+ delete st->grab;
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size,
+ const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ IEnumPins *pin_enum = NULL;
+ IPin *pin = NULL;
+ HRESULT hr;
+ int err;
+ (void)ctx;
+ (void)errorh;
+
+ if (!stp || !vs || !prm || !size)
+ return EINVAL;
+
+ st = (struct vidsrc_st *) mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = get_device(st, dev);
+ if (err)
+ goto out;
+
+ st->vs = vs;
+
+ st->size = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ st->grab = new Grabber(st);
+ if (!st->grab) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
+ IID_IGraphBuilder, (void **) &st->graph);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: IID_IGraphBuilder failed: %ld\n", hr);
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL,
+ CLSCTX_INPROC, IID_ICaptureGraphBuilder2,
+ (void **) &st->capture);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: IID_ICaptureGraphBuilder2: %ld\n", hr);
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->capture->SetFiltergraph(st->graph);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: SetFiltergraph failed: %ld\n", hr);
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->dev_moniker->BindToObject(NULL, NULL, IID_IBaseFilter,
+ (void **) &st->dev_filter);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: bind to base filter failed: %ld\n", hr);
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->graph->AddFilter(st->dev_filter, L"Video Capture");
+ if (FAILED(hr)) {
+ warning("dshow: alloc: VideoCapture failed: %ld\n", hr);
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->dev_filter->EnumPins(&pin_enum);
+ if (pin_enum) {
+ pin_enum->Reset();
+ hr = pin_enum->Next(1, &pin, NULL);
+ }
+
+ add_sample_grabber(st);
+ err = config_pin(st, pin);
+ pin->Release();
+ if (err)
+ goto out;
+
+ hr = st->capture->RenderStream(&PIN_CATEGORY_CAPTURE,
+ &MEDIATYPE_Video,
+ st->dev_filter,
+ NULL, st->grabber_filter);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: RenderStream failed\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->graph->QueryInterface(IID_IMediaControl,
+ (void **) &st->mc);
+ if (FAILED(hr)) {
+ warning("dshow: alloc: IMediaControl failed\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ hr = st->mc->Run();
+ if (FAILED(hr)) {
+ warning("dshow: alloc: Run failed\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ if (CoInitialize(NULL) != S_OK)
+ return ENODATA;
+
+ return vidsrc_register(&vsrc, baresip_vidsrcl(),
+ "dshow", alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ vsrc = (struct vidsrc *) mem_deref(vsrc);
+ CoUninitialize();
+
+ return 0;
+}
+
+
+extern "C" const struct mod_export DECL_EXPORTS(dshow) = {
+ "dshow",
+ "vidsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/dshow/module.mk b/modules/dshow/module.mk
new file mode 100644
index 0000000..f70623d
--- /dev/null
+++ b/modules/dshow/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := dshow
+$(MOD)_SRCS += dshow.cpp
+$(MOD)_LFLAGS += -lstrmiids -lole32 -loleaut32 -lstdc++
+
+include mk/mod.mk
diff --git a/modules/dtls_srtp/dtls.c b/modules/dtls_srtp/dtls.c
new file mode 100644
index 0000000..1b78255
--- /dev/null
+++ b/modules/dtls_srtp/dtls.c
@@ -0,0 +1,51 @@
+/**
+ * @file dtls.c DTLS functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "dtls_srtp.h"
+
+
+int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls)
+{
+ uint8_t md[20];
+ unsigned int i;
+ int err = 0;
+
+ if (!tls)
+ return EINVAL;
+
+ err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA1, md, sizeof(md));
+ if (err)
+ return err;
+
+ for (i=0; i<sizeof(md); i++) {
+ err |= re_hprintf(pf, "%s%02X", i==0 ? "" : ":", md[i]);
+ }
+
+ return err;
+}
+
+
+int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls)
+{
+ uint8_t md[32];
+ unsigned int i;
+ int err = 0;
+
+ if (!tls)
+ return EINVAL;
+
+ err = tls_fingerprint(tls, TLS_FINGERPRINT_SHA256, md, sizeof(md));
+ if (err)
+ return err;
+
+ for (i=0; i<sizeof(md); i++) {
+ err |= re_hprintf(pf, "%s%02X", i==0 ? "" : ":", md[i]);
+ }
+
+ return err;
+}
diff --git a/modules/dtls_srtp/dtls_srtp.c b/modules/dtls_srtp/dtls_srtp.c
new file mode 100644
index 0000000..b92ff06
--- /dev/null
+++ b/modules/dtls_srtp/dtls_srtp.c
@@ -0,0 +1,507 @@
+/**
+ * @file dtls_srtp.c DTLS-SRTP media encryption
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <string.h>
+#include "dtls_srtp.h"
+
+
+/**
+ * @defgroup dtls_srtp dtls_srtp
+ *
+ * DTLS-SRTP media encryption module
+ *
+ * This module implements end-to-end media encryption using DTLS-SRTP
+ * which is now mandatory for WebRTC endpoints.
+ *
+ * DTLS-SRTP can be enabled in ~/.baresip/accounts:
+ *
+ \verbatim
+ <sip:user@domain.com>;mediaenc=dtls_srtp
+ <sip:user@domain.com>;mediaenc=dtls_srtpf
+ <sip:user@domain.com>;mediaenc=srtp-mandf
+ \endverbatim
+ *
+ *
+ * Internally the protocol stack diagram looks something like this:
+ *
+ \verbatim
+ * application
+ * |
+ * |
+ * [DTLS] [SRTP]
+ * \ /
+ * \ /
+ * \ /
+ * \/
+ * ( TURN/ICE )
+ * |
+ * |
+ * [socket]
+ \endverbatim
+ *
+ */
+
+struct menc_sess {
+ struct sdp_session *sdp;
+ bool offerer;
+ menc_error_h *errorh;
+ void *arg;
+};
+
+/* media */
+struct dtls_srtp {
+ struct comp compv[2];
+ const struct menc_sess *sess;
+ struct sdp_media *sdpm;
+ struct tmr tmr;
+ bool started;
+ bool active;
+ bool mux;
+};
+
+static struct tls *tls;
+static const char* srtp_profiles =
+ "SRTP_AES128_CM_SHA1_80:"
+ "SRTP_AES128_CM_SHA1_32";
+
+
+static void sess_destructor(void *arg)
+{
+ struct menc_sess *sess = arg;
+
+ mem_deref(sess->sdp);
+}
+
+
+static void destructor(void *arg)
+{
+ struct dtls_srtp *st = arg;
+ size_t i;
+
+ tmr_cancel(&st->tmr);
+
+ for (i=0; i<2; i++) {
+ struct comp *c = &st->compv[i];
+
+ mem_deref(c->uh_srtp);
+ mem_deref(c->tls_conn);
+ mem_deref(c->dtls_sock);
+ mem_deref(c->app_sock); /* must be freed last */
+ mem_deref(c->tx);
+ mem_deref(c->rx);
+ }
+
+ mem_deref(st->sdpm);
+}
+
+
+static bool verify_fingerprint(const struct sdp_session *sess,
+ const struct sdp_media *media,
+ struct tls_conn *tc)
+{
+ struct pl hash;
+ uint8_t md_sdp[32], md_dtls[32];
+ size_t sz_sdp = sizeof(md_sdp);
+ size_t sz_dtls;
+ enum tls_fingerprint type;
+ int err;
+
+ if (sdp_fingerprint_decode(sdp_media_session_rattr(media, sess,
+ "fingerprint"),
+ &hash, md_sdp, &sz_sdp))
+ return false;
+
+ if (0 == pl_strcasecmp(&hash, "sha-1")) {
+ type = TLS_FINGERPRINT_SHA1;
+ sz_dtls = 20;
+ }
+ else if (0 == pl_strcasecmp(&hash, "sha-256")) {
+ type = TLS_FINGERPRINT_SHA256;
+ sz_dtls = 32;
+ }
+ else {
+ warning("dtls_srtp: unknown fingerprint '%r'\n", &hash);
+ return false;
+ }
+
+ err = tls_peer_fingerprint(tc, type, md_dtls, sizeof(md_dtls));
+ if (err) {
+ warning("dtls_srtp: could not get DTLS fingerprint (%m)\n",
+ err);
+ return false;
+ }
+
+ if (sz_sdp != sz_dtls || 0 != memcmp(md_sdp, md_dtls, sz_sdp)) {
+ warning("dtls_srtp: %r fingerprint mismatch\n", &hash);
+ info("SDP: %w\n", md_sdp, sz_sdp);
+ info("DTLS: %w\n", md_dtls, sz_dtls);
+ return false;
+ }
+
+ info("dtls_srtp: verified %r fingerprint OK\n", &hash);
+
+ return true;
+}
+
+
+static int session_alloc(struct menc_sess **sessp,
+ struct sdp_session *sdp, bool offerer,
+ menc_error_h *errorh, void *arg)
+{
+ struct menc_sess *sess;
+ int err;
+
+ if (!sessp || !sdp)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), sess_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->sdp = mem_ref(sdp);
+ sess->offerer = offerer;
+ sess->errorh = errorh;
+ sess->arg = arg;
+
+ /* RFC 4145 */
+ err = sdp_session_set_lattr(sdp, true, "setup",
+ offerer ? "actpass" : "active");
+ if (err)
+ goto out;
+
+ /* RFC 4572 */
+ err = sdp_session_set_lattr(sdp, true, "fingerprint", "SHA-256 %H",
+ dtls_print_sha256_fingerprint, tls);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static void dtls_estab_handler(void *arg)
+{
+ struct comp *comp = arg;
+ const struct dtls_srtp *ds = comp->ds;
+ enum srtp_suite suite;
+ uint8_t cli_key[30], srv_key[30];
+ int err;
+
+ if (!verify_fingerprint(ds->sess->sdp, ds->sdpm, comp->tls_conn)) {
+ warning("dtls_srtp: could not verify remote fingerprint\n");
+ if (ds->sess->errorh)
+ ds->sess->errorh(EPIPE, ds->sess->arg);
+ return;
+ }
+
+ err = tls_srtp_keyinfo(comp->tls_conn, &suite,
+ cli_key, sizeof(cli_key),
+ srv_key, sizeof(srv_key));
+ if (err) {
+ warning("dtls_srtp: could not get SRTP keyinfo (%m)\n", err);
+ return;
+ }
+
+ comp->negotiated = true;
+
+ info("dtls_srtp: ---> DTLS-SRTP complete (%s/%s) Profile=%s\n",
+ sdp_media_name(ds->sdpm),
+ comp->is_rtp ? "RTP" : "RTCP", srtp_suite_name(suite));
+
+ err |= srtp_stream_add(&comp->tx, suite,
+ ds->active ? cli_key : srv_key, 30, true);
+ err |= srtp_stream_add(&comp->rx, suite,
+ ds->active ? srv_key : cli_key, 30, false);
+
+ err |= srtp_install(comp);
+ if (err) {
+ warning("dtls_srtp: srtp_install: %m\n", err);
+ }
+
+ /* todo: notify application that crypto is up and running */
+}
+
+
+static void dtls_close_handler(int err, void *arg)
+{
+ struct comp *comp = arg;
+
+ info("dtls_srtp: dtls-connection closed (%m)\n", err);
+
+ comp->tls_conn = mem_deref(comp->tls_conn);
+
+ if (!comp->negotiated) {
+
+ if (comp->ds->sess->errorh)
+ comp->ds->sess->errorh(err, comp->ds->sess->arg);
+ }
+}
+
+
+static void dtls_conn_handler(const struct sa *peer, void *arg)
+{
+ struct comp *comp = arg;
+ int err;
+ (void)peer;
+
+ info("dtls_srtp: incoming DTLS connect from %J\n", peer);
+
+ err = dtls_accept(&comp->tls_conn, tls, comp->dtls_sock,
+ dtls_estab_handler, NULL, dtls_close_handler, comp);
+ if (err) {
+ warning("dtls_srtp: dtls_accept failed (%m)\n", err);
+ return;
+ }
+}
+
+
+static int component_start(struct comp *comp, struct sdp_media *sdpm)
+{
+ struct sa raddr;
+ int err = 0;
+
+ if (!comp->app_sock || comp->negotiated || comp->dtls_sock)
+ return 0;
+
+ if (comp->is_rtp)
+ raddr = *sdp_media_raddr(sdpm);
+ else
+ sdp_media_raddr_rtcp(sdpm, &raddr);
+
+ err = dtls_listen(&comp->dtls_sock, NULL,
+ comp->app_sock, 2, LAYER_DTLS,
+ dtls_conn_handler, comp);
+ if (err) {
+ warning("dtls_srtp: dtls_listen failed (%m)\n", err);
+ return err;
+ }
+
+ if (sa_isset(&raddr, SA_ALL)) {
+
+ if (comp->ds->active && !comp->tls_conn) {
+
+ err = dtls_connect(&comp->tls_conn, tls,
+ comp->dtls_sock, &raddr,
+ dtls_estab_handler, NULL,
+ dtls_close_handler, comp);
+ if (err) {
+ warning("dtls_srtp: dtls_connect()"
+ " failed (%m)\n", err);
+ return err;
+ }
+ }
+ }
+
+ return err;
+}
+
+
+static int media_start(struct dtls_srtp *st, struct sdp_media *sdpm)
+{
+ int err = 0;
+
+ if (st->started)
+ return 0;
+
+ info("dtls_srtp: media=%s -- start DTLS %s\n",
+ sdp_media_name(sdpm), st->active ? "client" : "server");
+
+ if (!sdp_media_has_media(sdpm))
+ return 0;
+
+ err = component_start(&st->compv[0], sdpm);
+
+ if (!st->mux)
+ err |= component_start(&st->compv[1], sdpm);
+
+ if (err)
+ return err;
+
+ st->started = true;
+
+ return 0;
+}
+
+
+static void timeout(void *arg)
+{
+ struct dtls_srtp *st = arg;
+
+ media_start(st, st->sdpm);
+}
+
+
+static int media_alloc(struct menc_media **mp, struct menc_sess *sess,
+ struct rtp_sock *rtp, int proto,
+ void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct dtls_srtp *st;
+ const char *setup, *fingerprint;
+ int err = 0;
+ unsigned i;
+ (void)rtp;
+
+ if (!mp || !sess || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ st = (struct dtls_srtp *)*mp;
+ if (st)
+ goto setup;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sess = sess;
+ st->sdpm = mem_ref(sdpm);
+ st->compv[0].app_sock = mem_ref(rtpsock);
+ st->compv[1].app_sock = mem_ref(rtcpsock);
+
+ for (i=0; i<2; i++)
+ st->compv[i].ds = st;
+
+ st->compv[0].is_rtp = true;
+ st->compv[1].is_rtp = false;
+
+ err = sdp_media_set_alt_protos(st->sdpm, 4,
+ "RTP/SAVP",
+ "RTP/SAVPF",
+ "UDP/TLS/RTP/SAVP",
+ "UDP/TLS/RTP/SAVPF");
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+ else
+ *mp = (struct menc_media *)st;
+
+ setup:
+ st->mux = (rtpsock == rtcpsock) || (rtcpsock == NULL);
+
+ setup = sdp_media_session_rattr(st->sdpm, st->sess->sdp, "setup");
+ if (setup) {
+ st->active = !(0 == str_casecmp(setup, "active"));
+
+ /* note: we need to wait for ICE to settle ... */
+ tmr_start(&st->tmr, 100, timeout, st);
+ }
+
+ /* SDP offer/answer on fingerprint attribute */
+ fingerprint = sdp_media_session_rattr(st->sdpm, st->sess->sdp,
+ "fingerprint");
+ if (fingerprint) {
+
+ struct pl hash;
+
+ err = sdp_fingerprint_decode(fingerprint, &hash, NULL, NULL);
+ if (err)
+ return err;
+
+ if (0 == pl_strcasecmp(&hash, "SHA-1")) {
+ err = sdp_media_set_lattr(st->sdpm, true,
+ "fingerprint", "SHA-1 %H",
+ dtls_print_sha1_fingerprint,
+ tls);
+ }
+ else if (0 == pl_strcasecmp(&hash, "SHA-256")) {
+ err = sdp_media_set_lattr(st->sdpm, true,
+ "fingerprint", "SHA-256 %H",
+ dtls_print_sha256_fingerprint,
+ tls);
+ }
+ else {
+ info("dtls_srtp: unsupported fingerprint hash `%r'\n",
+ &hash);
+ return EPROTO;
+ }
+ }
+
+ return err;
+}
+
+
+static struct menc dtls_srtp = {
+ LE_INIT, "dtls_srtp", "UDP/TLS/RTP/SAVP", session_alloc, media_alloc
+};
+
+static struct menc dtls_srtpf = {
+ LE_INIT, "dtls_srtpf", "UDP/TLS/RTP/SAVPF", session_alloc, media_alloc
+};
+
+static struct menc dtls_srtp2 = {
+ /* note: temp for Webrtc interop */
+ LE_INIT, "srtp-mandf", "RTP/SAVPF", session_alloc, media_alloc
+};
+
+
+static int module_init(void)
+{
+ struct list *mencl = baresip_mencl();
+ int err;
+
+ err = tls_alloc(&tls, TLS_METHOD_DTLSV1, NULL, NULL);
+ if (err) {
+ warning("dtls_srtp: failed to create DTLS context (%m)\n",
+ err);
+ return err;
+ }
+
+ err = tls_set_selfsigned(tls, "dtls@baresip");
+ if (err) {
+ warning("dtls_srtp: failed to self-sign certificate (%m)\n",
+ err);
+ return err;
+ }
+
+ tls_set_verify_client(tls);
+
+ err = tls_set_srtp(tls, srtp_profiles);
+ if (err) {
+ warning("dtls_srtp: failed to enable SRTP profile (%m)\n",
+ err);
+ return err;
+ }
+
+ menc_register(mencl, &dtls_srtpf);
+ menc_register(mencl, &dtls_srtp);
+ menc_register(mencl, &dtls_srtp2);
+
+ debug("DTLS-SRTP ready with profiles %s\n", srtp_profiles);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ menc_unregister(&dtls_srtp);
+ menc_unregister(&dtls_srtpf);
+ menc_unregister(&dtls_srtp2);
+ tls = mem_deref(tls);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(dtls_srtp) = {
+ "dtls_srtp",
+ "menc",
+ module_init,
+ module_close
+};
diff --git a/modules/dtls_srtp/dtls_srtp.h b/modules/dtls_srtp/dtls_srtp.h
new file mode 100644
index 0000000..531134c
--- /dev/null
+++ b/modules/dtls_srtp/dtls_srtp.h
@@ -0,0 +1,33 @@
+/**
+ * @file dtls_srtp.h DTLS-SRTP Internal api
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ LAYER_SRTP = 20,
+ LAYER_DTLS = 20, /* must be above zero */
+};
+
+struct comp {
+ const struct dtls_srtp *ds; /* parent */
+ struct dtls_sock *dtls_sock;
+ struct tls_conn *tls_conn;
+ struct srtp_stream *tx;
+ struct srtp_stream *rx;
+ struct udp_helper *uh_srtp;
+ void *app_sock;
+ bool negotiated;
+ bool is_rtp;
+};
+
+/* dtls.c */
+int dtls_print_sha1_fingerprint(struct re_printf *pf, const struct tls *tls);
+int dtls_print_sha256_fingerprint(struct re_printf *pf, const struct tls *tls);
+
+
+/* srtp.c */
+int srtp_stream_add(struct srtp_stream **sp, enum srtp_suite suite,
+ const uint8_t *key, size_t key_size, bool tx);
+int srtp_install(struct comp *comp);
diff --git a/modules/dtls_srtp/module.mk b/modules/dtls_srtp/module.mk
new file mode 100644
index 0000000..4fb3628
--- /dev/null
+++ b/modules/dtls_srtp/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := dtls_srtp
+$(MOD)_SRCS += dtls_srtp.c srtp.c dtls.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/dtls_srtp/srtp.c b/modules/dtls_srtp/srtp.c
new file mode 100644
index 0000000..c5ca52a
--- /dev/null
+++ b/modules/dtls_srtp/srtp.c
@@ -0,0 +1,150 @@
+/**
+ * @file dtls_srtp/srtp.c Secure RTP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "dtls_srtp.h"
+
+
+struct srtp_stream {
+ struct srtp *srtp;
+};
+
+
+/*
+ * See RFC 5764 figure 3:
+ *
+ * +----------------+
+ * | 127 < B < 192 -+--> forward to RTP
+ * | |
+ * packet --> | 19 < B < 64 -+--> forward to DTLS
+ * | |
+ * | B < 2 -+--> forward to STUN
+ * +----------------+
+ *
+ */
+static inline bool is_rtp_or_rtcp(const struct mbuf *mb)
+{
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mbuf_buf(mb)[0];
+
+ return 127 < b && b < 192;
+}
+
+
+static inline bool is_rtcp_packet(const struct mbuf *mb)
+{
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return false;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ return 64 <= pt && pt <= 95;
+}
+
+
+static void destructor(void *arg)
+{
+ struct srtp_stream *s = arg;
+
+ mem_deref(s->srtp);
+}
+
+
+static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct comp *comp = arg;
+ (void)dst;
+
+ if (!is_rtp_or_rtcp(mb))
+ return false;
+
+ if (is_rtcp_packet(mb)) {
+ *err = srtcp_encrypt(comp->tx->srtp, mb);
+ if (*err) {
+ warning("srtp: srtcp_encrypt failed (%m)\n", *err);
+ }
+ }
+ else {
+ *err = srtp_encrypt(comp->tx->srtp, mb);
+ if (*err) {
+ warning("srtp: srtp_encrypt failed (%m)\n", *err);
+ }
+ }
+
+
+ return *err ? true : false; /* continue processing */
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct comp *comp = arg;
+ int err;
+ (void)src;
+
+ if (!is_rtp_or_rtcp(mb))
+ return false;
+
+ if (is_rtcp_packet(mb)) {
+ err = srtcp_decrypt(comp->rx->srtp, mb);
+ }
+ else {
+ err = srtp_decrypt(comp->rx->srtp, mb);
+ }
+
+ if (err) {
+ warning("srtp: recv: failed to decrypt %s-packet (%m)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP", err);
+ return true; /* error - drop packet */
+ }
+
+ return false; /* continue processing */
+}
+
+
+int srtp_stream_add(struct srtp_stream **sp, enum srtp_suite suite,
+ const uint8_t *key, size_t key_size, bool tx)
+{
+ struct srtp_stream *s;
+ int err = 0;
+ (void)tx;
+
+ if (!sp || !key)
+ return EINVAL;
+
+ s = mem_zalloc(sizeof(*s), destructor);
+ if (!s)
+ return ENOMEM;
+
+ err = srtp_alloc(&s->srtp, suite, key, key_size, 0);
+ if (err) {
+ warning("srtp: srtp_alloc() failed (%m)\n", err);
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(s);
+ else
+ *sp = s;
+
+ return err;
+}
+
+
+int srtp_install(struct comp *comp)
+{
+ return udp_register_helper(&comp->uh_srtp, comp->app_sock,
+ LAYER_SRTP,
+ send_handler, recv_handler, comp);
+}
diff --git a/modules/dtmfio/dtmfio.c b/modules/dtmfio/dtmfio.c
new file mode 100644
index 0000000..8df0a22
--- /dev/null
+++ b/modules/dtmfio/dtmfio.c
@@ -0,0 +1,149 @@
+/*************************************************************************/
+/* Copyright (c) 2014, Aaron Herting 'qwertos' <aaron@herting.cc> */
+/* Based upon code licensed under the same license by Creytiv.com */
+/* */
+/* All rights reserved. */
+/* */
+/* Redistribution and use in source and binary forms, with or without */
+/* modification, are permitted provided that the following conditions */
+/* are met: */
+/* */
+/* 1. Redistributions of source code must retain the above copyright */
+/* notice, this list of conditions and the following disclaimer. */
+/* */
+/* 2. Redistributions in binary form must reproduce the above copyright */
+/* notice, this list of conditions and the following disclaimer in the */
+/* documentation and/or other materials provided with the distribution. */
+/* */
+/* 3. Neither the name of the copyright holder nor the names of its */
+/* contributors may be used to endorse or promote products derived from */
+/* this software without specific prior written permission. */
+/* */
+/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
+/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
+/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR */
+/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */
+/* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */
+/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, */
+/* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS */
+/* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED */
+/* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT */
+/* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY */
+/* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */
+/* POSSIBILITY OF SUCH DAMAGE. */
+/*************************************************************************/
+
+
+/**
+ * @defgroup dtmfio dtmfio
+ *
+ * DTMF input/output module
+ *
+ *
+ * # DTMFIO Module
+ *
+ * ## Description
+ *
+ * Writes received dtmf button presses to a FIFO located at /tmp/dtmf.out.
+ *
+ * Also, will write an 'E' when a call is established and an 'F' when the
+ * call is finished.
+ *
+ * ## To Do
+ *
+ * + Proper error handling
+ * + Using a dtmf.in file, be able to send DTMF signals
+ * + Use a filename specified by the user in the config file
+ * + Clean up build output so there aren't errors regarding unused vars
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <re.h>
+#include <baresip.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+static FILE *fd;
+static const char *DTMF_OUT = "/tmp/dtmf.out";
+
+
+static void dtmf_handler(struct call *call, char key, void *arg)
+{
+ (void)call;
+ (void)arg;
+
+ if ( key != 0 ) {
+ fprintf(fd, "%c", key);
+ fflush(fd);
+ }
+}
+
+
+static void ua_event_handler(struct ua *ua,
+ enum ua_event ev,
+ struct call *call,
+ const char *prm,
+ void *arg )
+{
+ (void)ua;
+ (void)prm;
+ (void)arg;
+
+ if ( ev == UA_EVENT_CALL_ESTABLISHED ) {
+ fprintf(fd, "E");
+ fflush(fd);
+ call_set_handlers( call, NULL, dtmf_handler, NULL);
+ }
+
+ if (ev == UA_EVENT_CALL_CLOSED ) {
+ fprintf(fd, "F");
+ fflush(fd);
+ }
+}
+
+
+static int module_init(void)
+{
+ if ( mkfifo( DTMF_OUT, S_IWUSR | S_IRUSR ) ) {
+ int err = errno;
+ warning("Creation of the FIFO errored."
+ " This might cause issues. (%m)\n", err);
+ return err;
+ }
+
+ fd = fopen( DTMF_OUT , "w+" );
+
+ if ( fd == 0 ){
+ warning("Opening of the FIFO errored."
+ " This might cause issues.\n");
+ }
+
+ uag_event_register( ua_event_handler, NULL );
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ uag_event_unregister(ua_event_handler);
+
+ if (fd) {
+ fclose(fd);
+ fd = NULL;
+ }
+
+ unlink(DTMF_OUT);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(dtmfio) = {
+ "dtmfio",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/dtmfio/module.mk b/modules/dtmfio/module.mk
new file mode 100644
index 0000000..cb1264b
--- /dev/null
+++ b/modules/dtmfio/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+# Modified by Aaron Herting <aaron@herting.cc>
+#
+
+MOD := dtmfio
+$(MOD)_SRCS += dtmfio.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/echo/echo.c b/modules/echo/echo.c
new file mode 100644
index 0000000..91e587c
--- /dev/null
+++ b/modules/echo/echo.c
@@ -0,0 +1,158 @@
+/**
+ * @file echo.c Echo module
+ */
+#include <re.h>
+#include <baresip.h>
+
+/**
+ *
+ * Multi Call Echo module
+ *
+ * REQUIRES: aubridge
+ * NOTE: This module is experimental.
+ *
+ */
+
+struct session {
+ struct le le;
+ struct call *call_in;
+};
+
+
+static struct list sessionl;
+
+
+static void destructor(void *arg)
+{
+ struct session *sess = arg;
+
+ debug("echo: session destroyed (in=%p)\n",
+ sess->call_in);
+
+ list_unlink(&sess->le);
+ mem_deref(sess->call_in);
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *str, void *arg)
+{
+ struct session *sess = arg;
+ (void)call;
+
+ switch (ev) {
+
+ case CALL_EVENT_CLOSED:
+ debug("echo: CALL_CLOSED: %s\n", str);
+ mem_deref(sess);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void call_dtmf_handler(struct call *call, char key, void *arg)
+{
+ (void)arg;
+
+ debug("echo: relaying DTMF event: key = '%c'\n", key ? key : '.');
+
+ call_send_digit(call, key);
+}
+
+
+static int new_session(struct call *call)
+{
+ struct session *sess;
+ char a[64];
+ int err = 0;
+
+ sess = mem_zalloc(sizeof(*sess), destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->call_in = call;
+
+ re_snprintf(a, sizeof(a), "A-%x", sess);
+
+ audio_set_devicename(call_audio(sess->call_in), a, a);
+
+ call_set_handlers(sess->call_in, call_event_handler,
+ call_dtmf_handler, sess);
+
+ list_append(&sessionl, &sess->le, sess);
+ ua_answer(uag_current(), NULL);
+
+ if (err)
+ mem_deref(sess);
+
+ return err;
+}
+
+
+static void ua_event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ int err;
+ (void)prm;
+ (void)arg;
+
+ switch (ev) {
+
+ case UA_EVENT_CALL_INCOMING:
+ debug("echo: CALL_INCOMING: peer=%s --> local=%s\n",
+ call_peeruri(call),
+ call_localuri(call));
+
+ err = new_session(call);
+ if (err) {
+ ua_hangup(ua, call, 500, "Server Error");
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ list_init(&sessionl);
+
+ err = uag_event_register(ua_event_handler, 0);
+ if (err)
+ return err;
+
+ debug("echo: module loaded\n");
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ debug("echo: module closing..\n");
+
+ if (!list_isempty(&sessionl)) {
+
+ info("echo: flushing %u sessions\n", list_count(&sessionl));
+ list_flush(&sessionl);
+ }
+
+ uag_event_unregister(ua_event_handler);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(echo) = {
+ "echo",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/echo/module.mk b/modules/echo/module.mk
new file mode 100644
index 0000000..0c7ee6f
--- /dev/null
+++ b/modules/echo/module.mk
@@ -0,0 +1,9 @@
+#
+# module.mk
+#
+#
+
+MOD := echo
+$(MOD)_SRCS += echo.c
+
+include mk/mod.mk
diff --git a/modules/evdev/evdev.c b/modules/evdev/evdev.c
new file mode 100644
index 0000000..7d27f1e
--- /dev/null
+++ b/modules/evdev/evdev.c
@@ -0,0 +1,348 @@
+/**
+ * @file evdev.c Input event device UI module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <re.h>
+#include <baresip.h>
+#include "print.h"
+
+
+/**
+ * @defgroup evdev evdev
+ *
+ * User-Interface (UI) module using the Linux input subsystem.
+ *
+ * The following options can be configured:
+ *
+ \verbatim
+ evdev_device /dev/input/event0 # Name of the input device to use
+ \endverbatim
+ */
+
+
+struct ui_st {
+ int fd;
+};
+
+
+static struct ui_st *evdev;
+static char evdev_device[64] = "/dev/input/event0";
+
+
+static void evdev_close(struct ui_st *st)
+{
+ if (st->fd < 0)
+ return;
+
+ fd_close(st->fd);
+ (void)close(st->fd);
+ st->fd = -1;
+}
+
+
+static void evdev_destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ evdev_close(st);
+}
+
+
+static int code2ascii(uint16_t modifier, uint16_t code)
+{
+ switch (code) {
+
+ case KEY_0: return '0';
+ case KEY_1: return '1';
+ case KEY_2: return '2';
+ case KEY_3: return KEY_LEFTSHIFT==modifier ? '#' : '3';
+ case KEY_4: return '4';
+ case KEY_5: return '5';
+ case KEY_6: return '6';
+ case KEY_7: return '7';
+ case KEY_8: return '8';
+ case KEY_9: return '9';
+ case KEY_BACKSPACE: return '\b';
+ case KEY_ENTER: return '\n';
+ case KEY_ESC: return 0x1b;
+ case KEY_KPASTERISK: return '*';
+#ifdef KEY_NUMERIC_0
+ case KEY_NUMERIC_0: return '0';
+#endif
+#ifdef KEY_NUMERIC_1
+ case KEY_NUMERIC_1: return '1';
+#endif
+#ifdef KEY_NUMERIC_2
+ case KEY_NUMERIC_2: return '2';
+#endif
+#ifdef KEY_NUMERIC_3
+ case KEY_NUMERIC_3: return '3';
+#endif
+#ifdef KEY_NUMERIC_4
+ case KEY_NUMERIC_4: return '4';
+#endif
+#ifdef KEY_NUMERIC_5
+ case KEY_NUMERIC_5: return '5';
+#endif
+#ifdef KEY_NUMERIC_6
+ case KEY_NUMERIC_6: return '6';
+#endif
+#ifdef KEY_NUMERIC_7
+ case KEY_NUMERIC_7: return '7';
+#endif
+#ifdef KEY_NUMERIC_8
+ case KEY_NUMERIC_8: return '8';
+#endif
+#ifdef KEY_NUMERIC_9
+ case KEY_NUMERIC_9: return '9';
+#endif
+#ifdef KEY_NUMERIC_STAR
+ case KEY_NUMERIC_STAR: return '*';
+#endif
+#ifdef KEY_NUMERIC_POUND
+ case KEY_NUMERIC_POUND: return '#';
+#endif
+#ifdef KEY_KP0
+ case KEY_KP0: return '0';
+#endif
+#ifdef KEY_KP1
+ case KEY_KP1: return '1';
+#endif
+#ifdef KEY_KP2
+ case KEY_KP2: return '2';
+#endif
+#ifdef KEY_KP3
+ case KEY_KP3: return '3';
+#endif
+#ifdef KEY_KP4
+ case KEY_KP4: return '4';
+#endif
+#ifdef KEY_KP5
+ case KEY_KP5: return '5';
+#endif
+#ifdef KEY_KP6
+ case KEY_KP6: return '6';
+#endif
+#ifdef KEY_KP7
+ case KEY_KP7: return '7';
+#endif
+#ifdef KEY_KP8
+ case KEY_KP8: return '8';
+#endif
+#ifdef KEY_KP9
+ case KEY_KP9: return '9';
+#endif
+#ifdef KEY_KPDOT
+ case KEY_KPDOT: return 0x1b;
+#endif
+#ifdef KEY_KPENTER
+ case KEY_KPENTER: return '\n';
+#endif
+ default: return -1;
+ }
+}
+
+
+static int stderr_handler(const char *p, size_t sz, void *arg)
+{
+ (void)arg;
+
+ if (write(STDERR_FILENO, p, sz) < 0)
+ return errno;
+
+ return 0;
+}
+
+
+static void reportkey(struct ui_st *st, int ascii)
+{
+ static struct re_printf pf_stderr = {stderr_handler, NULL};
+ (void)st;
+
+ ui_input_key(baresip_uis(), ascii, &pf_stderr);
+}
+
+
+static void evdev_fd_handler(int flags, void *arg)
+{
+ struct ui_st *st = arg;
+ struct input_event evv[64]; /* the events (up to 64 at once) */
+ uint16_t modifier = 0;
+ size_t n;
+ int i;
+
+ /* This might happen if you unplug a USB device */
+ if (flags & FD_EXCEPT) {
+ warning("evdev: fd handler: FD_EXCEPT - device unplugged?\n");
+ evdev_close(st);
+ return;
+ }
+
+ n = read(st->fd, evv, sizeof(evv));
+
+ if (n < (int) sizeof(struct input_event)) {
+ warning("evdev: event: short read (%m)\n", errno);
+ return;
+ }
+
+ for (i = 0; i < (int) (n / sizeof(struct input_event)); i++) {
+ const struct input_event *ev = &evv[i];
+
+ if (EV_KEY != ev->type)
+ continue;
+
+ if (KEY_LEFTSHIFT == ev->code) {
+ modifier = KEY_LEFTSHIFT;
+ continue;
+ }
+
+ if (1 == ev->value) {
+ const int ascii = code2ascii(modifier, ev->code);
+ if (-1 == ascii) {
+ warning("evdev: unhandled key code %u\n",
+ ev->code);
+ }
+ else
+ reportkey(st, ascii);
+ modifier = 0;
+ }
+ else if (0 == ev->value) {
+ reportkey(st, KEYCODE_REL);
+ }
+ }
+}
+
+
+static int evdev_alloc(struct ui_st **stp, const char *dev)
+{
+ struct ui_st *st;
+ int err = 0;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), evdev_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->fd = open(dev, O_RDWR);
+ if (st->fd < 0) {
+ err = errno;
+ warning("evdev: failed to open device '%s' (%m)\n", dev, err);
+ goto out;
+ }
+
+#if 0
+ /* grab the event device to prevent it from propagating
+ its events to the regular keyboard driver */
+ if (-1 == ioctl(st->fd, EVIOCGRAB, (void *)1)) {
+ warning("evdev: ioctl EVIOCGRAB on %s (%m)\n", dev, errno);
+ }
+#endif
+
+ print_name(st->fd);
+ print_events(st->fd);
+ print_keys(st->fd);
+ print_leds(st->fd);
+
+ err = fd_listen(st->fd, FD_READ, evdev_fd_handler, st);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int buzz(const struct ui_st *st, int value)
+{
+ struct input_event ev;
+ ssize_t n;
+
+ ev.type = EV_SND;
+ ev.code = SND_BELL;
+ ev.value = value;
+
+ n = write(st->fd, &ev, sizeof(ev));
+ if (n < 0) {
+ warning("evdev: output: write fd=%d (%m)\n", st->fd, errno);
+ }
+
+ return errno;
+}
+
+
+static int evdev_output(const char *str)
+{
+ struct ui_st *st = evdev;
+ int err = 0;
+
+ if (!st || !str)
+ return EINVAL;
+
+ while (*str) {
+ switch (*str++) {
+
+ case '\a':
+ err |= buzz(st, 1);
+ break;
+
+ default:
+ err |= buzz(st, 0);
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static struct ui ui_evdev = {
+ .name = "evdev",
+ .outputh = evdev_output
+};
+
+
+static int module_init(void)
+{
+ int err;
+
+ conf_get_str(conf_cur(), "evdev_device",
+ evdev_device, sizeof(evdev_device));
+
+ err = evdev_alloc(&evdev, evdev_device);
+ if (err)
+ return err;
+
+ ui_register(baresip_uis(), &ui_evdev);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ ui_unregister(&ui_evdev);
+ evdev = mem_deref(evdev);
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(evdev) = {
+ "evdev",
+ "ui",
+ module_init,
+ module_close
+};
diff --git a/modules/evdev/module.mk b/modules/evdev/module.mk
new file mode 100644
index 0000000..5d9ede2
--- /dev/null
+++ b/modules/evdev/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := evdev
+$(MOD)_SRCS += evdev.c
+$(MOD)_SRCS += print.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/evdev/print.c b/modules/evdev/print.c
new file mode 100644
index 0000000..4c6cc77
--- /dev/null
+++ b/modules/evdev/print.c
@@ -0,0 +1,513 @@
+/**
+ * @file print.c Input event device info
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <re.h>
+#include <baresip.h>
+#include "print.h"
+
+
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+
+/**
+ * Print the name information
+ *
+ * @param fd Device file descriptor
+ */
+void print_name(int fd)
+{
+ char name[256]= "Unknown";
+
+ if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+ perror("evdev ioctl");
+ }
+
+ info("evdev: device name: %s\n", name);
+}
+
+
+/**
+ * Print supported events
+ *
+ * @param fd Device file descriptor
+ */
+void print_events(int fd)
+{
+ uint8_t evtype_bitmask[EV_MAX/8 + 1];
+ int i;
+
+ memset(evtype_bitmask, 0, sizeof(evtype_bitmask));
+ if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) {
+ warning("evdev: ioctl EVIOCGBIT (%m)\n", errno);
+ return;
+ }
+
+ printf("Supported event types:\n");
+
+ for (i = 0; i < EV_MAX; i++) {
+ if (!test_bit(i, evtype_bitmask))
+ continue;
+
+ printf(" Event type 0x%02x ", i);
+
+ switch (i) {
+
+ case EV_KEY :
+ printf(" (Keys or Buttons)\n");
+ break;
+ case EV_REL :
+ printf(" (Relative Axes)\n");
+ break;
+ case EV_ABS :
+ printf(" (Absolute Axes)\n");
+ break;
+ case EV_MSC :
+ printf(" (Something miscellaneous)\n");
+ break;
+ case EV_LED :
+ printf(" (LEDs)\n");
+ break;
+ case EV_SND :
+ printf(" (Sounds)\n");
+ break;
+ case EV_REP :
+ printf(" (Repeat)\n");
+ break;
+ case EV_FF :
+ printf(" (Force Feedback)\n");
+ break;
+ default:
+ printf(" (Unknown event type: 0x%04x)\n", i);
+ break;
+ }
+ }
+}
+
+
+/**
+ * Print supported keys
+ *
+ * @param fd Device file descriptor
+ */
+void print_keys(int fd)
+{
+ uint8_t key_bitmask[KEY_MAX/8 + 1];
+ int i;
+
+ memset(key_bitmask, 0, sizeof(key_bitmask));
+ if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)),
+ key_bitmask) < 0) {
+ perror("evdev ioctl");
+ }
+
+ printf("Supported Keys:\n");
+
+ for (i = 0; i < KEY_MAX; i++) {
+ if (!test_bit(i, key_bitmask))
+ continue;
+
+ printf(" Key 0x%02x ", i);
+
+ switch (i) {
+
+ case KEY_RESERVED : printf(" (Reserved)\n"); break;
+ case KEY_ESC : printf(" (Escape)\n"); break;
+ case KEY_1 : printf(" (1)\n"); break;
+ case KEY_2 : printf(" (2)\n"); break;
+ case KEY_3 : printf(" (3)\n"); break;
+ case KEY_4 : printf(" (4)\n"); break;
+ case KEY_5 : printf(" (5)\n"); break;
+ case KEY_6 : printf(" (6)\n"); break;
+ case KEY_7 : printf(" (7)\n"); break;
+ case KEY_8 : printf(" (8)\n"); break;
+ case KEY_9 : printf(" ()\n"); break;
+ case KEY_0 : printf(" ()\n"); break;
+ case KEY_MINUS : printf(" (-)\n"); break;
+ case KEY_EQUAL : printf(" (=)\n"); break;
+ case KEY_BACKSPACE : printf(" (Backspace)\n"); break;
+ case KEY_TAB : printf(" (Tab)\n"); break;
+ case KEY_Q : printf(" (Q)\n"); break;
+ case KEY_W : printf(" (W)\n"); break;
+ case KEY_E : printf(" (E)\n"); break;
+ case KEY_R : printf(" (R)\n"); break;
+ case KEY_T : printf(" (T)\n"); break;
+ case KEY_Y : printf(" (Y)\n"); break;
+ case KEY_U : printf(" (U)\n"); break;
+ case KEY_I : printf(" (I)\n"); break;
+ case KEY_O : printf(" (O)\n"); break;
+ case KEY_P : printf(" (P)\n"); break;
+ case KEY_LEFTBRACE : printf(" ([)\n"); break;
+ case KEY_RIGHTBRACE : printf(" (])\n"); break;
+ case KEY_ENTER : printf(" (Enter)\n"); break;
+ case KEY_LEFTCTRL : printf(" (LH Control)\n"); break;
+ case KEY_A : printf(" (A)\n"); break;
+ case KEY_S : printf(" (S)\n"); break;
+ case KEY_D : printf(" (D)\n"); break;
+ case KEY_F : printf(" (F)\n"); break;
+ case KEY_G : printf(" (G)\n"); break;
+ case KEY_H : printf(" (H)\n"); break;
+ case KEY_J : printf(" (J)\n"); break;
+ case KEY_K : printf(" (K)\n"); break;
+ case KEY_L : printf(" (L)\n"); break;
+ case KEY_SEMICOLON : printf(" (;)\n"); break;
+ case KEY_APOSTROPHE : printf(" (')\n"); break;
+ case KEY_GRAVE : printf(" (`)\n"); break;
+ case KEY_LEFTSHIFT : printf(" (LH Shift)\n"); break;
+ case KEY_BACKSLASH : printf(" (\\)\n"); break;
+ case KEY_Z : printf(" (Z)\n"); break;
+ case KEY_X : printf(" (X)\n"); break;
+ case KEY_C : printf(" (C)\n"); break;
+ case KEY_V : printf(" (V)\n"); break;
+ case KEY_B : printf(" (B)\n"); break;
+ case KEY_N : printf(" (N)\n"); break;
+ case KEY_M : printf(" (M)\n"); break;
+ case KEY_COMMA : printf(" (,)\n"); break;
+ case KEY_DOT : printf(" (.)\n"); break;
+ case KEY_SLASH : printf(" (/)\n"); break;
+ case KEY_RIGHTSHIFT : printf(" (RH Shift)\n"); break;
+ case KEY_KPASTERISK : printf(" (*)\n"); break;
+ case KEY_LEFTALT : printf(" (LH Alt)\n"); break;
+ case KEY_SPACE : printf(" (Space)\n"); break;
+ case KEY_CAPSLOCK : printf(" (CapsLock)\n"); break;
+ case KEY_F1 : printf(" (F1)\n"); break;
+ case KEY_F2 : printf(" (F2)\n"); break;
+ case KEY_F3 : printf(" (F3)\n"); break;
+ case KEY_F4 : printf(" (F4)\n"); break;
+ case KEY_F5 : printf(" (F5)\n"); break;
+ case KEY_F6 : printf(" (F6)\n"); break;
+ case KEY_F7 : printf(" (F7)\n"); break;
+ case KEY_F8 : printf(" (F8)\n"); break;
+ case KEY_F9 : printf(" (F9)\n"); break;
+ case KEY_F10 : printf(" (F10)\n"); break;
+ case KEY_NUMLOCK : printf(" (NumLock)\n"); break;
+ case KEY_SCROLLLOCK : printf(" (ScrollLock)\n"); break;
+ case KEY_KP7 : printf(" (KeyPad 7)\n"); break;
+ case KEY_KP8 : printf(" (KeyPad 8)\n"); break;
+ case KEY_KP9 : printf(" (Keypad 9)\n"); break;
+ case KEY_KPMINUS : printf(" (KeyPad Minus)\n"); break;
+ case KEY_KP4 : printf(" (KeyPad 4)\n"); break;
+ case KEY_KP5 : printf(" (KeyPad 5)\n"); break;
+ case KEY_KP6 : printf(" (KeyPad 6)\n"); break;
+ case KEY_KPPLUS : printf(" (KeyPad Plus)\n"); break;
+ case KEY_KP1 : printf(" (KeyPad 1)\n"); break;
+ case KEY_KP2 : printf(" (KeyPad 2)\n"); break;
+ case KEY_KP3 : printf(" (KeyPad 3)\n"); break;
+ case KEY_KPDOT : printf(" (KeyPad decimal point)\n"); break;
+/* case KEY_103RD : printf(" (Huh?)\n"); break; */
+ case KEY_F13 : printf(" (F13)\n"); break;
+ case KEY_102ND : printf(" (Beats me...)\n"); break;
+ case KEY_F11 : printf(" (F11)\n"); break;
+ case KEY_F12 : printf(" (F12)\n"); break;
+ case KEY_F14 : printf(" (F14)\n"); break;
+ case KEY_F15 : printf(" (F15)\n"); break;
+ case KEY_F16 : printf(" (F16)\n"); break;
+ case KEY_F17 : printf(" (F17)\n"); break;
+ case KEY_F18 : printf(" (F18)\n"); break;
+ case KEY_F19 : printf(" (F19)\n"); break;
+ case KEY_F20 : printf(" (F20)\n"); break;
+ case KEY_KPENTER : printf(" (Keypad Enter)\n"); break;
+ case KEY_RIGHTCTRL : printf(" (RH Control)\n"); break;
+ case KEY_KPSLASH : printf(" (KeyPad Forward Slash)\n"); break;
+ case KEY_SYSRQ : printf(" (System Request)\n"); break;
+ case KEY_RIGHTALT : printf(" (RH Alternate)\n"); break;
+ case KEY_LINEFEED : printf(" (Line Feed)\n"); break;
+ case KEY_HOME : printf(" (Home)\n"); break;
+ case KEY_UP : printf(" (Up)\n"); break;
+ case KEY_PAGEUP : printf(" (Page Up)\n"); break;
+ case KEY_LEFT : printf(" (Left)\n"); break;
+ case KEY_RIGHT : printf(" (Right)\n"); break;
+ case KEY_END : printf(" (End)\n"); break;
+ case KEY_DOWN : printf(" (Down)\n"); break;
+ case KEY_PAGEDOWN : printf(" (Page Down)\n"); break;
+ case KEY_INSERT : printf(" (Insert)\n"); break;
+ case KEY_DELETE : printf(" (Delete)\n"); break;
+ case KEY_MACRO : printf(" (Macro)\n"); break;
+ case KEY_MUTE : printf(" (Mute)\n"); break;
+ case KEY_VOLUMEDOWN : printf(" (Volume Down)\n"); break;
+ case KEY_VOLUMEUP : printf(" (Volume Up)\n"); break;
+ case KEY_POWER : printf(" (Power)\n"); break;
+ case KEY_KPEQUAL : printf(" (KeyPad Equal)\n"); break;
+ case KEY_KPPLUSMINUS : printf(" (KeyPad +/-)\n"); break;
+ case KEY_PAUSE : printf(" (Pause)\n"); break;
+ case KEY_F21 : printf(" (F21)\n"); break;
+ case KEY_F22 : printf(" (F22)\n"); break;
+ case KEY_F23 : printf(" (F23)\n"); break;
+ case KEY_F24 : printf(" (F24)\n"); break;
+ case KEY_KPCOMMA : printf(" (KeyPad comma)\n"); break;
+ case KEY_LEFTMETA : printf(" (LH Meta)\n"); break;
+ case KEY_RIGHTMETA : printf(" (RH Meta)\n"); break;
+ case KEY_COMPOSE : printf(" (Compose)\n"); break;
+ case KEY_STOP : printf(" (Stop)\n"); break;
+ case KEY_AGAIN : printf(" (Again)\n"); break;
+ case KEY_PROPS : printf(" (Properties)\n"); break;
+ case KEY_UNDO : printf(" (Undo)\n"); break;
+ case KEY_FRONT : printf(" (Front)\n"); break;
+ case KEY_COPY : printf(" (Copy)\n"); break;
+ case KEY_OPEN : printf(" (Open)\n"); break;
+ case KEY_PASTE : printf(" (Paste)\n"); break;
+ case KEY_FIND : printf(" (Find)\n"); break;
+ case KEY_CUT : printf(" (Cut)\n"); break;
+ case KEY_HELP : printf(" (Help)\n"); break;
+ case KEY_MENU : printf(" (Menu)\n"); break;
+ case KEY_CALC : printf(" (Calculator)\n"); break;
+ case KEY_SETUP : printf(" (Setup)\n"); break;
+ case KEY_SLEEP : printf(" (Sleep)\n"); break;
+ case KEY_WAKEUP : printf(" (Wakeup)\n"); break;
+ case KEY_FILE : printf(" (File)\n"); break;
+ case KEY_SENDFILE : printf(" (Send File)\n"); break;
+ case KEY_DELETEFILE : printf(" (Delete File)\n"); break;
+ case KEY_XFER : printf(" (Transfer)\n"); break;
+ case KEY_PROG1 : printf(" (Program 1)\n"); break;
+ case KEY_PROG2 : printf(" (Program 2)\n"); break;
+ case KEY_WWW : printf(" (Web Browser)\n"); break;
+ case KEY_MSDOS : printf(" (DOS mode)\n"); break;
+ case KEY_COFFEE : printf(" (Coffee)\n"); break;
+ case KEY_DIRECTION : printf(" (Direction)\n"); break;
+ case KEY_CYCLEWINDOWS : printf(" (Window cycle)\n"); break;
+ case KEY_MAIL : printf(" (Mail)\n"); break;
+ case KEY_BOOKMARKS : printf(" (Book Marks)\n"); break;
+ case KEY_COMPUTER : printf(" (Computer)\n"); break;
+ case KEY_BACK : printf(" (Back)\n"); break;
+ case KEY_FORWARD : printf(" (Forward)\n"); break;
+ case KEY_CLOSECD : printf(" (Close CD)\n"); break;
+ case KEY_EJECTCD : printf(" (Eject CD)\n"); break;
+ case KEY_EJECTCLOSECD : printf(" (Eject / Close CD)\n"); break;
+ case KEY_NEXTSONG : printf(" (Next Song)\n"); break;
+ case KEY_PLAYPAUSE : printf(" (Play and Pause)\n"); break;
+ case KEY_PREVIOUSSONG : printf(" (Previous Song)\n"); break;
+ case KEY_STOPCD : printf(" (Stop CD)\n"); break;
+ case KEY_RECORD : printf(" (Record)\n"); break;
+ case KEY_REWIND : printf(" (Rewind)\n"); break;
+ case KEY_PHONE : printf(" (Phone)\n"); break;
+ case KEY_ISO : printf(" (ISO)\n"); break;
+ case KEY_CONFIG : printf(" (Config)\n"); break;
+ case KEY_HOMEPAGE : printf(" (Home)\n"); break;
+ case KEY_REFRESH : printf(" (Refresh)\n"); break;
+ case KEY_EXIT : printf(" (Exit)\n"); break;
+ case KEY_MOVE : printf(" (Move)\n"); break;
+ case KEY_EDIT : printf(" (Edit)\n"); break;
+ case KEY_SCROLLUP : printf(" (Scroll Up)\n"); break;
+ case KEY_SCROLLDOWN : printf(" (Scroll Down)\n"); break;
+ case KEY_KPLEFTPAREN : printf(" (KeyPad LH paren)\n"); break;
+ case KEY_KPRIGHTPAREN : printf(" (KeyPad RH paren)\n"); break;
+#if 0
+ case KEY_INTL1 : printf(" (Intl 1)\n"); break;
+ case KEY_INTL2 : printf(" (Intl 2)\n"); break;
+ case KEY_INTL3 : printf(" (Intl 3)\n"); break;
+ case KEY_INTL4 : printf(" (Intl 4)\n"); break;
+ case KEY_INTL5 : printf(" (Intl 5)\n"); break;
+ case KEY_INTL6 : printf(" (Intl 6)\n"); break;
+ case KEY_INTL7 : printf(" (Intl 7)\n"); break;
+ case KEY_INTL8 : printf(" (Intl 8)\n"); break;
+ case KEY_INTL9 : printf(" (Intl 9)\n"); break;
+ case KEY_LANG1 : printf(" (Language 1)\n"); break;
+ case KEY_LANG2 : printf(" (Language 2)\n"); break;
+ case KEY_LANG3 : printf(" (Language 3)\n"); break;
+ case KEY_LANG4 : printf(" (Language 4)\n"); break;
+ case KEY_LANG5 : printf(" (Language 5)\n"); break;
+ case KEY_LANG6 : printf(" (Language 6)\n"); break;
+ case KEY_LANG7 : printf(" (Language 7)\n"); break;
+ case KEY_LANG8 : printf(" (Language 8)\n"); break;
+ case KEY_LANG9 : printf(" (Language 9)\n"); break;
+#endif
+ case KEY_PLAYCD : printf(" (Play CD)\n"); break;
+ case KEY_PAUSECD : printf(" (Pause CD)\n"); break;
+ case KEY_PROG3 : printf(" (Program 3)\n"); break;
+ case KEY_PROG4 : printf(" (Program 4)\n"); break;
+ case KEY_SUSPEND : printf(" (Suspend)\n"); break;
+ case KEY_CLOSE : printf(" (Close)\n"); break;
+ case KEY_UNKNOWN : printf(" (Specifically unknown)\n"); break;
+#ifdef KEY_BRIGHTNESSDOWN
+ case KEY_BRIGHTNESSDOWN: printf(" (Brightness Down)\n");break;
+#endif
+#ifdef KEY_BRIGHTNESSUP
+ case KEY_BRIGHTNESSUP : printf(" (Brightness Up)\n"); break;
+#endif
+ case BTN_0 : printf(" (Button 0)\n"); break;
+ case BTN_1 : printf(" (Button 1)\n"); break;
+ case BTN_2 : printf(" (Button 2)\n"); break;
+ case BTN_3 : printf(" (Button 3)\n"); break;
+ case BTN_4 : printf(" (Button 4)\n"); break;
+ case BTN_5 : printf(" (Button 5)\n"); break;
+ case BTN_6 : printf(" (Button 6)\n"); break;
+ case BTN_7 : printf(" (Button 7)\n"); break;
+ case BTN_8 : printf(" (Button 8)\n"); break;
+ case BTN_9 : printf(" (Button 9)\n"); break;
+ case BTN_LEFT : printf(" (Left Button)\n"); break;
+ case BTN_RIGHT : printf(" (Right Button)\n"); break;
+ case BTN_MIDDLE : printf(" (Middle Button)\n"); break;
+ case BTN_SIDE : printf(" (Side Button)\n"); break;
+ case BTN_EXTRA : printf(" (Extra Button)\n"); break;
+ case BTN_FORWARD : printf(" (Forward Button)\n"); break;
+ case BTN_BACK : printf(" (Back Button)\n"); break;
+ case BTN_TRIGGER : printf(" (Trigger Button)\n"); break;
+ case BTN_THUMB : printf(" (Thumb Button)\n"); break;
+ case BTN_THUMB2 : printf(" (Second Thumb Button)\n"); break;
+ case BTN_TOP : printf(" (Top Button)\n"); break;
+ case BTN_TOP2 : printf(" (Second Top Button)\n"); break;
+ case BTN_PINKIE : printf(" (Pinkie Button)\n"); break;
+ case BTN_BASE : printf(" (Base Button)\n"); break;
+ case BTN_BASE2 : printf(" (Second Base Button)\n"); break;
+ case BTN_BASE3 : printf(" (Third Base Button)\n"); break;
+ case BTN_BASE4 : printf(" (Fourth Base Button)\n"); break;
+ case BTN_BASE5 : printf(" (Fifth Base Button)\n"); break;
+ case BTN_BASE6 : printf(" (Sixth Base Button)\n"); break;
+ case BTN_DEAD : printf(" (Dead Button)\n"); break;
+ case BTN_A : printf(" (Button A)\n"); break;
+ case BTN_B : printf(" (Button B)\n"); break;
+ case BTN_C : printf(" (Button C)\n"); break;
+ case BTN_X : printf(" (Button X)\n"); break;
+ case BTN_Y : printf(" (Button Y)\n"); break;
+ case BTN_Z : printf(" (Button Z)\n"); break;
+ case BTN_TL : printf(" (Thumb Left Button)\n"); break;
+ case BTN_TR : printf(" (Thumb Right Button )\n"); break;
+ case BTN_TL2 : printf(" (Second Thumb Left Button)\n"); break;
+ case BTN_TR2 : printf(" (Second Thumb Right Button )\n");
+ break;
+ case BTN_SELECT : printf(" (Select Button)\n"); break;
+ case BTN_MODE : printf(" (Mode Button)\n"); break;
+ case BTN_THUMBL : printf(" (Another Left Thumb Button )\n");
+ break;
+ case BTN_THUMBR : printf(" (Another Right Thumb Button )\n");
+ break;
+ case BTN_TOOL_PEN : printf(" (Digitiser Pen Tool)\n"); break;
+ case BTN_TOOL_RUBBER : printf(" (Digitiser Rubber Tool)\n");
+ break;
+ case BTN_TOOL_BRUSH : printf(" (Digitiser Brush Tool)\n");
+ break;
+ case BTN_TOOL_PENCIL : printf(" (Digitiser Pencil Tool)\n");
+ break;
+ case BTN_TOOL_AIRBRUSH:printf(" (Digitiser Airbrush Tool)\n");
+ break;
+ case BTN_TOOL_FINGER : printf(" (Digitiser Finger Tool)\n");
+ break;
+ case BTN_TOOL_MOUSE : printf(" (Digitiser Mouse Tool)\n");
+ break;
+ case BTN_TOOL_LENS : printf(" (Digitiser Lens Tool)\n"); break;
+ case BTN_TOUCH : printf(" (Digitiser Touch Button )\n"); break;
+ case BTN_STYLUS : printf(" (Digitiser Stylus Button )\n");
+ break;
+ case BTN_STYLUS2: printf(" (Second Digitiser Stylus Btn)\n");
+ break;
+#ifdef KEY_NUMERIC_0
+ case KEY_NUMERIC_0: printf(" (Numeric 0)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_1
+ case KEY_NUMERIC_1: printf(" (Numeric 1)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_2
+ case KEY_NUMERIC_2: printf(" (Numeric 2)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_3
+ case KEY_NUMERIC_3: printf(" (Numeric 3)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_4
+ case KEY_NUMERIC_4: printf(" (Numeric 4)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_5
+ case KEY_NUMERIC_5: printf(" (Numeric 5)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_6
+ case KEY_NUMERIC_6: printf(" (Numeric 6)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_7
+ case KEY_NUMERIC_7: printf(" (Numeric 7)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_8
+ case KEY_NUMERIC_8: printf(" (Numeric 8)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_9
+ case KEY_NUMERIC_9: printf(" (Numeric 9)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_STAR
+ case KEY_NUMERIC_STAR: printf(" (Numeric *)\n");
+ break;
+#endif
+#ifdef KEY_NUMERIC_POUND
+ case KEY_NUMERIC_POUND: printf(" (Numeric #)\n");
+ break;
+#endif
+ default:
+ printf(" (Unknown key)\n");
+ }
+ }
+}
+
+
+/**
+ * Print supported LEDs
+ *
+ * @param fd Device file descriptor
+ */
+void print_leds(int fd)
+{
+ uint8_t led_bitmask[LED_MAX/8 + 1];
+ int i;
+
+ memset(led_bitmask, 0, sizeof(led_bitmask));
+ if (ioctl(fd, EVIOCGBIT(EV_LED, sizeof(led_bitmask)),
+ led_bitmask) < 0) {
+ perror("evdev ioctl");
+ }
+
+ printf("Supported LEDs:\n");
+
+ for (i = 0; i < LED_MAX; i++) {
+ if (!test_bit(i, led_bitmask))
+ continue;
+
+ printf(" LED type 0x%02x ", i);
+
+ switch (i) {
+
+ case LED_NUML :
+ printf(" (Num Lock)\n");
+ break;
+ case LED_CAPSL :
+ printf(" (Caps Lock)\n");
+ break;
+ case LED_SCROLLL :
+ printf(" (Scroll Lock)\n");
+ break;
+ case LED_COMPOSE :
+ printf(" (Compose)\n");
+ break;
+ case LED_KANA :
+ printf(" (Kana)\n");
+ break;
+ case LED_SLEEP :
+ printf(" (Sleep)\n");
+ break;
+ case LED_SUSPEND :
+ printf(" (Suspend)\n");
+ break;
+ case LED_MUTE :
+ printf(" (Mute)\n");
+ break;
+ case LED_MISC :
+ printf(" (Miscellaneous)\n");
+ break;
+ default:
+ printf(" (Unknown LED type: 0x%04x)\n", i);
+ }
+ }
+}
diff --git a/modules/evdev/print.h b/modules/evdev/print.h
new file mode 100644
index 0000000..49acfcf
--- /dev/null
+++ b/modules/evdev/print.h
@@ -0,0 +1,11 @@
+/**
+ * @file print.h Interface to Input event device info
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+void print_name(int fd);
+void print_events(int fd);
+void print_keys(int fd);
+void print_leds(int fd);
diff --git a/modules/fakevideo/fakevideo.c b/modules/fakevideo/fakevideo.c
new file mode 100644
index 0000000..e0552d6
--- /dev/null
+++ b/modules/fakevideo/fakevideo.c
@@ -0,0 +1,199 @@
+/**
+ * @file fakevideo.c Fake video source and video display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup fakevideo fakevideo
+ *
+ * Fake video source and display module
+ *
+ * This module can be used to generate fake video input frames, and to
+ * send output video frames to a fake non-existant display.
+ *
+ * Example config:
+ \verbatim
+ video_source fakevideo,nil
+ video_display fakevideo,nil
+ \endverbatim
+ */
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+ struct vidframe *frame;
+ pthread_t thread;
+ bool run;
+ int fps;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance */
+};
+
+
+static struct vidsrc *vidsrc;
+static struct vidisp *vidisp;
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ uint64_t ts = tmr_jiffies();
+
+ while (st->run) {
+
+ if (tmr_jiffies() < ts) {
+ sys_msleep(4);
+ continue;
+ }
+
+ st->frameh(st->frame, st->arg);
+
+ ts += (1000/st->fps);
+ }
+
+ return NULL;
+}
+
+
+static void src_destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ mem_deref(st->frame);
+}
+
+
+static void disp_destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+ (void)st;
+}
+
+
+static int src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), src_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->fps = prm->fps;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int disp_alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ (void)prm;
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ if (!stp || !vd)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), disp_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ *stp = st;
+
+ return 0;
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ (void)st;
+ (void)title;
+ (void)frame;
+
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ int err = 0;
+ err |= vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "fakevideo", src_alloc, NULL);
+ err |= vidisp_register(&vidisp, baresip_vidispl(),
+ "fakevideo", disp_alloc, NULL,
+ display, NULL);
+ return err;
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ vidisp = mem_deref(vidisp);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(fakevideo) = {
+ "fakevideo",
+ "fakevideo",
+ module_init,
+ module_close
+};
diff --git a/modules/fakevideo/module.mk b/modules/fakevideo/module.mk
new file mode 100644
index 0000000..fbe1e63
--- /dev/null
+++ b/modules/fakevideo/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := fakevideo
+$(MOD)_SRCS += fakevideo.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/g711/g711.c b/modules/g711/g711.c
new file mode 100644
index 0000000..7c01b61
--- /dev/null
+++ b/modules/g711/g711.c
@@ -0,0 +1,133 @@
+/**
+ * @file g711.c G.711 Audio Codec
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup g711 g711
+ *
+ * The G.711 audio codec
+ */
+
+
+static int pcmu_encode(struct auenc_state *aes, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ (void)aes;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < sampc)
+ return ENOMEM;
+
+ *len = sampc;
+
+ while (sampc--)
+ *buf++ = g711_pcm2ulaw(*sampv++);
+
+ return 0;
+}
+
+
+static int pcmu_decode(struct audec_state *ads, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ (void)ads;
+
+ if (!sampv || !sampc || !buf)
+ return EINVAL;
+
+ if (*sampc < len)
+ return ENOMEM;
+
+ *sampc = len;
+
+ while (len--)
+ *sampv++ = g711_ulaw2pcm(*buf++);
+
+ return 0;
+}
+
+
+static int pcma_encode(struct auenc_state *aes, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ (void)aes;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < sampc)
+ return ENOMEM;
+
+ *len = sampc;
+
+ while (sampc--)
+ *buf++ = g711_pcm2alaw(*sampv++);
+
+ return 0;
+}
+
+
+static int pcma_decode(struct audec_state *ads, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ (void)ads;
+
+ if (!sampv || !sampc || !buf)
+ return EINVAL;
+
+ if (*sampc < len)
+ return ENOMEM;
+
+ *sampc = len;
+
+ while (len--)
+ *sampv++ = g711_alaw2pcm(*buf++);
+
+ return 0;
+}
+
+
+static struct aucodec pcmu = {
+ LE_INIT, "0", "PCMU", 8000, 8000, 1, NULL,
+ NULL, pcmu_encode, NULL, pcmu_decode, NULL, NULL, NULL
+};
+
+static struct aucodec pcma = {
+ LE_INIT, "8", "PCMA", 8000, 8000, 1, NULL,
+ NULL, pcma_encode, NULL, pcma_decode, NULL, NULL, NULL
+};
+
+
+static int module_init(void)
+{
+ aucodec_register(baresip_aucodecl(), &pcmu);
+ aucodec_register(baresip_aucodecl(), &pcma);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&pcma);
+ aucodec_unregister(&pcmu);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(g711) = {
+ "g711",
+ "audio codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/g711/module.mk b/modules/g711/module.mk
new file mode 100644
index 0000000..432269d
--- /dev/null
+++ b/modules/g711/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := g711
+$(MOD)_SRCS += g711.c
+
+include mk/mod.mk
diff --git a/modules/g722/g722.c b/modules/g722/g722.c
new file mode 100644
index 0000000..16d1f98
--- /dev/null
+++ b/modules/g722/g722.c
@@ -0,0 +1,191 @@
+/**
+ * @file g722.c G.722 audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1
+#include <spandsp.h>
+
+
+/**
+ * @defgroup g722 g722
+ *
+ * The G.722 audio codec
+ *
+ * ## From RFC 3551:
+
+ 4.5.2 G722
+
+ G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding
+ within 64 kbit/s". The G.722 encoder produces a stream of octets,
+ each of which SHALL be octet-aligned in an RTP packet. The first bit
+ transmitted in the G.722 octet, which is the most significant bit of
+ the higher sub-band sample, SHALL correspond to the most significant
+ bit of the octet in the RTP packet.
+
+ Even though the actual sampling rate for G.722 audio is 16,000 Hz,
+ the RTP clock rate for the G722 payload format is 8,000 Hz because
+ that value was erroneously assigned in RFC 1890 and must remain
+ unchanged for backward compatibility. The octet rate or sample-pair
+ rate is 8,000 Hz.
+
+ ## Reference:
+
+ http://www.soft-switch.org/spandsp-modules.html
+
+ */
+
+
+enum {
+ G722_SAMPLE_RATE = 16000,
+ G722_BITRATE_48k = 48000,
+ G722_BITRATE_56k = 56000,
+ G722_BITRATE_64k = 64000
+};
+
+
+struct auenc_state {
+ g722_encode_state_t enc;
+};
+
+struct audec_state {
+ g722_decode_state_t dec;
+};
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ if (*aesp)
+ return 0;
+
+ st = mem_alloc(sizeof(*st), NULL);
+ if (!st)
+ return ENOMEM;
+
+ if (!g722_encode_init(&st->enc, G722_BITRATE_64k, 0)) {
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ if (*adsp)
+ return 0;
+
+ st = mem_alloc(sizeof(*st), NULL);
+ if (!st)
+ return ENOMEM;
+
+ if (!g722_decode_init(&st->dec, G722_BITRATE_64k, 0)) {
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int n;
+
+ n = g722_encode(&st->enc, buf, sampv, (int)sampc);
+ if (n <= 0) {
+ return EPROTO;
+ }
+ else if (n > (int)*len) {
+ return EOVERFLOW;
+ }
+
+ *len = n;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int n;
+
+ if (!st || !sampv || !buf)
+ return EINVAL;
+
+ n = g722_decode(&st->dec, sampv, buf, (int)len);
+ if (n < 0)
+ return EPROTO;
+
+ *sampc = n;
+
+ return 0;
+}
+
+
+static struct aucodec g722 = {
+ LE_INIT, "9", "G722", 16000, 8000, 1, NULL,
+ encode_update, encode,
+ decode_update, decode, NULL,
+ NULL, NULL
+};
+
+
+static int module_init(void)
+{
+ aucodec_register(baresip_aucodecl(), &g722);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&g722);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(g722) = {
+ "g722",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/g722/module.mk b/modules/g722/module.mk
new file mode 100644
index 0000000..f56dd07
--- /dev/null
+++ b/modules/g722/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := g722
+$(MOD)_SRCS += g722.c
+$(MOD)_LFLAGS += -lspandsp
+
+include mk/mod.mk
diff --git a/modules/g7221/decode.c b/modules/g7221/decode.c
new file mode 100644
index 0000000..0f7155f
--- /dev/null
+++ b/modules/g7221/decode.c
@@ -0,0 +1,70 @@
+/**
+ * @file g7221/decode.c G.722.1 Decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+#define G722_1_EXPOSE_INTERNAL_STRUCTURES
+
+#include <g722_1.h>
+#include "g7221.h"
+
+
+struct audec_state {
+ g722_1_decode_state_t dec;
+};
+
+
+int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp)
+{
+ const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac;
+ struct audec_state *ads;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ ads = *adsp;
+
+ if (ads)
+ return 0;
+
+ ads = mem_alloc(sizeof(*ads), NULL);
+ if (!ads)
+ return ENOMEM;
+
+ if (!g722_1_decode_init(&ads->dec, g7221->bitrate, ac->srate)) {
+ mem_deref(ads);
+ return EPROTO;
+ }
+
+ *adsp = ads;
+
+ return 0;
+}
+
+
+int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ size_t framec;
+
+ if (!ads || !sampv || !sampc || !buf)
+ return EINVAL;
+
+ framec = len / ads->dec.bytes_per_frame;
+
+ if (len != ads->dec.bytes_per_frame * framec)
+ return EPROTO;
+
+ if (*sampc < ads->dec.frame_size * framec)
+ return ENOMEM;
+
+ *sampc = g722_1_decode(&ads->dec, sampv, buf, (int)len);
+
+ return 0;
+}
diff --git a/modules/g7221/encode.c b/modules/g7221/encode.c
new file mode 100644
index 0000000..8345f5f
--- /dev/null
+++ b/modules/g7221/encode.c
@@ -0,0 +1,71 @@
+/**
+ * @file g7221/encode.c G.722.1 Encode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+#define G722_1_EXPOSE_INTERNAL_STRUCTURES
+
+#include <g722_1.h>
+#include "g7221.h"
+
+
+struct auenc_state {
+ g722_1_encode_state_t enc;
+};
+
+
+int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ const struct g7221_aucodec *g7221 = (struct g7221_aucodec *)ac;
+ struct auenc_state *aes;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ aes = *aesp;
+
+ if (aes)
+ return 0;
+
+ aes = mem_alloc(sizeof(*aes), NULL);
+ if (!aes)
+ return ENOMEM;
+
+ if (!g722_1_encode_init(&aes->enc, g7221->bitrate, ac->srate)) {
+ mem_deref(aes);
+ return EPROTO;
+ }
+
+ *aesp = aes;
+
+ return 0;
+}
+
+
+int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ size_t framec;
+
+ if (!aes || !buf || !len || !sampv)
+ return EINVAL;
+
+ framec = sampc / aes->enc.frame_size;
+
+ if (sampc != aes->enc.frame_size * framec)
+ return EPROTO;
+
+ if (*len < aes->enc.bytes_per_frame * framec)
+ return ENOMEM;
+
+ *len = g722_1_encode(&aes->enc, buf, sampv, (int)sampc);
+
+ return 0;
+}
diff --git a/modules/g7221/g7221.c b/modules/g7221/g7221.c
new file mode 100644
index 0000000..3e2de02
--- /dev/null
+++ b/modules/g7221/g7221.c
@@ -0,0 +1,50 @@
+/**
+ * @file g7221.c G.722.1 Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "g7221.h"
+
+
+static struct g7221_aucodec g7221 = {
+ .ac = {
+ .name = "G7221",
+ .srate = 16000,
+ .crate = 16000,
+ .ch = 1,
+ .encupdh = g7221_encode_update,
+ .ench = g7221_encode,
+ .decupdh = g7221_decode_update,
+ .dech = g7221_decode,
+ .fmtp_ench = g7221_fmtp_enc,
+ .fmtp_cmph = g7221_fmtp_cmp,
+ },
+ .bitrate = 32000,
+};
+
+
+static int module_init(void)
+{
+ aucodec_register(baresip_aucodecl(), (struct aucodec *)&g7221);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister((struct aucodec *)&g7221);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(g7221) = {
+ "g7221",
+ "audio codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/g7221/g7221.h b/modules/g7221/g7221.h
new file mode 100644
index 0000000..635fc01
--- /dev/null
+++ b/modules/g7221/g7221.h
@@ -0,0 +1,29 @@
+/**
+ * @file g7221.h Private G.722.1 Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct g7221_aucodec {
+ struct aucodec ac;
+ uint32_t bitrate;
+};
+
+/* Encode */
+int g7221_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+int g7221_encode(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc);
+
+
+/* Decode */
+int g7221_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp);
+int g7221_decode(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len);
+
+
+/* SDP */
+int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
+bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg);
diff --git a/modules/g7221/module.mk b/modules/g7221/module.mk
new file mode 100644
index 0000000..e0471a7
--- /dev/null
+++ b/modules/g7221/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := g7221
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += g7221.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_LFLAGS += -lg722_1
+
+include mk/mod.mk
diff --git a/modules/g7221/sdp.c b/modules/g7221/sdp.c
new file mode 100644
index 0000000..7bfc62c
--- /dev/null
+++ b/modules/g7221/sdp.c
@@ -0,0 +1,54 @@
+/**
+ * @file g7221/sdp.c G.722.1 SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "g7221.h"
+
+
+static uint32_t g7221_bitrate(const char *fmtp)
+{
+ struct pl pl, bitrate;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "bitrate", &bitrate))
+ return pl_u32(&bitrate);
+
+ return 0;
+}
+
+
+int g7221_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ const struct g7221_aucodec *g7221 = arg;
+ (void)offer;
+
+ if (!mb || !fmt || !g7221)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s bitrate=%u\r\n",
+ fmt->id, g7221->bitrate);
+}
+
+
+bool g7221_fmtp_cmp(const char *lfmtp, const char *rfmtp, void *arg)
+{
+ const struct g7221_aucodec *g7221 = arg;
+ (void)lfmtp;
+
+ if (!g7221)
+ return false;
+
+ if (g7221->bitrate != g7221_bitrate(rfmtp))
+ return false;
+
+ return true;
+}
diff --git a/modules/g726/g726.c b/modules/g726/g726.c
new file mode 100644
index 0000000..fd4e462
--- /dev/null
+++ b/modules/g726/g726.c
@@ -0,0 +1,208 @@
+/**
+ * @file g726.c G.726 Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES 1
+#include <spandsp.h>
+
+
+/**
+ * @defgroup g726 g726
+ *
+ * The G.726 audio codec
+ */
+
+
+enum { MAX_PACKET = 100 };
+
+
+struct g726_aucodec {
+ struct aucodec ac;
+ int bitrate;
+};
+
+struct auenc_state {
+ g726_state_t st;
+};
+
+struct audec_state {
+ g726_state_t st;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ g726_release(&st->st);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ g726_release(&st->st);
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct g726_aucodec *gac = (struct g726_aucodec *)ac;
+ struct auenc_state *st;
+ int err = 0;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR,
+ G726_PACKING_LEFT)) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct g726_aucodec *gac = (struct g726_aucodec *)ac;
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (!g726_init(&st->st, gac->bitrate, G726_ENCODING_LINEAR,
+ G726_PACKING_LEFT)) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < MAX_PACKET)
+ return ENOMEM;
+
+ *len = g726_encode(&st->st, buf, sampv, (int)sampc);
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ if (!sampv || !sampc || !buf)
+ return EINVAL;
+
+ *sampc = g726_decode(&st->st, sampv, buf, (int)len);
+
+ return 0;
+}
+
+
+static struct g726_aucodec g726[4] = {
+ {
+ {
+ LE_INIT, 0, "G726-40", 8000, 8000, 1, NULL,
+ encode_update, encode, decode_update, decode, 0, 0, 0
+ },
+ 40000
+ },
+ {
+ {
+ LE_INIT, 0, "G726-32", 8000, 8000, 1, NULL,
+ encode_update, encode, decode_update, decode, 0, 0, 0
+ },
+ 32000
+ },
+ {
+ {
+ LE_INIT, 0, "G726-24", 8000, 8000, 1, NULL,
+ encode_update, encode, decode_update, decode, 0, 0, 0
+ },
+ 24000
+ },
+ {
+ {
+ LE_INIT, 0, "G726-16", 8000, 8000, 1, NULL,
+ encode_update, encode, decode_update, decode, 0, 0, 0
+ },
+ 16000
+ }
+};
+
+
+static int module_init(void)
+{
+ struct list *aucodecl = baresip_aucodecl();
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(g726); i++)
+ aucodec_register(aucodecl, (struct aucodec *)&g726[i]);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(g726); i++)
+ aucodec_unregister((struct aucodec *)&g726[i]);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(g726) = {
+ "g726",
+ "audio codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/g726/module.mk b/modules/g726/module.mk
new file mode 100644
index 0000000..c828ea0
--- /dev/null
+++ b/modules/g726/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := g726
+$(MOD)_SRCS += g726.c
+$(MOD)_LFLAGS += -lspandsp
+
+include mk/mod.mk
diff --git a/modules/gsm/gsm.c b/modules/gsm/gsm.c
new file mode 100644
index 0000000..be225d8
--- /dev/null
+++ b/modules/gsm/gsm.c
@@ -0,0 +1,178 @@
+/**
+ * @file gsm.c GSM Audio Codec
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <gsm.h> /* please report if you have problems finding this file */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup gsm gsm
+ *
+ * The GSM audio codec
+ */
+
+
+enum {
+ FRAME_SIZE = 160
+};
+
+
+struct auenc_state {
+ gsm enc;
+};
+
+struct audec_state {
+ gsm dec;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ gsm_destroy(st->enc);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ gsm_destroy(st->dec);
+}
+
+
+static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+ (void)ac;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp)
+ return EINVAL;
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->enc = gsm_create();
+ if (!st->enc) {
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)ac;
+ (void)fmtp;
+
+ if (!adsp)
+ return EINVAL;
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->dec = gsm_create();
+ if (!st->dec) {
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ if (sampc != FRAME_SIZE)
+ return EPROTO;
+ if (*len < sizeof(gsm_frame))
+ return ENOMEM;
+
+ gsm_encode(st->enc, (gsm_signal *)sampv, buf);
+
+ *len = sizeof(gsm_frame);
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int ret;
+
+ if (*sampc < FRAME_SIZE)
+ return ENOMEM;
+ if (len < sizeof(gsm_frame))
+ return EBADMSG;
+
+ ret = gsm_decode(st->dec, (gsm_byte *)buf, (gsm_signal *)sampv);
+ if (ret)
+ return EPROTO;
+
+ *sampc = 160;
+
+ return 0;
+}
+
+
+static struct aucodec ac_gsm = {
+ LE_INIT, "3", "GSM", 8000, 8000, 1, NULL,
+ encode_update, encode, decode_update, decode, NULL, NULL, NULL
+};
+
+
+static int module_init(void)
+{
+ debug("gsm: GSM v%u.%u.%u\n", GSM_MAJOR, GSM_MINOR, GSM_PATCHLEVEL);
+
+ aucodec_register(baresip_aucodecl(), &ac_gsm);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&ac_gsm);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gsm) = {
+ "gsm",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/gsm/module.mk b/modules/gsm/module.mk
new file mode 100644
index 0000000..92219dd
--- /dev/null
+++ b/modules/gsm/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := gsm
+$(MOD)_SRCS += gsm.c
+$(MOD)_LFLAGS += -L$(SYSROOT)/lib -lgsm
+$(MOD)_CFLAGS += -I$(SYSROOT)/include/gsm -I$(SYSROOT)/local/include
+
+include mk/mod.mk
diff --git a/modules/gst/README b/modules/gst/README
new file mode 100644
index 0000000..0076e9f
--- /dev/null
+++ b/modules/gst/README
@@ -0,0 +1,34 @@
+Gstreamer notes
+---------------
+
+ The module 'gst' is using the Gstreamer framework to play external
+ media and provide this as an internal audio source.
+
+
+Debian NOTES
+
+ The http handler 'neonhttpsrc' is by default not part of Debian Etch.
+ You must download the gst-plugins-bad package manually and build it.
+
+
+Currently installed packages:
+
+$ dpkg --get-selections | grep gstrea
+gstreamer0.10-alsa install
+gstreamer0.10-doc install
+gstreamer0.10-ffmpeg install
+gstreamer0.10-plugins-bad install
+gstreamer0.10-plugins-base install
+gstreamer0.10-plugins-good install
+gstreamer0.10-plugins-ugly install
+gstreamer0.10-tools install
+gstreamer0.10-x install
+libgstreamer-plugins-base0.10-0 install
+libgstreamer-plugins-base0.10-dev install
+libgstreamer0.10-0 install
+libgstreamer0.10-dev install
+
+
+baresip configuration:
+
+ module gst.so
diff --git a/modules/gst/dump.c b/modules/gst/dump.c
new file mode 100644
index 0000000..138b3ed
--- /dev/null
+++ b/modules/gst/dump.c
@@ -0,0 +1,65 @@
+/**
+ * @file gst/dump.c Gstreamer playbin pipeline - dump utilities
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <gst/gst.h>
+#include "gst.h"
+
+
+void gst_dump_props(GstElement *g)
+{
+ uint64_t u64;
+ gchar *strval;
+ double volume;
+ int n;
+
+ debug("Gst properties:\n");
+
+ g_object_get(g, "delay", &u64, NULL);
+ debug(" delay: %lu ns\n", u64);
+
+ g_object_get(g, "uri", &strval, NULL);
+ debug(" uri: %s\n", strval);
+ g_free(strval);
+
+ g_object_get(g, "suburi", &strval, NULL);
+ debug(" suburi: %s\n", strval);
+ g_free(strval);
+
+ g_object_get(g, "queue-size", &u64, NULL);
+ debug(" queue-size: %lu ns\n", u64);
+
+ g_object_get(g, "queue-threshold", &u64, NULL);
+ debug(" queue-threshold: %lu ns\n", u64);
+
+ g_object_get(g, "nstreams", &n, NULL);
+ debug(" nstreams: %d\n", n);
+
+ g_object_get(g, "volume", &volume, NULL);
+ debug(" Volume: %f\n", volume);
+}
+
+
+void gst_dump_caps(const GstCaps *caps)
+{
+ GstStructure *s;
+ int rate, channels, width;
+
+ if (!caps)
+ return;
+
+ if (!gst_caps_get_size(caps))
+ return;
+
+ s = gst_caps_get_structure(caps, 0);
+
+ gst_structure_get_int(s, "rate", &rate);
+ gst_structure_get_int(s, "channels", &channels);
+ gst_structure_get_int(s, "width", &width);
+
+ info("gst: caps dump: %d Hz, %d channels, width=%d\n",
+ rate, channels, width);
+}
diff --git a/modules/gst/gst.c b/modules/gst/gst.c
new file mode 100644
index 0000000..d729b70
--- /dev/null
+++ b/modules/gst/gst.c
@@ -0,0 +1,454 @@
+/**
+ * @file gst/gst.c Gstreamer playbin pipeline
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#define __USE_POSIX199309
+#include <time.h>
+#include <pthread.h>
+#include <gst/gst.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "gst.h"
+
+
+/**
+ * @defgroup gst gst
+ *
+ * Audio source module using gstreamer as input
+ *
+ * The module 'gst' is using the Gstreamer framework to play external
+ * media and provide this as an internal audio source.
+ *
+ * Example config:
+ \verbatim
+ audio_source gst,http://relay.slayradio.org:8000/
+ \endverbatim
+ */
+
+
+/**
+ * Defines the Gstreamer state
+ *
+ * <pre>
+ * ptime=variable ptime=20ms
+ * .-----------. N kHz .---------. N kHz
+ * | | 1-2 channels | | 1-2 channels
+ * | Gstreamer |--------------->|Packetize|-------------> [read handler]
+ * | | | |
+ * '-----------' '---------'
+ *
+ * </pre>
+ */
+struct ausrc_st {
+ const struct ausrc *as; /**< Inheritance */
+
+ pthread_t tid; /**< Thread ID */
+ bool run; /**< Running flag */
+ ausrc_read_h *rh; /**< Read handler */
+ ausrc_error_h *errh; /**< Error handler */
+ void *arg; /**< Handler argument */
+ struct ausrc_prm prm; /**< Read parameters */
+ struct aubuf *aubuf; /**< Packet buffer */
+ size_t psize; /**< Packet size in bytes */
+ size_t sampc;
+
+ /* Gstreamer */
+ char *uri;
+ GstElement *pipeline, *bin, *source, *capsfilt, *sink;
+ GMainLoop *loop;
+};
+
+
+typedef struct _GstFakeSink GstFakeSink;
+static char gst_uri[256] = "http://relay1.slayradio.org:8000/";
+static struct ausrc *ausrc;
+
+
+static void *thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ /* Now set to playing and iterate. */
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+
+ while (st->run) {
+ g_main_loop_run(st->loop);
+ }
+
+ return NULL;
+}
+
+
+static gboolean bus_watch_handler(GstBus *bus, GstMessage *msg, gpointer data)
+{
+ struct ausrc_st *st = data;
+ GMainLoop *loop = st->loop;
+ GstTagList *tag_list;
+ gchar *title;
+ GError *err;
+ gchar *d;
+
+ (void)bus;
+
+ switch (GST_MESSAGE_TYPE(msg)) {
+
+ case GST_MESSAGE_EOS:
+ /* XXX decrementing repeat count? */
+
+ /* Re-start stream */
+ if (st->run) {
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+ }
+ else {
+ g_main_loop_quit(loop);
+ }
+ break;
+
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error(msg, &err, &d);
+
+ warning("gst: Error: %d(%m) message=%s\n", err->code,
+ err->code, err->message);
+ warning("gst: Debug: %s\n", d);
+
+ g_free(d);
+
+ /* Call error handler */
+ if (st->errh)
+ st->errh(err->code, err->message, st->arg);
+
+ g_error_free(err);
+
+ st->run = false;
+ g_main_loop_quit(loop);
+ break;
+
+ case GST_MESSAGE_TAG:
+ gst_message_parse_tag(msg, &tag_list);
+
+ if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &title)) {
+ info("gst: title: %s\n", title);
+ g_free(title);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+
+static void format_check(struct ausrc_st *st, GstStructure *s)
+{
+ int rate, channels, width;
+ gboolean sign;
+
+ if (!st || !s)
+ return;
+
+ gst_structure_get_int(s, "rate", &rate);
+ gst_structure_get_int(s, "channels", &channels);
+ gst_structure_get_int(s, "width", &width);
+ gst_structure_get_boolean(s, "signed", &sign);
+
+ if ((int)st->prm.srate != rate) {
+ warning("gst: expected %u Hz (got %u Hz)\n", st->prm.srate,
+ rate);
+ }
+ if (st->prm.ch != channels) {
+ warning("gst: expected %d channels (got %d)\n",
+ st->prm.ch, channels);
+ }
+ if (16 != width) {
+ warning("gst: expected 16-bit width (got %d)\n", width);
+ }
+ if (!sign) {
+ warning("gst: expected signed 16-bit format\n");
+ }
+}
+
+
+static void play_packet(struct ausrc_st *st)
+{
+ int16_t buf[st->sampc];
+
+ /* timed read from audio-buffer */
+ if (aubuf_get_samp(st->aubuf, st->prm.ptime, buf, st->sampc))
+ return;
+
+ /* call read handler */
+ if (st->rh)
+ st->rh(buf, st->sampc, st->arg);
+}
+
+
+/* Expected format: 16-bit signed PCM */
+static void packet_handler(struct ausrc_st *st, GstBuffer *buffer)
+{
+ int err;
+
+ if (!st->run)
+ return;
+
+ /* NOTE: When streaming from files, the buffer will be filled up
+ * pretty quickly..
+ */
+
+ err = aubuf_write(st->aubuf, GST_BUFFER_DATA(buffer),
+ GST_BUFFER_SIZE(buffer));
+ if (err) {
+ warning("gst: aubuf_write: %m\n", err);
+ }
+
+ /* Empty buffer now */
+ while (st->run) {
+ const struct timespec delay = {0, st->prm.ptime*1000000/2};
+
+ play_packet(st);
+
+ if (aubuf_cur_size(st->aubuf) < st->psize)
+ break;
+
+ (void)nanosleep(&delay, NULL);
+ }
+}
+
+
+static void handoff_handler(GstFakeSink *fakesink, GstBuffer *buffer,
+ GstPad *pad, gpointer user_data)
+{
+ struct ausrc_st *st = user_data;
+
+ (void)fakesink;
+ (void)pad;
+
+ format_check(st, gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0));
+
+ packet_handler(st, buffer);
+}
+
+
+static void set_caps(struct ausrc_st *st)
+{
+ GstCaps *caps;
+
+ /* Set the capabilities we want */
+ caps = gst_caps_new_simple("audio/x-raw-int",
+ "rate", G_TYPE_INT, st->prm.srate,
+ "channels", G_TYPE_INT, st->prm.ch,
+ "width", G_TYPE_INT, 16,
+ "signed", G_TYPE_BOOLEAN,true,
+ NULL);
+#if 0
+ gst_dump_caps(caps);
+#endif
+ g_object_set(G_OBJECT(st->capsfilt), "caps", caps, NULL);
+}
+
+
+/**
+ * Set up the Gstreamer pipeline. The playbin element is used to decode
+ * all kinds of different formats. The capsfilter is used to deliver the
+ * audio in a fixed format (X Hz, 1-2 channels, 16 bit signed)
+ *
+ * The pipeline looks like this:
+ *
+ * <pre>
+ * .--------------. .------------------------------------------.
+ * | playbin | |mybin .------------. .------------. |
+ * |----. .----| |-----. | capsfilter | | fakesink | |
+ * |sink| |src |--->|ghost| |----. .---| |----. .---| | handoff
+ * |----' '----| |pad |-->|sink| |src|-->|sink| |src|--+--> handler
+ * | | |-----' '------------' '------------' |
+ * '--------------' '------------------------------------------'
+ * </pre>
+ *
+ * @param st Audio source state
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int gst_setup(struct ausrc_st *st)
+{
+ GstBus *bus;
+ GstPad *pad;
+
+ st->loop = g_main_loop_new(NULL, FALSE);
+
+ st->pipeline = gst_pipeline_new("pipeline");
+ if (!st->pipeline) {
+ warning("gst: failed to create pipeline element\n");
+ return ENOMEM;
+ }
+
+ /********************* Player BIN **************************/
+
+ st->source = gst_element_factory_make("playbin", "source");
+ if (!st->source) {
+ warning("gst: failed to create playbin source element\n");
+ return ENOMEM;
+ }
+
+ /********************* My BIN **************************/
+
+ st->bin = gst_bin_new("mybin");
+
+ st->capsfilt = gst_element_factory_make("capsfilter", NULL);
+ if (!st->capsfilt) {
+ warning("gst: failed to create capsfilter element\n");
+ return ENOMEM;
+ }
+
+ set_caps(st);
+
+ st->sink = gst_element_factory_make("fakesink", "sink");
+ if (!st->sink) {
+ warning("gst: failed to create sink element\n");
+ return ENOMEM;
+ }
+
+ gst_bin_add_many(GST_BIN(st->bin), st->capsfilt, st->sink, NULL);
+ gst_element_link_many(st->capsfilt, st->sink, NULL);
+
+ /* add ghostpad */
+ pad = gst_element_get_pad(st->capsfilt, "sink");
+ gst_element_add_pad(st->bin, gst_ghost_pad_new("sink", pad));
+ gst_object_unref(GST_OBJECT(pad));
+
+ /* put all elements in a bin */
+ gst_bin_add_many(GST_BIN(st->pipeline), st->source, NULL);
+
+ /* Override audio-sink handoff handler */
+ g_object_set(G_OBJECT(st->sink), "signal-handoffs", TRUE, NULL);
+ g_signal_connect(st->sink, "handoff", G_CALLBACK(handoff_handler), st);
+ g_object_set(G_OBJECT(st->source), "audio-sink", st->bin, NULL);
+
+ /********************* Misc **************************/
+
+ /* Bus watch */
+ bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline));
+ gst_bus_add_watch(bus, bus_watch_handler, st);
+ gst_object_unref(bus);
+
+ /* Set URI */
+ g_object_set(G_OBJECT(st->source), "uri", st->uri, NULL);
+
+ return 0;
+}
+
+
+static void gst_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ g_main_loop_quit(st->loop);
+ pthread_join(st->tid, NULL);
+ }
+
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(st->pipeline));
+
+ mem_deref(st->uri);
+ mem_deref(st->aubuf);
+}
+
+
+static int gst_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ (void)ctx;
+
+ if (!device)
+ device = gst_uri;
+
+ if (!prm)
+ return EINVAL;
+ if (prm->fmt != AUFMT_S16LE)
+ return ENOTSUP;
+
+ st = mem_zalloc(sizeof(*st), gst_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ err = str_dup(&st->uri, device);
+ if (err)
+ goto out;
+
+ st->prm = *prm;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ st->psize = 2 * st->sampc;
+
+ err = aubuf_alloc(&st->aubuf, st->psize, 0);
+ if (err)
+ goto out;
+
+ err = gst_setup(st);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->tid, NULL, thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int mod_gst_init(void)
+{
+ gchar *s;
+
+ gst_init(0, NULL);
+
+ s = gst_version_string();
+
+ info("gst: init: %s\n", s);
+
+ g_free(s);
+
+ return ausrc_register(&ausrc, baresip_ausrcl(), "gst", gst_alloc);
+}
+
+
+static int mod_gst_close(void)
+{
+ gst_deinit();
+ ausrc = mem_deref(ausrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gst) = {
+ "gst",
+ "sound",
+ mod_gst_init,
+ mod_gst_close
+};
diff --git a/modules/gst/gst.h b/modules/gst/gst.h
new file mode 100644
index 0000000..37faac9
--- /dev/null
+++ b/modules/gst/gst.h
@@ -0,0 +1,9 @@
+/**
+ * @file gst/gst.h Gstreamer playbin pipeline -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+void gst_dump_props(GstElement *g);
+void gst_dump_caps(const GstCaps *caps);
diff --git a/modules/gst/module.mk b/modules/gst/module.mk
new file mode 100644
index 0000000..61de6f6
--- /dev/null
+++ b/modules/gst/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := gst
+$(MOD)_SRCS += gst.c dump.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-0.10)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-0.10)
+
+include mk/mod.mk
diff --git a/modules/gst1/gst.c b/modules/gst1/gst.c
new file mode 100644
index 0000000..380334a
--- /dev/null
+++ b/modules/gst1/gst.c
@@ -0,0 +1,464 @@
+/**
+ * @file gst1/gst.c Gstreamer 1.0 playbin pipeline
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+#include <gst/gst.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup gst1 gst1
+ *
+ * Audio source module using gstreamer 1.0 as input
+ *
+ * The module 'gst1' is using the Gstreamer framework to play external
+ * media and provide this as an internal audio source.
+ *
+ * Example config:
+ \verbatim
+ audio_source gst,http://relay.slayradio.org:8000/
+ \endverbatim
+ */
+
+
+/**
+ * Defines the Gstreamer state
+ *
+ * <pre>
+ * ptime=variable ptime=20ms
+ * .-----------. N kHz .---------. N kHz
+ * | | 1-2 channels | | 1-2 channels
+ * | Gstreamer |--------------->|Packetize|-------------> [read handler]
+ * | | | |
+ * '-----------' '---------'
+ *
+ * </pre>
+ */
+struct ausrc_st {
+ const struct ausrc *as; /**< Inheritance */
+
+ pthread_t tid; /**< Thread ID */
+ bool run; /**< Running flag */
+ ausrc_read_h *rh; /**< Read handler */
+ ausrc_error_h *errh; /**< Error handler */
+ void *arg; /**< Handler argument */
+ struct ausrc_prm prm; /**< Read parameters */
+ struct aubuf *aubuf; /**< Packet buffer */
+ size_t psize; /**< Packet size in bytes */
+ size_t sampc;
+
+ /* Gstreamer */
+ char *uri;
+ GstElement *pipeline, *bin, *source, *capsfilt, *sink;
+ GMainLoop *loop;
+};
+
+
+typedef struct _GstFakeSink GstFakeSink;
+static char gst_uri[256] = "http://relay1.slayradio.org:8000/";
+static struct ausrc *ausrc;
+
+
+static void *thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ /* Now set to playing and iterate. */
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+
+ while (st->run) {
+ g_main_loop_run(st->loop);
+ }
+
+ return NULL;
+}
+
+
+static gboolean bus_watch_handler(GstBus *bus, GstMessage *msg, gpointer data)
+{
+ struct ausrc_st *st = data;
+ GMainLoop *loop = st->loop;
+ GstTagList *tag_list;
+ gchar *title;
+ GError *err;
+ gchar *d;
+
+ (void)bus;
+
+ switch (GST_MESSAGE_TYPE(msg)) {
+
+ case GST_MESSAGE_EOS:
+ /* XXX decrementing repeat count? */
+
+ /* Re-start stream */
+ if (st->run) {
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+ }
+ else {
+ g_main_loop_quit(loop);
+ }
+ break;
+
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error(msg, &err, &d);
+
+ warning("gst: Error: %d(%m) message=\"%s\"\n", err->code,
+ err->code, err->message);
+ warning("gst: Debug: %s\n", d);
+
+ g_free(d);
+
+ /* Call error handler */
+ if (st->errh)
+ st->errh(err->code, err->message, st->arg);
+
+ g_error_free(err);
+
+ st->run = false;
+ g_main_loop_quit(loop);
+ break;
+
+ case GST_MESSAGE_TAG:
+ gst_message_parse_tag(msg, &tag_list);
+
+ if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &title)) {
+ info("gst: title: %s\n", title);
+ g_free(title);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+
+static void format_check(struct ausrc_st *st, GstStructure *s)
+{
+ int rate, channels, width;
+ gboolean sign;
+
+ if (!st || !s)
+ return;
+
+ gst_structure_get_int(s, "rate", &rate);
+ gst_structure_get_int(s, "channels", &channels);
+ gst_structure_get_int(s, "width", &width);
+ gst_structure_get_boolean(s, "signed", &sign);
+
+ if ((int)st->prm.srate != rate) {
+ warning("gst: expected %u Hz (got %u Hz)\n", st->prm.srate,
+ rate);
+ }
+ if (st->prm.ch != channels) {
+ warning("gst: expected %d channels (got %d)\n",
+ st->prm.ch, channels);
+ }
+ if (16 != width) {
+ warning("gst: expected 16-bit width (got %d)\n", width);
+ }
+ if (!sign) {
+ warning("gst: expected signed 16-bit format\n");
+ }
+}
+
+
+static void play_packet(struct ausrc_st *st)
+{
+ int16_t buf[st->sampc];
+
+ /* timed read from audio-buffer */
+ if (aubuf_get_samp(st->aubuf, st->prm.ptime, buf, st->sampc))
+ return;
+
+ /* call read handler */
+ if (st->rh)
+ st->rh(buf, st->sampc, st->arg);
+}
+
+
+/* Expected format: 16-bit signed PCM */
+static void packet_handler(struct ausrc_st *st, GstBuffer *buffer)
+{
+ GstMapInfo info;
+ int err;
+
+ if (!st->run)
+ return;
+
+ /* NOTE: When streaming from files, the buffer will be filled up
+ * pretty quickly..
+ */
+
+ if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) {
+ warning("gst: gst_buffer_map failed\n");
+ return;
+ }
+
+ err = aubuf_write(st->aubuf, info.data, info.size);
+ if (err) {
+ warning("gst: aubuf_write: %m\n", err);
+ }
+
+ gst_buffer_unmap(buffer, &info);
+
+ /* Empty buffer now */
+ while (st->run) {
+ const struct timespec delay = {0, st->prm.ptime*1000000/2};
+
+ play_packet(st);
+
+ if (aubuf_cur_size(st->aubuf) < st->psize)
+ break;
+
+ (void)nanosleep(&delay, NULL);
+ }
+}
+
+
+static void handoff_handler(GstFakeSink *fakesink, GstBuffer *buffer,
+ GstPad *pad, gpointer user_data)
+{
+ struct ausrc_st *st = user_data;
+ GstCaps *caps;
+ (void)fakesink;
+
+ caps = gst_pad_get_current_caps(pad);
+
+ format_check(st, gst_caps_get_structure(caps, 0));
+
+ packet_handler(st, buffer);
+}
+
+
+static void set_caps(struct ausrc_st *st)
+{
+ GstCaps *caps;
+
+ /* Set the capabilities we want */
+ caps = gst_caps_new_simple("audio/x-raw",
+ "rate", G_TYPE_INT, st->prm.srate,
+ "channels", G_TYPE_INT, st->prm.ch,
+ "width", G_TYPE_INT, 16,
+ "signed", G_TYPE_BOOLEAN,true,
+ NULL);
+
+ g_object_set(G_OBJECT(st->capsfilt), "caps", caps, NULL);
+}
+
+
+/**
+ * Set up the Gstreamer pipeline. The playbin element is used to decode
+ * all kinds of different formats. The capsfilter is used to deliver the
+ * audio in a fixed format (X Hz, 1-2 channels, 16 bit signed)
+ *
+ * The pipeline looks like this:
+ *
+ * <pre>
+ * .--------------. .------------------------------------------.
+ * | playbin | |mybin .------------. .------------. |
+ * |----. .----| |-----. | capsfilter | | fakesink | |
+ * |sink| |src |--->|ghost| |----. .---| |----. .---| | handoff
+ * |----' '----| |pad |-->|sink| |src|-->|sink| |src|--+--> handler
+ * | | |-----' '------------' '------------' |
+ * '--------------' '------------------------------------------'
+ * </pre>
+ *
+ * @param st Audio source state
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int gst_setup(struct ausrc_st *st)
+{
+ GstBus *bus;
+ GstPad *pad;
+
+ st->loop = g_main_loop_new(NULL, FALSE);
+
+ st->pipeline = gst_pipeline_new("pipeline");
+ if (!st->pipeline) {
+ warning("gst: failed to create pipeline element\n");
+ return ENOMEM;
+ }
+
+ /********************* Player BIN **************************/
+
+ st->source = gst_element_factory_make("playbin", "source");
+ if (!st->source) {
+ warning("gst: failed to create playbin source element\n");
+ return ENOMEM;
+ }
+
+ /********************* My BIN **************************/
+
+ st->bin = gst_bin_new("mybin");
+
+ st->capsfilt = gst_element_factory_make("capsfilter", NULL);
+ if (!st->capsfilt) {
+ warning("gst: failed to create capsfilter element\n");
+ return ENOMEM;
+ }
+
+ set_caps(st);
+
+ st->sink = gst_element_factory_make("fakesink", "sink");
+ if (!st->sink) {
+ warning("gst: failed to create sink element\n");
+ return ENOMEM;
+ }
+
+ gst_bin_add_many(GST_BIN(st->bin), st->capsfilt, st->sink, NULL);
+ gst_element_link_many(st->capsfilt, st->sink, NULL);
+
+ /* add ghostpad */
+ pad = gst_element_get_static_pad(st->capsfilt, "sink");
+ gst_element_add_pad(st->bin, gst_ghost_pad_new("sink", pad));
+ gst_object_unref(GST_OBJECT(pad));
+
+ /* put all elements in a bin */
+ gst_bin_add_many(GST_BIN(st->pipeline), st->source, NULL);
+
+ /* Override audio-sink handoff handler */
+ g_object_set(G_OBJECT(st->sink), "signal-handoffs", TRUE, NULL);
+ g_signal_connect(st->sink, "handoff", G_CALLBACK(handoff_handler), st);
+
+ g_object_set(G_OBJECT(st->source), "audio-sink", st->bin, NULL);
+
+ /********************* Misc **************************/
+
+ /* Bus watch */
+ bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline));
+ gst_bus_add_watch(bus, bus_watch_handler, st);
+ gst_object_unref(bus);
+
+ /* Set URI */
+ g_object_set(G_OBJECT(st->source), "uri", st->uri, NULL);
+
+ return 0;
+}
+
+
+static void gst_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ g_main_loop_quit(st->loop);
+ pthread_join(st->tid, NULL);
+ }
+
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(st->pipeline));
+
+ mem_deref(st->uri);
+ mem_deref(st->aubuf);
+}
+
+
+static int gst_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ (void)ctx;
+
+ if (!device)
+ device = gst_uri;
+
+ if (!prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("gst: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), gst_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ err = str_dup(&st->uri, device);
+ if (err)
+ goto out;
+
+ st->prm = *prm;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ st->psize = 2 * st->sampc;
+
+ err = aubuf_alloc(&st->aubuf, st->psize, 0);
+ if (err)
+ goto out;
+
+ err = gst_setup(st);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->tid, NULL, thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int mod_gst_init(void)
+{
+ gchar *s;
+
+ gst_init(0, NULL);
+
+ s = gst_version_string();
+
+ info("gst: init: %s\n", s);
+
+ g_free(s);
+
+ return ausrc_register(&ausrc, baresip_ausrcl(), "gst", gst_alloc);
+}
+
+
+static int mod_gst_close(void)
+{
+ gst_deinit();
+ ausrc = mem_deref(ausrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gst1) = {
+ "gst1",
+ "sound",
+ mod_gst_init,
+ mod_gst_close
+};
diff --git a/modules/gst1/module.mk b/modules/gst1/module.mk
new file mode 100644
index 0000000..5e67f69
--- /dev/null
+++ b/modules/gst1/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := gst1
+$(MOD)_SRCS += gst.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-1.0)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-1.0)
+$(MOD)_CFLAGS += -Wno-cast-align
+
+include mk/mod.mk
diff --git a/modules/gst_video/encode.c b/modules/gst_video/encode.c
new file mode 100644
index 0000000..b6dfe8a
--- /dev/null
+++ b/modules/gst_video/encode.c
@@ -0,0 +1,540 @@
+/**
+ * @file gst_video/encode.c Video codecs using Gstreamer video pipeline
+ *
+ * Copyright (C) 2010 - 2013 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ */
+#define _DEFAULT_SOURCE 1
+#define __USE_POSIX199309
+#define _BSD_SOURCE 1
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/app/gstappsrc.h>
+#include "gst_video.h"
+
+
+struct videnc_state {
+
+ struct vidsz size;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+
+ struct {
+ uint32_t packetization_mode;
+ uint32_t profile_idc;
+ uint32_t profile_iop;
+ uint32_t level_idc;
+ uint32_t max_fs;
+ uint32_t max_smbps;
+ } h264;
+
+ videnc_packet_h *pkth;
+ void *pkth_arg;
+
+ /* Gstreamer */
+ GstElement *pipeline, *source, *sink;
+ GstBus *bus;
+ gulong need_data_handler;
+ gulong enough_data_handler;
+ gulong new_buffer_handler;
+ bool gst_inited;
+
+ /* Main loop thread. */
+ int run;
+ pthread_t tid;
+
+ /* Thread synchronization. */
+ pthread_mutex_t mutex;
+ pthread_cond_t wait;
+ int bwait;
+};
+
+
+static void gst_encoder_close(struct videnc_state *st);
+
+
+static void internal_bus_watch_handler(struct videnc_state *st)
+{
+ GError *err;
+ gchar *d;
+ GstMessage *msg = gst_bus_pop(st->bus);
+
+ if (!msg) {
+ /* take a nap (300ms) */
+ usleep(300 * 1000);
+ return;
+ }
+
+ switch (GST_MESSAGE_TYPE(msg)) {
+
+ case GST_MESSAGE_EOS:
+
+ /* XXX decrementing repeat count? */
+
+ /* Re-start stream */
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+ break;
+
+ case GST_MESSAGE_ERROR:
+ gst_message_parse_error(msg, &err, &d);
+
+ warning("gst_video: Error: %d(%m) message=%s\n", err->code,
+ err->code, err->message);
+ warning("gst_video: Debug: %s\n", d);
+
+ g_free(d);
+ g_error_free(err);
+
+ st->run = FALSE;
+ break;
+
+ default:
+ break;
+ }
+
+ gst_message_unref(msg);
+}
+
+
+static void *internal_thread(void *arg)
+{
+ struct videnc_state *st = arg;
+
+ /* Now set to playing and iterate. */
+ debug("gst_video: Setting pipeline to PLAYING\n");
+
+ gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+
+ while (st->run) {
+ internal_bus_watch_handler(st);
+ }
+
+ debug("gst_video: Pipeline thread was stopped.\n");
+
+ return NULL;
+}
+
+
+static void internal_appsrc_start_feed(GstElement * pipeline, guint size,
+ struct videnc_state *st)
+{
+ (void)pipeline;
+ (void)size;
+
+ if (!st)
+ return;
+
+ pthread_mutex_lock(&st->mutex);
+ st->bwait = FALSE;
+ pthread_cond_signal(&st->wait);
+ pthread_mutex_unlock(&st->mutex);
+}
+
+
+static void internal_appsrc_stop_feed(GstElement * pipeline,
+ struct videnc_state *st)
+{
+ (void)pipeline;
+
+ if (!st)
+ return;
+
+ pthread_mutex_lock(&st->mutex);
+ st->bwait = TRUE;
+ pthread_mutex_unlock(&st->mutex);
+}
+
+
+/* The appsink has received a buffer */
+static void internal_appsink_new_buffer(GstElement *sink,
+ struct videnc_state *st)
+{
+ GstBuffer *buffer;
+
+ if (!st)
+ return;
+
+ /* Retrieve the buffer */
+ g_signal_emit_by_name(sink, "pull-buffer", &buffer);
+
+ if (buffer) {
+ GstClockTime ts;
+ uint32_t rtp_ts;
+
+ guint8 *data = GST_BUFFER_DATA(buffer);
+ guint size = GST_BUFFER_SIZE(buffer);
+
+ ts = GST_BUFFER_TIMESTAMP(buffer);
+
+ rtp_ts = (uint32_t)((90000ULL*ts) / 1000000000UL );
+
+ h264_packetize(rtp_ts, data, size, st->pktsize,
+ st->pkth, st->pkth_arg);
+
+ gst_buffer_unref(buffer);
+ }
+}
+
+
+/**
+ * Set up the Gstreamer pipeline. Appsrc gets raw frames, and appsink takes
+ * encoded frames.
+ *
+ * The pipeline looks like this:
+ *
+ * <pre>
+ * .--------. .-----------. .----------.
+ * | appsrc | | x264enc | | appsink |
+ * | .----| |----. .---| |----. |
+ * | |src |-->|sink| |src|-->|sink|-----+-->handoff
+ * | '----| |----' '---| |----' | handler
+ * '--------' '-----------' '----------'
+ * </pre>
+ */
+static int gst_encoder_init(struct videnc_state *st, int width, int height,
+ int framerate, int bitrate)
+{
+ GError* gerror = NULL;
+ char pipeline[1024];
+ int err = 0;
+
+ gst_encoder_close(st);
+
+ snprintf(pipeline, sizeof(pipeline),
+ "appsrc name=source is-live=TRUE block=TRUE do-timestamp=TRUE ! "
+ "videoparse width=%d height=%d format=i420 framerate=%d/1 ! "
+ "x264enc byte-stream=TRUE rc-lookahead=0"
+ " sync-lookahead=0 bitrate=%d ! "
+ "appsink name=sink emit-signals=TRUE drop=TRUE",
+ width, height, framerate, bitrate / 1000 /* kbit/s */);
+
+ debug("gst_video: format: yu12 = yuv420p = i420\n");
+
+ /* Initialize pipeline. */
+ st->pipeline = gst_parse_launch(pipeline, &gerror);
+ if (gerror) {
+ warning("gst_video: launch error: %s: %s\n",
+ gerror->message, pipeline);
+ err = gerror->code;
+ g_error_free(gerror);
+ goto out;
+ }
+
+ st->source = gst_bin_get_by_name(GST_BIN(st->pipeline), "source");
+ st->sink = gst_bin_get_by_name(GST_BIN(st->pipeline), "sink");
+ if (!st->source || !st->sink) {
+ warning("gst_video: failed to get source or sink"
+ " pipeline elements\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Configure appsource */
+ st->need_data_handler = g_signal_connect(st->source, "need-data",
+ G_CALLBACK(internal_appsrc_start_feed), st);
+ st->enough_data_handler = g_signal_connect(st->source, "enough-data",
+ G_CALLBACK(internal_appsrc_stop_feed), st);
+
+ /* Configure appsink. */
+ st->new_buffer_handler = g_signal_connect(st->sink, "new-buffer",
+ G_CALLBACK(internal_appsink_new_buffer), st);
+
+ /********************* Misc **************************/
+
+ /* Bus watch */
+ st->bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline));
+
+ /********************* Thread **************************/
+
+ /* Synchronization primitives. */
+ pthread_mutex_init(&st->mutex, NULL);
+ pthread_cond_init(&st->wait, NULL);
+ st->bwait = FALSE;
+
+ err = gst_element_set_state(st->pipeline, GST_STATE_PLAYING);
+ if (GST_STATE_CHANGE_FAILURE == err) {
+ g_warning("set state returned GST_STATE_CHANGE_FAILUER\n");
+ }
+
+ /* Launch thread with gstreamer loop. */
+ st->run = true;
+ err = pthread_create(&st->tid, NULL, internal_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ st->gst_inited = true;
+
+ out:
+ return err;
+}
+
+
+static int gst_video_push(struct videnc_state *st, const uint8_t *src,
+ size_t size)
+{
+ GstBuffer *buffer;
+ int ret = 0;
+
+ if (!st) {
+ return EINVAL;
+ }
+
+ if (!size) {
+ warning("gst_video: push: eos returned %d at %d\n",
+ ret, __LINE__);
+ gst_app_src_end_of_stream((GstAppSrc *)st->source);
+ return ret;
+ }
+
+ /* Wait "start feed". */
+ pthread_mutex_lock(&st->mutex);
+ if (st->bwait) {
+#define WAIT_TIME_SECONDS 5
+ struct timespec ts;
+ struct timeval tp;
+ gettimeofday(&tp, NULL);
+ ts.tv_sec = tp.tv_sec;
+ ts.tv_nsec = tp.tv_usec * 1000;
+ ts.tv_sec += WAIT_TIME_SECONDS;
+ /* Wait. */
+ ret = pthread_cond_timedwait(&st->wait, &st->mutex, &ts);
+ if (ETIMEDOUT == ret) {
+ warning("gst_video: Raw frame is lost"
+ " because of timeout\n");
+ return ret;
+ }
+ }
+ pthread_mutex_unlock(&st->mutex);
+
+ /* Create a new empty buffer */
+ buffer = gst_buffer_new();
+ GST_BUFFER_MALLOCDATA(buffer) = (guint8 *)src;
+ GST_BUFFER_SIZE(buffer) = (guint)size;
+ GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer);
+
+ ret = gst_app_src_push_buffer((GstAppSrc *)st->source, buffer);
+
+ if (ret != GST_FLOW_OK) {
+ warning("gst_video: push buffer returned"
+ " %d for %d bytes \n", ret, size);
+ return ret;
+ }
+
+ return ret;
+}
+
+
+static void gst_encoder_close(struct videnc_state *st)
+{
+ if (!st)
+ return;
+
+ st->gst_inited = false;
+
+ /* Remove asynchronous callbacks to prevent using gst_video_t
+ context ("st") after releasing. */
+ if (st->source) {
+ g_signal_handler_disconnect(st->source,
+ st->need_data_handler);
+ g_signal_handler_disconnect(st->source,
+ st->enough_data_handler);
+ }
+ if (st->sink) {
+ g_signal_handler_disconnect(st->sink, st->new_buffer_handler);
+ }
+
+ /* Stop thread. */
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->tid, NULL);
+ }
+
+ if (st->source) {
+ gst_object_unref(GST_OBJECT(st->source));
+ st->source = NULL;
+ }
+ if (st->sink) {
+ gst_object_unref(GST_OBJECT(st->sink));
+ st->sink = NULL;
+ }
+ if (st->bus) {
+ gst_object_unref(GST_OBJECT(st->bus));
+ st->bus = NULL;
+ }
+
+ if (st->pipeline) {
+ gst_element_set_state(st->pipeline, GST_STATE_NULL);
+ gst_object_unref(GST_OBJECT(st->pipeline));
+ st->pipeline = NULL;
+ }
+}
+
+
+static void encode_destructor(void *arg)
+{
+ struct videnc_state *st = arg;
+
+ gst_encoder_close(st);
+}
+
+
+static int decode_sdpparam_h264(struct videnc_state *st, const struct pl *name,
+ const struct pl *val)
+{
+ if (0 == pl_strcasecmp(name, "packetization-mode")) {
+ st->h264.packetization_mode = pl_u32(val);
+
+ if (st->h264.packetization_mode != 0) {
+ warning("gst_video: illegal packetization-mode %u\n",
+ st->h264.packetization_mode);
+ return EPROTO;
+ }
+ }
+ else if (0 == pl_strcasecmp(name, "profile-level-id")) {
+ struct pl prof = *val;
+ if (prof.l != 6) {
+ warning("gst_video: invalid profile-level-id (%r)\n",
+ val);
+ return EPROTO;
+ }
+
+ prof.l = 2;
+ st->h264.profile_idc = pl_x32(&prof); prof.p += 2;
+ st->h264.profile_iop = pl_x32(&prof); prof.p += 2;
+ st->h264.level_idc = pl_x32(&prof);
+ }
+ else if (0 == pl_strcasecmp(name, "max-fs")) {
+ st->h264.max_fs = pl_u32(val);
+ }
+ else if (0 == pl_strcasecmp(name, "max-smbps")) {
+ st->h264.max_smbps = pl_u32(val);
+ }
+
+ return 0;
+}
+
+
+static void param_handler(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct videnc_state *st = arg;
+
+ (void)decode_sdpparam_h264(st, name, val);
+}
+
+
+int gst_video_encode_update(struct videnc_state **vesp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *ves;
+ int err = 0;
+
+ if (!vesp || !vc || !prm)
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), encode_destructor);
+ if (!ves)
+ return ENOMEM;
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->gst_inited && (ves->bitrate != prm->bitrate ||
+ ves->pktsize != prm->pktsize ||
+ ves->fps != prm->fps)) {
+ gst_encoder_close(ves);
+ }
+ }
+
+ if (str_isset(fmtp)) {
+ struct pl sdp_fmtp;
+
+ pl_set_str(&sdp_fmtp, fmtp);
+
+ fmt_param_apply(&sdp_fmtp, param_handler, ves);
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->pkth_arg = arg;
+
+ info("gst_video: video encoder %s: %d fps, %d bit/s, pktsize=%u\n",
+ vc->name, prm->fps, prm->bitrate, prm->pktsize);
+
+ return err;
+}
+
+
+int gst_video_encode(struct videnc_state *st, bool update,
+ const struct vidframe *frame)
+{
+ uint8_t *data;
+ size_t size;
+ int height;
+ int err;
+
+ if (!st || !frame || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ if (!st->gst_inited || !vidsz_cmp(&st->size, &frame->size)) {
+
+ err = gst_encoder_init(st, frame->size.w, frame->size.h,
+ st->fps, st->bitrate);
+
+ if (err) {
+ warning("gst_video codec: gst_video_alloc failed\n");
+ return err;
+ }
+
+ /* To detect if requested size was changed. */
+ st->size = frame->size;
+ }
+
+ if (update) {
+ debug("gst_video: gstreamer picture update"
+ ", it's not implemented...\n");
+ }
+
+ height = frame->size.h;
+
+ /* NOTE: I420 (YUV420P): hardcoded. */
+ size = frame->linesize[0] * height
+ + frame->linesize[1] * height * 0.5
+ + frame->linesize[2] * height * 0.5;
+
+ data = malloc(size); /* XXX: memory-leak ? */
+ if (!data)
+ return ENOMEM;
+
+ size = 0;
+
+ /* XXX: avoid memcpy here ? */
+ memcpy(&data[size], frame->data[0], frame->linesize[0] * height);
+ size += frame->linesize[0] * height;
+ memcpy(&data[size], frame->data[1], frame->linesize[1] * height * 0.5);
+ size += frame->linesize[1] * height * 0.5;
+ memcpy(&data[size], frame->data[2], frame->linesize[2] * height * 0.5);
+ size += frame->linesize[2] * height * 0.5;
+
+ return gst_video_push(st, data, size);
+}
diff --git a/modules/gst_video/gst_video.c b/modules/gst_video/gst_video.c
new file mode 100644
index 0000000..8807caf
--- /dev/null
+++ b/modules/gst_video/gst_video.c
@@ -0,0 +1,64 @@
+/**
+ * @file gst_video/gst_video.c Video codecs using Gstreamer
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <gst/gst.h>
+#include "gst_video.h"
+
+
+/**
+ * @defgroup gst_video gst_video
+ *
+ * This module implements video codecs using Gstreamer.
+ *
+ * Currently only H.264 encoding is supported, but this can be extended
+ * if needed. No decoding is done by this module, so that must be done by
+ * another video-codec module.
+ *
+ * Thanks to Victor Sergienko and Fadeev Alexander for the
+ * initial version, which was based on avcodec module.
+ */
+
+
+static struct vidcodec h264 = {
+ .name = "H264",
+ .variant = "packetization-mode=0",
+ .encupdh = gst_video_encode_update,
+ .ench = gst_video_encode,
+ .fmtp_ench = gst_video_fmtp_enc,
+ .fmtp_cmph = gst_video_fmtp_cmp,
+};
+
+
+static int module_init(void)
+{
+ gst_init(NULL, NULL);
+
+ vidcodec_register(baresip_vidcodecl(), &h264);
+
+ info("gst_video: using gstreamer H.264 encoder\n");
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&h264);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gst_video) = {
+ "gst_video",
+ "vidcodec",
+ module_init,
+ module_close
+};
diff --git a/modules/gst_video/gst_video.h b/modules/gst_video/gst_video.h
new file mode 100644
index 0000000..b1edd59
--- /dev/null
+++ b/modules/gst_video/gst_video.h
@@ -0,0 +1,24 @@
+/**
+ * @file gst_video/gst_video.h Gstreamer video pipeline -- internal API
+ *
+ * Copyright (C) 2010 - 2014 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ */
+
+
+/* Encode */
+struct videnc_state;
+
+int gst_video_encode_update(struct videnc_state **vesp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int gst_video_encode(struct videnc_state *st, bool update,
+ const struct vidframe *frame);
+
+
+/* SDP */
+uint32_t gst_video_h264_packetization_mode(const char *fmtp);
+int gst_video_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
+bool gst_video_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data);
diff --git a/modules/gst_video/module.mk b/modules/gst_video/module.mk
new file mode 100644
index 0000000..bb233f4
--- /dev/null
+++ b/modules/gst_video/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := gst_video
+$(MOD)_SRCS += gst_video.c encode.c sdp.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-0.10 gstreamer-app-0.10)
+$(MOD)_CFLAGS += \
+ $(shell pkg-config --cflags gstreamer-0.10 gstreamer-app-0.10)
+
+include mk/mod.mk
diff --git a/modules/gst_video/sdp.c b/modules/gst_video/sdp.c
new file mode 100644
index 0000000..294d319
--- /dev/null
+++ b/modules/gst_video/sdp.c
@@ -0,0 +1,56 @@
+/**
+ * @file gst_video/sdp.c H.264 SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "gst_video.h"
+
+
+static const uint8_t h264_level_idc = 0x0c;
+
+
+uint32_t gst_video_h264_packetization_mode(const char *fmtp)
+{
+ struct pl pl, mode;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "packetization-mode", &mode))
+ return pl_u32(&mode);
+
+ return 0;
+}
+
+
+int gst_video_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ struct vidcodec *vc = arg;
+ const uint8_t profile_idc = 0x42; /* baseline profile */
+ const uint8_t profile_iop = 0x80;
+ (void)offer;
+
+ if (!mb || !fmt || !vc)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s"
+ " packetization-mode=0"
+ ";profile-level-id=%02x%02x%02x"
+ "\r\n",
+ fmt->id, profile_idc, profile_iop, h264_level_idc);
+}
+
+
+bool gst_video_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data)
+{
+ (void)data;
+
+ return gst_video_h264_packetization_mode(fmtp1) ==
+ gst_video_h264_packetization_mode(fmtp2);
+}
diff --git a/modules/gst_video1/encode.c b/modules/gst_video1/encode.c
new file mode 100644
index 0000000..d0a3d44
--- /dev/null
+++ b/modules/gst_video1/encode.c
@@ -0,0 +1,613 @@
+/**
+ * @file gst_video/encode.c Video codecs using Gstreamer video pipeline
+ *
+ * Copyright (C) 2010 - 2013 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ * Copyright (C) 2015 Thomas Strobel
+ */
+
+#define __USE_POSIX199309
+#define _DEFAULT_SOURCE 1
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+#include "gst_video.h"
+
+
+struct videnc_state {
+
+ struct {
+ struct vidsz size;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ } encoder;
+
+ struct {
+ uint32_t packetization_mode;
+ uint32_t profile_idc;
+ uint32_t profile_iop;
+ uint32_t level_idc;
+ uint32_t max_fs;
+ uint32_t max_smbps;
+ } h264;
+
+ videnc_packet_h *pkth;
+ void *arg;
+
+ /* Gstreamer */
+ struct {
+ bool valid;
+
+ GstElement *pipeline;
+ GstAppSrc *source;
+
+ GstAppSrcCallbacks appsrcCallbacks;
+ GstAppSinkCallbacks appsinkCallbacks;
+
+ struct {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ int flag;
+ } eos;
+
+ /* Thread synchronization. */
+ struct {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ /* 0: no-wait, 1: wait, -1: pipeline destroyed */
+ int flag;
+ } wait;
+ } streamer;
+};
+
+
+static void appsrc_need_data_cb(GstAppSrc *src, guint size, gpointer user_data)
+{
+ struct videnc_state *st = user_data;
+ (void)src;
+ (void)size;
+
+ pthread_mutex_lock(&st->streamer.wait.mutex);
+ if (st->streamer.wait.flag == 1){
+ st->streamer.wait.flag = 0;
+ pthread_cond_signal(&st->streamer.wait.cond);
+ }
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+}
+
+
+static void appsrc_enough_data_cb(GstAppSrc *src, gpointer user_data)
+{
+ struct videnc_state *st = user_data;
+ (void)src;
+
+ pthread_mutex_lock(&st->streamer.wait.mutex);
+ if (st->streamer.wait.flag == 0)
+ st->streamer.wait.flag = 1;
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+}
+
+
+static void appsrc_destroy_notify_cb(struct videnc_state *st)
+{
+ pthread_mutex_lock(&st->streamer.wait.mutex);
+ st->streamer.wait.flag = -1;
+ pthread_cond_signal(&st->streamer.wait.cond);
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+}
+
+
+/* The appsink has received a sample */
+static GstFlowReturn appsink_new_sample_cb(GstAppSink *sink,
+ gpointer user_data)
+{
+ struct videnc_state *st = user_data;
+ GstSample *sample;
+ GstBuffer *buffer;
+ GstMapInfo info;
+ guint8 *data;
+ gsize size;
+
+ /* Retrieve the sample */
+ sample = gst_app_sink_pull_sample(sink);
+
+ if (sample) {
+ GstClockTime ts;
+ uint32_t rtp_ts;
+
+ buffer = gst_sample_get_buffer(sample);
+ gst_buffer_map( buffer, &info, (GstMapFlags)(GST_MAP_READ) );
+
+ data = info.data;
+ size = info.size;
+
+ ts = GST_BUFFER_DTS_OR_PTS(buffer);
+
+ if (ts == GST_CLOCK_TIME_NONE) {
+ warning("gst_video: timestamp is unknown\n");
+ rtp_ts = 0;
+ }
+ else {
+ /* convert from nanoseconds to RTP clock */
+ rtp_ts = (uint32_t)((90000ULL * ts) / 1000000000UL);
+ }
+
+ h264_packetize(rtp_ts, data, size, st->encoder.pktsize,
+ st->pkth, st->arg);
+
+ gst_buffer_unmap(buffer, &info);
+ gst_sample_unref(sample);
+ }
+
+ return GST_FLOW_OK;
+}
+
+
+static void appsink_end_of_stream_cb(GstAppSink *src, gpointer user_data)
+{
+ struct videnc_state *st = user_data;
+ (void)src;
+
+ pthread_mutex_lock(&st->streamer.eos.mutex);
+ if (st->streamer.eos.flag == 0) {
+ st->streamer.eos.flag = 1;
+ pthread_cond_signal(&st->streamer.eos.cond);
+ }
+ pthread_mutex_unlock(&st->streamer.eos.mutex);
+}
+
+
+static void appsink_destroy_notify_cb(struct videnc_state *st)
+{
+ pthread_mutex_lock(&st->streamer.eos.mutex);
+ st->streamer.eos.flag = -1;
+ pthread_cond_signal(&st->streamer.eos.cond);
+ pthread_mutex_unlock(&st->streamer.eos.mutex);
+}
+
+
+static GstBusSyncReply bus_sync_handler_cb(GstBus *bus, GstMessage *msg,
+ struct videnc_state *st)
+{
+ (void)bus;
+
+ if ((GST_MESSAGE_TYPE (msg)) == GST_MESSAGE_ERROR) {
+ GError *err = NULL;
+ gchar *dbg_info = NULL;
+ gst_message_parse_error (msg, &err, &dbg_info);
+ warning("gst_video: Error: %d(%m) message=%s\n",
+ err->code, err->code, err->message);
+ warning("gst_video: Debug: %s\n", dbg_info);
+ g_error_free (err);
+ g_free (dbg_info);
+
+ /* mark pipeline as broked */
+ st->streamer.valid = false;
+ }
+
+ gst_message_unref(msg);
+ return GST_BUS_DROP;
+}
+
+
+static void bus_destroy_notify_cb(struct videnc_state *st)
+{
+ (void)st;
+}
+
+
+/**
+ * Set up the Gstreamer pipeline. Appsrc gets raw frames, and appsink takes
+ * encoded frames.
+ *
+ * The pipeline looks like this:
+ *
+ * <pre>
+ * .--------. .-----------. .----------.
+ * | appsrc | | x264enc | | appsink |
+ * | .----| |----. .---| |----. |
+ * | |src |-->|sink| |src|-->|sink|-----+-->handoff
+ * | '----| |----' '---| |----' | handler
+ * '--------' '-----------' '----------'
+ * </pre>
+ */
+static int pipeline_init(struct videnc_state *st, const struct vidsz *size)
+{
+ GstAppSrc *source;
+ GstAppSink *sink;
+ GstBus *bus;
+ GError* gerror = NULL;
+ char pipeline[1024];
+ GstStateChangeReturn ret;
+ int err = 0;
+
+ if (!st || !size)
+ return EINVAL;
+
+ snprintf(pipeline, sizeof(pipeline),
+ "appsrc name=source is-live=TRUE block=TRUE "
+ "do-timestamp=TRUE max-bytes=1000000 ! "
+ "videoparse width=%d height=%d format=i420 framerate=%d/1 ! "
+ "x264enc byte-stream=TRUE rc-lookahead=0 "
+ "tune=zerolatency speed-preset=ultrafast "
+ "sync-lookahead=0 bitrate=%d ! "
+ "appsink name=sink emit-signals=TRUE drop=TRUE",
+ size->w, size->h,
+ st->encoder.fps, st->encoder.bitrate / 1000 /* kbit/s */);
+
+ /* Initialize pipeline. */
+ st->streamer.pipeline = gst_parse_launch(pipeline, &gerror);
+
+ if (gerror) {
+ warning("gst_video: launch error: %d: %s: %s\n",
+ gerror->code, gerror->message, pipeline);
+ err = gerror->code;
+ g_error_free(gerror);
+ return err;
+ }
+
+ /* Configure appsource */
+ source = GST_APP_SRC(gst_bin_get_by_name(
+ GST_BIN(st->streamer.pipeline), "source"));
+ gst_app_src_set_callbacks(source, &(st->streamer.appsrcCallbacks),
+ st, (GDestroyNotify)appsrc_destroy_notify_cb);
+
+ /* Configure appsink. */
+ sink = GST_APP_SINK(gst_bin_get_by_name(
+ GST_BIN(st->streamer.pipeline), "sink"));
+ gst_app_sink_set_callbacks(sink, &(st->streamer.appsinkCallbacks),
+ st, (GDestroyNotify)appsink_destroy_notify_cb);
+ gst_object_unref(GST_OBJECT(sink));
+
+ /* Bus watch */
+ bus = gst_pipeline_get_bus(GST_PIPELINE(st->streamer.pipeline));
+ gst_bus_set_sync_handler(bus, (GstBusSyncHandler)bus_sync_handler_cb,
+ st, (GDestroyNotify)bus_destroy_notify_cb);
+ gst_object_unref(GST_OBJECT(bus));
+
+ /* Set start values of locks */
+ pthread_mutex_lock(&st->streamer.wait.mutex);
+ st->streamer.wait.flag = 0;
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+
+ pthread_mutex_lock(&st->streamer.eos.mutex);
+ st->streamer.eos.flag = 0;
+ pthread_mutex_unlock(&st->streamer.eos.mutex);
+
+ /* Start pipeline */
+ ret = gst_element_set_state(st->streamer.pipeline, GST_STATE_PLAYING);
+ if (GST_STATE_CHANGE_FAILURE == ret) {
+ g_warning("set state returned GST_STATE_CHANGE_FAILURE\n");
+ err = EPROTO;
+ goto out;
+ }
+
+ st->streamer.source = source;
+
+ /* Mark pipeline as working */
+ st->streamer.valid = true;
+
+ out:
+ return err;
+}
+
+
+static void pipeline_close(struct videnc_state *st)
+{
+ if (!st)
+ return;
+
+ st->streamer.valid = false;
+
+ if (st->streamer.source) {
+ gst_object_unref(GST_OBJECT(st->streamer.source));
+ st->streamer.source = NULL;
+ }
+
+ if (st->streamer.pipeline) {
+ gst_element_set_state(st->streamer.pipeline, GST_STATE_NULL);
+
+ /* pipeline */
+ gst_object_unref(GST_OBJECT(st->streamer.pipeline));
+ st->streamer.pipeline = NULL;
+ }
+}
+
+
+static void destruct_resources(void *data)
+{
+ struct videnc_state *st = data;
+
+ /* close pipeline */
+ pipeline_close(st);
+
+ /* destroy locks */
+ pthread_mutex_destroy(&st->streamer.eos.mutex);
+ pthread_cond_destroy(&st->streamer.eos.cond);
+
+ pthread_mutex_destroy(&st->streamer.wait.mutex);
+ pthread_cond_destroy(&st->streamer.wait.cond);
+}
+
+
+static int allocate_resources(struct videnc_state **stp)
+{
+ struct videnc_state *st;
+
+ st = mem_zalloc(sizeof(*st), destruct_resources);
+ if (!st)
+ return ENOMEM;
+
+ *stp = st;
+
+ /* initialize locks */
+ pthread_mutex_init(&st->streamer.eos.mutex, NULL);
+ pthread_cond_init(&st->streamer.eos.cond, NULL);
+
+ pthread_mutex_init(&st->streamer.wait.mutex, NULL);
+ pthread_cond_init(&st->streamer.wait.cond, NULL);
+
+
+ /* Set appsource callbacks. */
+ st->streamer.appsrcCallbacks.need_data = &appsrc_need_data_cb;
+ st->streamer.appsrcCallbacks.enough_data = &appsrc_enough_data_cb;
+
+ /* Set appsink callbacks. */
+ st->streamer.appsinkCallbacks.new_sample = &appsink_new_sample_cb;
+ st->streamer.appsinkCallbacks.eos = &appsink_end_of_stream_cb;
+
+ return 0;
+}
+
+
+/*
+ decode sdpparameter for h264
+*/
+static void param_handler(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct videnc_state *st = arg;
+
+ if (0 == pl_strcasecmp(name, "packetization-mode")) {
+ st->h264.packetization_mode = pl_u32(val);
+
+ if (st->h264.packetization_mode != 0) {
+ warning("gst_video: illegal packetization-mode %u\n",
+ st->h264.packetization_mode);
+ return;
+ }
+ }
+ else if (0 == pl_strcasecmp(name, "profile-level-id")) {
+ struct pl prof = *val;
+ if (prof.l != 6) {
+ warning("gst_video: invalid profile-level-id (%r)\n",
+ val);
+ return;
+ }
+
+ prof.l = 2;
+ st->h264.profile_idc = pl_x32(&prof); prof.p += 2;
+ st->h264.profile_iop = pl_x32(&prof); prof.p += 2;
+ st->h264.level_idc = pl_x32(&prof);
+ }
+ else if (0 == pl_strcasecmp(name, "max-fs")) {
+ st->h264.max_fs = pl_u32(val);
+ }
+ else if (0 == pl_strcasecmp(name, "max-smbps")) {
+ st->h264.max_smbps = pl_u32(val);
+ }
+
+ return;
+}
+
+
+int gst_video1_encoder_set(struct videnc_state **stp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *st = *stp;
+ int err = 0;
+
+ if (!stp || !vc || !prm || !pkth)
+ return EINVAL;
+
+ if (!st) {
+ err = allocate_resources(stp);
+ if (err) {
+ warning("gst_video: resource allocation failed\n");
+ return err;
+ }
+ st = *stp;
+
+ st->pkth = pkth;
+ st->arg = arg;
+ }
+ else {
+ if (!st->streamer.valid) {
+ warning("gst_video codec: trying to work"
+ " with invalid pipeline\n");
+ return EINVAL;
+ }
+
+ if ((st->encoder.bitrate != prm->bitrate ||
+ st->encoder.pktsize != prm->pktsize ||
+ st->encoder.fps != prm->fps)) {
+
+ pipeline_close(st);
+ }
+ }
+
+ st->encoder.bitrate = prm->bitrate;
+ st->encoder.pktsize = prm->pktsize;
+ st->encoder.fps = prm->fps;
+
+ if (str_isset(fmtp)) {
+ struct pl sdp_fmtp;
+ pl_set_str(&sdp_fmtp, fmtp);
+
+ /* store new parameters */
+ fmt_param_apply(&sdp_fmtp, param_handler, st);
+ }
+
+ info("gst_video: video encoder %s: %d fps, %d bit/s, pktsize=%u\n",
+ vc->name, st->encoder.fps,
+ st->encoder.bitrate, st->encoder.pktsize);
+
+ return err;
+}
+
+
+/*
+ * couple gstreamer tightly by lock-stepping
+ */
+static int pipeline_push(struct videnc_state *st, const struct vidframe *frame)
+{
+ GstBuffer *buffer;
+ uint8_t *data;
+ size_t size;
+ GstFlowReturn ret;
+ int err = 0;
+
+#if 1
+ /* XXX: should not block the function here */
+
+ /*
+ * Wait "start feed".
+ */
+ pthread_mutex_lock(&st->streamer.wait.mutex);
+ if (st->streamer.wait.flag == 1) {
+ pthread_cond_wait(&st->streamer.wait.cond,
+ &st->streamer.wait.mutex);
+ }
+ if (st->streamer.eos.flag == -1)
+ /* error */
+ err = ENODEV;
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+ if (err)
+ return err;
+#endif
+
+ /*
+ * Copy frame into buffer for gstreamer
+ */
+
+ /* NOTE: I420 (YUV420P): hardcoded. */
+ size = frame->linesize[0] * frame->size.h
+ + frame->linesize[1] * frame->size.h * 0.5
+ + frame->linesize[2] * frame->size.h * 0.5;
+
+ /* allocate memory; memory is freed within callback of
+ gst_memory_new_wrapped of gst_video_push */
+ data = g_try_malloc(size);
+ if (!data)
+ return ENOMEM;
+
+ /* copy content of frame */
+ size = 0;
+ memcpy(&data[size], frame->data[0],
+ frame->linesize[0] * frame->size.h);
+ size += frame->linesize[0] * frame->size.h;
+ memcpy(&data[size], frame->data[1],
+ frame->linesize[1] * frame->size.h * 0.5);
+ size += frame->linesize[1] * frame->size.h * 0.5;
+ memcpy(&data[size], frame->data[2],
+ frame->linesize[2] * frame->size.h * 0.5);
+ size += frame->linesize[2] * frame->size.h * 0.5;
+
+ /* Wrap memory in a gstreamer buffer */
+ buffer = gst_buffer_new();
+ gst_buffer_insert_memory(buffer, -1,
+ gst_memory_new_wrapped (0, data, size, 0,
+ size, data, g_free));
+
+ /*
+ * Push data and EOS into gstreamer.
+ */
+
+ ret = gst_app_src_push_buffer(st->streamer.source, buffer);
+ if (ret != GST_FLOW_OK) {
+ warning("gst_video: pushing buffer failed\n");
+ err = EPROTO;
+ goto out;
+ }
+
+#if 0
+ ret = gst_app_src_end_of_stream(st->streamer.source);
+ if (ret != GST_FLOW_OK) {
+ warning("gst_video: pushing EOS failed\n");
+ err = EPROTO;
+ goto out;
+ }
+#endif
+
+
+#if 0
+ /*
+ * Wait "processing done".
+ */
+ pthread_mutex_lock(&st->streamer.eos.mutex);
+ if (st->streamer.eos.flag == 0)
+ /* will returns with EOS (1) or error (-1) */
+ pthread_cond_wait(&st->streamer.wait.cond,
+ &st->streamer.wait.mutex);
+ if (st->streamer.eos.flag == 1)
+ /* reset eos */
+ st->streamer.eos.flag = 0;
+ else
+ /* error */
+ err = -1;
+ pthread_mutex_unlock(&st->streamer.wait.mutex);
+#endif
+
+
+ out:
+ return err;
+}
+
+
+int gst_video1_encode(struct videnc_state *st, bool update,
+ const struct vidframe *frame)
+{
+ int err;
+
+ if (!st || !frame || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ if (!st->streamer.valid ||
+ !vidsz_cmp(&st->encoder.size, &frame->size)) {
+
+ pipeline_close(st);
+
+ err = pipeline_init(st, &frame->size);
+ if (err) {
+ warning("gst_video: pipeline initialization failed\n");
+ return err;
+ }
+
+ st->encoder.size = frame->size;
+ }
+
+ if (update) {
+ debug("gst_video: gstreamer picture update"
+ ", it's not implemented...\n");
+ }
+
+ /*
+ * Push frame into pipeline.
+ * Function call will return once frame has been processed completely.
+ */
+ err = pipeline_push(st, frame);
+
+ return err;
+}
diff --git a/modules/gst_video1/gst_video.c b/modules/gst_video1/gst_video.c
new file mode 100644
index 0000000..c15efac
--- /dev/null
+++ b/modules/gst_video1/gst_video.c
@@ -0,0 +1,66 @@
+/**
+ * @file gst_video1/gst_video.c Video codecs using Gstreamer 1.0
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <gst/gst.h>
+#include "gst_video.h"
+
+
+/**
+ * @defgroup gst_video1 gst_video1
+ *
+ * This module implements video codecs using Gstreamer 1.0
+ *
+ * Currently only H.264 encoding is supported, but this can be extended
+ * if needed. No decoding is done by this module, so that must be done by
+ * another video-codec module.
+ *
+ * Thanks to Victor Sergienko and Fadeev Alexander for the
+ * initial version, which was based on avcodec module.
+ */
+
+
+static struct vidcodec h264 = {
+ .name = "H264",
+ .variant = "packetization-mode=0",
+ .encupdh = gst_video1_encoder_set,
+ .ench = gst_video1_encode,
+ .fmtp_ench = gst_video1_fmtp_enc,
+ .fmtp_cmph = gst_video1_fmtp_cmp,
+};
+
+
+static int module_init(void)
+{
+ gst_init(NULL, NULL);
+
+ vidcodec_register(baresip_vidcodecl(), &h264);
+
+ info("gst_video: using gstreamer (%s)\n", gst_version_string());
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&h264);
+
+ gst_deinit();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gst_video1) = {
+ "gst_video1",
+ "vidcodec",
+ module_init,
+ module_close
+};
diff --git a/modules/gst_video1/gst_video.h b/modules/gst_video1/gst_video.h
new file mode 100644
index 0000000..150f5be
--- /dev/null
+++ b/modules/gst_video1/gst_video.h
@@ -0,0 +1,24 @@
+/**
+ * @file gst_video1/gst_video.h Gstreamer video pipeline -- internal API
+ *
+ * Copyright (C) 2010 - 2014 Creytiv.com
+ * Copyright (C) 2014 Fadeev Alexander
+ */
+
+
+/* Encode */
+struct videnc_state;
+
+int gst_video1_encoder_set(struct videnc_state **stp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int gst_video1_encode(struct videnc_state *st, bool update,
+ const struct vidframe *frame);
+
+
+/* SDP */
+uint32_t gst_video1_h264_packetization_mode(const char *fmtp);
+int gst_video1_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
+bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data);
diff --git a/modules/gst_video1/module.mk b/modules/gst_video1/module.mk
new file mode 100644
index 0000000..d60beb3
--- /dev/null
+++ b/modules/gst_video1/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := gst_video1
+$(MOD)_SRCS += gst_video.c encode.c sdp.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs gstreamer-1.0 gstreamer-app-1.0)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags gstreamer-1.0 gstreamer-app-1.0)
+$(MOD)_CFLAGS += -Wno-cast-align
+
+include mk/mod.mk
diff --git a/modules/gst_video1/sdp.c b/modules/gst_video1/sdp.c
new file mode 100644
index 0000000..7896b05
--- /dev/null
+++ b/modules/gst_video1/sdp.c
@@ -0,0 +1,57 @@
+/**
+ * @file gst_video/sdp.c H.264 SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "gst_video.h"
+
+
+static const uint8_t gst_video_h264_level_idc = 0x0c;
+
+
+uint32_t gst_video1_h264_packetization_mode(const char *fmtp)
+{
+ struct pl pl, mode;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "packetization-mode", &mode))
+ return pl_u32(&mode);
+
+ return 0;
+}
+
+
+int gst_video1_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ struct vidcodec *vc = arg;
+ const uint8_t profile_idc = 0x42; /* baseline profile */
+ const uint8_t profile_iop = 0x80;
+ (void)offer;
+
+ if (!mb || !fmt || !vc)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s"
+ " packetization-mode=0"
+ ";profile-level-id=%02x%02x%02x"
+ "\r\n",
+ fmt->id, profile_idc, profile_iop,
+ gst_video_h264_level_idc);
+}
+
+
+bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data)
+{
+ (void)data;
+
+ return gst_video1_h264_packetization_mode(fmtp1) ==
+ gst_video1_h264_packetization_mode(fmtp2);
+}
diff --git a/modules/gtk/call_window.c b/modules/gtk/call_window.c
new file mode 100644
index 0000000..6744510
--- /dev/null
+++ b/modules/gtk/call_window.c
@@ -0,0 +1,522 @@
+/**
+ * @file gtk/call_window.c GTK+ call window
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <gtk/gtk.h>
+#include "gtk_mod.h"
+
+
+struct call_window {
+ struct gtk_mod *mod;
+ struct call *call;
+
+ /** for communicating from gtk thread to main thread */
+ struct mqueue *mq;
+
+ struct {
+ struct vumeter_dec *dec;
+ struct vumeter_enc *enc;
+ } vu;
+ struct transfer_dialog *transfer_dialog;
+ GtkWidget *window;
+ GtkLabel *status;
+ GtkLabel *duration;
+ struct {
+ GtkWidget *hangup, *transfer, *hold, *mute;
+ } buttons;
+ struct {
+ GtkProgressBar *enc, *dec;
+ } progress;
+ guint duration_timer_tag;
+ guint vumeter_timer_tag;
+ bool closed;
+ int cur_key;
+};
+
+enum call_window_events {
+ MQ_HANGUP,
+ MQ_CLOSE,
+ MQ_HOLD,
+ MQ_MUTE,
+ MQ_TRANSFER,
+};
+
+static struct call_window *last_call_win = NULL;
+static struct vumeter_dec *last_dec = NULL;
+static struct vumeter_enc *last_enc = NULL;
+
+
+static void call_window_update_duration(struct call_window *win)
+{
+ gchar buf[32];
+
+ const uint32_t dur = call_duration(win->call);
+ const uint32_t sec = dur%60%60;
+ const uint32_t min = dur/60%60;
+ const uint32_t hrs = dur/60/60;
+
+ re_snprintf(buf, sizeof buf, "%u:%02u:%02u", hrs, min, sec);
+ gtk_label_set_text(win->duration, buf);
+}
+
+
+static void call_window_update_vumeters(struct call_window *win)
+{
+ double value;
+
+ if (win->vu.enc && win->vu.enc->started) {
+ value = min((double)win->vu.enc->avg_rec / 0x4000, 1);
+ gtk_progress_bar_set_fraction(win->progress.enc, value);
+ }
+ if (win->vu.dec && win->vu.dec->started) {
+ value = min((double)win->vu.dec->avg_play / 0x4000, 1);
+ gtk_progress_bar_set_fraction(win->progress.dec, value);
+ }
+}
+
+
+static gboolean call_timer(gpointer arg)
+{
+ struct call_window *win = arg;
+ call_window_update_duration(win);
+ return G_SOURCE_CONTINUE;
+}
+
+
+static gboolean vumeter_timer(gpointer arg)
+{
+ struct call_window *win = arg;
+ call_window_update_vumeters(win);
+ return G_SOURCE_CONTINUE;
+}
+
+
+static void vumeter_timer_start(struct call_window *win)
+{
+ if (!win->vumeter_timer_tag)
+ win->vumeter_timer_tag =
+ g_timeout_add(100, vumeter_timer, win);
+ if (win->vu.enc)
+ win->vu.enc->avg_rec = 0;
+ if (win->vu.dec)
+ win->vu.dec->avg_play = 0;
+}
+
+
+static void vumeter_timer_stop(struct call_window *win)
+{
+ if (win->vumeter_timer_tag) {
+ g_source_remove(win->vumeter_timer_tag);
+ win->vumeter_timer_tag = 0;
+ }
+ gtk_progress_bar_set_fraction(win->progress.enc, 0);
+ gtk_progress_bar_set_fraction(win->progress.dec, 0);
+}
+
+
+static void call_window_set_vu_dec(struct call_window *win,
+ struct vumeter_dec *dec)
+{
+ if (win->vu.dec)
+ mem_deref(win->vu.dec);
+ win->vu.dec = mem_ref(dec);
+ vumeter_timer_start(win);
+}
+
+
+static void call_window_set_vu_enc(struct call_window *win,
+ struct vumeter_enc *enc)
+{
+ if (win->vu.enc)
+ mem_deref(win->vu.enc);
+ win->vu.enc = mem_ref(enc);
+ vumeter_timer_start(win);
+}
+
+
+/* This is a hack to associate a call with its vumeters */
+
+void call_window_got_vu_dec(struct vumeter_dec *dec)
+{
+ if (last_call_win)
+ call_window_set_vu_dec(last_call_win, dec);
+ else
+ last_dec = dec;
+}
+
+
+void call_window_got_vu_enc(struct vumeter_enc *enc)
+{
+ if (last_call_win)
+ call_window_set_vu_enc(last_call_win, enc);
+ else
+ last_enc = enc;
+}
+
+
+static void got_call_window(struct call_window *win)
+{
+ if (last_enc)
+ call_window_set_vu_enc(win, last_enc);
+ if (last_dec)
+ call_window_set_vu_dec(win, last_dec);
+ if (!last_enc || !last_dec)
+ last_call_win = win;
+}
+
+
+static void call_on_hangup(GtkToggleButton *btn, struct call_window *win)
+{
+ (void)btn;
+ mqueue_push(win->mq, MQ_CLOSE, win);
+}
+
+
+static void call_on_hold_toggle(GtkToggleButton *btn, struct call_window *win)
+{
+ bool hold = gtk_toggle_button_get_active(btn);
+ if (hold)
+ vumeter_timer_stop(win);
+ else
+ vumeter_timer_start(win);
+ mqueue_push(win->mq, MQ_HOLD, (void *)(size_t)hold);
+}
+
+
+static void call_on_mute_toggle(GtkToggleButton *btn, struct call_window *win)
+{
+ bool mute = gtk_toggle_button_get_active(btn);
+ mqueue_push(win->mq, MQ_MUTE, (void *)(size_t)mute);
+}
+
+
+static void call_on_transfer(GtkToggleButton *btn, struct call_window *win)
+{
+ (void)btn;
+ if (!win->transfer_dialog)
+ win->transfer_dialog = transfer_dialog_alloc(win);
+ else
+ transfer_dialog_show(win->transfer_dialog);
+}
+
+static gboolean call_on_window_close(GtkWidget *widget, GdkEventAny *event,
+ struct call_window *win)
+{
+ (void)event;
+ (void)widget;
+ mqueue_push(win->mq, MQ_CLOSE, NULL);
+ return TRUE;
+}
+
+
+static gboolean call_on_key_press(GtkWidget *window, GdkEvent *ev,
+ struct call_window *win)
+{
+ gchar key = ev->key.string[0];
+ (void)window;
+
+ switch (key) {
+
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ case '*': case '0': case '#':
+ win->cur_key = key;
+ call_send_digit(win->call, key);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+
+static gboolean call_on_key_release(GtkWidget *window, GdkEvent *ev,
+ struct call_window *win)
+{
+ (void)window;
+
+ if (win->cur_key && win->cur_key == ev->key.string[0]) {
+ win->cur_key = 0;
+ call_send_digit(win->call, 0);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void call_window_set_status(struct call_window *win,
+ const char *status)
+{
+ gtk_label_set_text(win->status, status);
+}
+
+
+static void mqueue_handler(int id, void *data, void *arg)
+{
+ struct call_window *win = arg;
+
+ switch ((enum call_window_events)id) {
+
+ case MQ_HANGUP:
+ ua_hangup(uag_current(), win->call, 0, NULL);
+ break;
+
+ case MQ_CLOSE:
+ if (!win->closed) {
+ ua_hangup(uag_current(), win->call, 0, NULL);
+ win->closed = true;
+ }
+ mem_deref(win);
+ break;
+
+ case MQ_MUTE:
+ audio_mute(call_audio(win->call), (size_t)data);
+ break;
+
+ case MQ_HOLD:
+ call_hold(win->call, (size_t)data);
+ break;
+
+ case MQ_TRANSFER:
+ call_transfer(win->call, data);
+ break;
+ }
+}
+
+
+static void call_window_destructor(void *arg)
+{
+ struct call_window *window = arg;
+
+ gdk_threads_enter();
+ gtk_mod_call_window_closed(window->mod, window);
+ gtk_widget_destroy(window->window);
+ mem_deref(window->transfer_dialog);
+ gdk_threads_leave();
+
+ mem_deref(window->call);
+ mem_deref(window->mq);
+ mem_deref(window->vu.enc);
+ mem_deref(window->vu.dec);
+ if (window->duration_timer_tag)
+ g_source_remove(window->duration_timer_tag);
+ if (window->vumeter_timer_tag)
+ g_source_remove(window->vumeter_timer_tag);
+ /* TODO: avoid race conditions here */
+ last_call_win = NULL;
+}
+
+
+struct call_window *call_window_new(struct call *call, struct gtk_mod *mod)
+{
+ struct call_window *win;
+ GtkWidget *window, *label, *status, *button, *progress, *image;
+ GtkWidget *button_box, *vbox, *hbox;
+ GtkWidget *duration;
+ int err = 0;
+
+ win = mem_zalloc(sizeof(*win), call_window_destructor);
+ if (!win)
+ return NULL;
+
+ mqueue_alloc(&win->mq, mqueue_handler, win);
+ if (err)
+ goto out;
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(window), call_peeruri(call));
+ gtk_window_set_type_hint(GTK_WINDOW(window),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ /* Peer name and URI */
+ label = gtk_label_new(call_peername(call));
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ label = gtk_label_new(call_peeruri(call));
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+ /* Call duration */
+ duration = gtk_label_new(NULL);
+ gtk_box_pack_start(GTK_BOX(vbox), duration, FALSE, FALSE, 0);
+
+ /* Status */
+ status = gtk_label_new(NULL);
+ gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
+
+ /* Progress bars */
+ hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_set_spacing(GTK_BOX(hbox), 6);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+ /* Encoding vumeter */
+ image = gtk_image_new_from_icon_name("audio-input-microphone",
+ GTK_ICON_SIZE_BUTTON);
+ progress = gtk_progress_bar_new();
+ win->progress.enc = GTK_PROGRESS_BAR(progress);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), progress, FALSE, FALSE, 0);
+
+ /* Decoding vumeter */
+ image = gtk_image_new_from_icon_name("audio-headphones",
+ GTK_ICON_SIZE_BUTTON);
+ progress = gtk_progress_bar_new();
+ win->progress.dec = GTK_PROGRESS_BAR(progress);
+ gtk_box_pack_end(GTK_BOX(hbox), progress, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+
+ /* Buttons */
+ button_box = gtk_hbutton_box_new();
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box),
+ GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(button_box), 6);
+ gtk_container_set_border_width(GTK_CONTAINER(button_box), 5);
+ gtk_box_pack_end(GTK_BOX(vbox), button_box, FALSE, TRUE, 0);
+
+ /* Hang up */
+ button = gtk_button_new_with_label("Hangup");
+ win->buttons.hangup = button;
+ gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0);
+ g_signal_connect(button, "clicked",
+ G_CALLBACK(call_on_hangup), win);
+ image = gtk_image_new_from_icon_name("call-stop",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+
+ /* Transfer */
+ button = gtk_button_new_with_label("Transfer");
+ win->buttons.transfer = button;
+ gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0);
+ g_signal_connect(button, "clicked", G_CALLBACK(call_on_transfer), win);
+ image = gtk_image_new_from_icon_name("forward", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+
+ /* Hold */
+ button = gtk_toggle_button_new_with_label("Hold");
+ win->buttons.hold = button;
+ gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0);
+ g_signal_connect(button, "toggled",
+ G_CALLBACK(call_on_hold_toggle), win);
+ image = gtk_image_new_from_icon_name("player_pause",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+
+ /* Mute */
+ button = gtk_toggle_button_new_with_label("Mute");
+ win->buttons.mute = button;
+ gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0);
+ g_signal_connect(button, "toggled",
+ G_CALLBACK(call_on_mute_toggle), win);
+ image = gtk_image_new_from_icon_name("microphone-sensitivity-muted",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+
+ gtk_widget_show_all(window);
+ gtk_window_present(GTK_WINDOW(window));
+
+ g_signal_connect(window, "delete_event",
+ G_CALLBACK(call_on_window_close), win);
+ g_signal_connect(window, "key-press-event",
+ G_CALLBACK(call_on_key_press), win);
+ g_signal_connect(window, "key-release-event",
+ G_CALLBACK(call_on_key_release), win);
+
+ win->call = mem_ref(call);
+ win->mod = mod;
+ win->window = window;
+ win->transfer_dialog = NULL;
+ win->status = GTK_LABEL(status);
+ win->duration = GTK_LABEL(duration);
+ win->closed = false;
+ win->duration_timer_tag = 0;
+ win->vumeter_timer_tag = 0;
+ win->vu.enc = NULL;
+ win->vu.dec = NULL;
+
+ got_call_window(win);
+
+out:
+ if (err)
+ mem_deref(win);
+
+ return win;
+}
+
+
+void call_window_transfer(struct call_window *win, const char *uri)
+{
+ mqueue_push(win->mq, MQ_TRANSFER, (char *)uri);
+}
+
+
+void call_window_closed(struct call_window *win, const char *reason)
+{
+ char buf[256];
+ const char *status;
+
+ vumeter_timer_stop(win);
+ if (win->duration_timer_tag) {
+ g_source_remove(win->duration_timer_tag);
+ win->duration_timer_tag = 0;
+ }
+ gtk_widget_set_sensitive(win->buttons.transfer, FALSE);
+ gtk_widget_set_sensitive(win->buttons.hold, FALSE);
+ gtk_widget_set_sensitive(win->buttons.mute, FALSE);
+
+ if (reason && reason[0]) {
+ re_snprintf(buf, sizeof buf, "closed: %s", reason);
+ status = buf;
+ }
+ else {
+ status = "closed";
+ }
+ call_window_set_status(win, status);
+ win->transfer_dialog = mem_deref(win->transfer_dialog);
+ win->closed = true;
+}
+
+
+void call_window_ringing(struct call_window *win)
+{
+ call_window_set_status(win, "ringing");
+}
+
+
+void call_window_progress(struct call_window *win)
+{
+ win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win);
+ last_call_win = win;
+ call_window_set_status(win, "progress");
+}
+
+
+void call_window_established(struct call_window *win)
+{
+ call_window_update_duration(win);
+ win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win);
+ last_call_win = win;
+ call_window_set_status(win, "established");
+}
+
+
+void call_window_transfer_failed(struct call_window *win, const char *reason)
+{
+ if (win->transfer_dialog) {
+ transfer_dialog_fail(win->transfer_dialog, reason);
+ }
+}
+
+
+bool call_window_is_for_call(struct call_window *win, struct call *call)
+{
+ return win->call == call;
+}
diff --git a/modules/gtk/dial_dialog.c b/modules/gtk/dial_dialog.c
new file mode 100644
index 0000000..340362b
--- /dev/null
+++ b/modules/gtk/dial_dialog.c
@@ -0,0 +1,96 @@
+/**
+ * @file gtk/dial_dialog.c GTK+ dial dialog
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <gtk/gtk.h>
+#include "gtk_mod.h"
+
+struct dial_dialog {
+ struct gtk_mod *mod;
+ GtkWidget *dialog;
+ GtkComboBox *uri_combobox;
+};
+
+static void dial_dialog_on_response(GtkDialog *dialog, gint response_id,
+ gpointer arg)
+{
+ struct dial_dialog *dd = arg;
+ char *uri;
+
+ if (response_id == GTK_RESPONSE_ACCEPT) {
+ uri = (char *)uri_combo_box_get_text(dd->uri_combobox);
+ gtk_mod_connect(dd->mod, uri);
+ }
+
+ gtk_widget_hide(GTK_WIDGET(dialog));
+}
+
+
+static void destructor(void *arg)
+{
+ struct dial_dialog *dd = arg;
+
+ gtk_widget_destroy(dd->dialog);
+}
+
+struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod)
+{
+ struct dial_dialog *dd;
+ GtkWidget *dial;
+ GtkWidget *content, *button, *image;
+ GtkWidget *uri_combobox;
+
+ dd = mem_zalloc(sizeof(*dd), destructor);
+ if (!dd)
+ return NULL;
+
+ dial = gtk_dialog_new_with_buttons("Dial", NULL, 0, NULL);
+
+ /* Cancel */
+ button = gtk_button_new_with_label("Cancel");
+ image = gtk_image_new_from_icon_name("call-stop",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dial), button,
+ GTK_RESPONSE_REJECT);
+
+ /* Call */
+ button = gtk_button_new_with_label("Call");
+ image = gtk_image_new_from_icon_name("call-start",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dial), button,
+ GTK_RESPONSE_ACCEPT);
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dial),
+ GTK_RESPONSE_ACCEPT);
+ uri_combobox = uri_combo_box_new();
+
+ content = gtk_dialog_get_content_area(GTK_DIALOG(dial));
+ gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5);
+ gtk_widget_show_all(content);
+
+ g_signal_connect(G_OBJECT(dial), "response",
+ G_CALLBACK(dial_dialog_on_response), dd);
+ g_signal_connect(G_OBJECT(dial), "delete-event",
+ G_CALLBACK(gtk_widget_hide_on_delete), dd);
+
+ dd->dialog = dial;
+ dd->uri_combobox = GTK_COMBO_BOX(uri_combobox);
+ dd->mod = mod;
+
+ return dd;
+}
+
+void dial_dialog_show(struct dial_dialog *dd)
+{
+ gtk_window_present(GTK_WINDOW(dd->dialog));
+ gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(dd->uri_combobox)));
+}
diff --git a/modules/gtk/gtk_mod.c b/modules/gtk/gtk_mod.c
new file mode 100644
index 0000000..dbd4a5b
--- /dev/null
+++ b/modules/gtk/gtk_mod.c
@@ -0,0 +1,1053 @@
+/**
+ * @file gtk/gtk_mod.c GTK+ UI module
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include "gtk_mod.h"
+
+#ifdef USE_LIBNOTIFY
+#include <libnotify/notify.h>
+#endif
+
+#if GLIB_CHECK_VERSION(2,40,0) || defined(USE_LIBNOTIFY)
+#define USE_NOTIFICATIONS 1
+#endif
+
+/* About */
+#define COPYRIGHT " Copyright (C) 2010 - 2015 Alfred E. Heggestad et al."
+#define COMMENTS "A modular SIP User-Agent with audio and video support"
+#define WEBSITE "http://www.creytiv.com/baresip.html"
+#define LICENSE "BSD"
+
+/**
+ * @defgroup gtk_mod gtk_mod
+ *
+ * GTK+ Menu-based User-Interface module
+ *
+ * Creates a tray icon with a menu for making calls.
+ *
+ */
+
+struct gtk_mod {
+ pthread_t thread;
+ bool run;
+ bool contacts_inited;
+ bool accounts_inited;
+ struct message_lsnr *message;
+ struct mqueue *mq;
+ GApplication *app;
+ GtkStatusIcon *status_icon;
+ GtkWidget *app_menu;
+ GtkWidget *contacts_menu;
+ GtkWidget *accounts_menu;
+ GtkWidget *status_menu;
+ GSList *accounts_menu_group;
+ struct dial_dialog *dial_dialog;
+ GSList *call_windows;
+ GSList *incoming_call_menus;
+};
+
+static struct gtk_mod mod_obj;
+
+enum gtk_mod_events {
+ MQ_POPUP,
+ MQ_CONNECT,
+ MQ_QUIT,
+ MQ_ANSWER,
+ MQ_HANGUP,
+ MQ_SELECT_UA,
+};
+
+static void answer_activated(GSimpleAction *, GVariant *, gpointer);
+static void reject_activated(GSimpleAction *, GVariant *, gpointer);
+static void denotify_incoming_call(struct gtk_mod *, struct call *);
+
+static GActionEntry app_entries[] = {
+ {"answer", answer_activated, "x", NULL, NULL, {0} },
+ {"reject", reject_activated, "x", NULL, NULL, {0} },
+};
+
+static struct call *get_call_from_gvariant(GVariant *param)
+{
+ gint64 call_ptr;
+ struct call *call;
+ struct list *calls = ua_calls(uag_current());
+ struct le *le;
+
+ call_ptr = g_variant_get_int64(param);
+ call = GINT_TO_POINTER(call_ptr);
+
+ for (le = list_head(calls); le; le = le->next)
+ if (le->data == call)
+ return call;
+
+ return NULL;
+}
+
+
+static void menu_on_about(GtkMenuItem *menuItem, gpointer arg)
+{
+ (void)menuItem;
+ (void)arg;
+
+ gtk_show_about_dialog(NULL,
+ "program-name", "baresip",
+ "version", BARESIP_VERSION,
+ "logo-icon-name", "call-start",
+ "copyright", COPYRIGHT,
+ "comments", COMMENTS,
+ "website", WEBSITE,
+ "license", LICENSE,
+ NULL);
+}
+
+
+static void menu_on_quit(GtkMenuItem *menuItem, gpointer arg)
+{
+ struct gtk_mod *mod = arg;
+ (void)menuItem;
+
+ gtk_widget_destroy(GTK_WIDGET(mod->app_menu));
+ g_object_unref(G_OBJECT(mod->status_icon));
+
+ mqueue_push(mod->mq, MQ_QUIT, 0);
+ info("quit from gtk\n");
+}
+
+
+static void menu_on_dial(GtkMenuItem *menuItem, gpointer arg)
+{
+ struct gtk_mod *mod = arg;
+ (void)menuItem;
+ if (!mod->dial_dialog)
+ mod->dial_dialog = dial_dialog_alloc(mod);
+ dial_dialog_show(mod->dial_dialog);
+}
+
+
+static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg)
+{
+ struct gtk_mod *mod = arg;
+ const char *uri = gtk_menu_item_get_label(menuItem);
+ /* Queue dial from the main thread */
+ mqueue_push(mod->mq, MQ_CONNECT, (char *)uri);
+}
+
+
+static void init_contacts_menu(struct gtk_mod *mod)
+{
+ struct contacts *contacts = baresip_contacts();
+ struct le *le;
+ GtkWidget *item;
+ GtkMenuShell *contacts_menu = GTK_MENU_SHELL(mod->contacts_menu);
+
+ /* Add contacts to submenu */
+ for (le = list_head(contact_list(contacts)); le; le = le->next) {
+ struct contact *c = le->data;
+ item = gtk_menu_item_new_with_label(contact_str(c));
+ gtk_menu_shell_append(contacts_menu, item);
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(menu_on_dial_contact), mod);
+ }
+}
+
+
+static void menu_on_account_toggled(GtkCheckMenuItem *menu_item,
+ struct gtk_mod *mod)
+{
+ struct ua *ua = g_object_get_data(G_OBJECT(menu_item), "ua");
+ if (menu_item->active)
+ mqueue_push(mod->mq, MQ_SELECT_UA, ua);
+}
+
+
+static void menu_on_presence_set(GtkMenuItem *item, struct gtk_mod *mod)
+{
+ struct le *le;
+ void *type = g_object_get_data(G_OBJECT(item), "presence");
+ enum presence_status status = GPOINTER_TO_UINT(type);
+ (void)mod;
+
+ for (le = list_head(uag_list()); le; le = le->next) {
+ struct ua *ua = le->data;
+ ua_presence_status_set(ua, status);
+ }
+}
+
+
+#ifdef USE_NOTIFICATIONS
+static void menu_on_incoming_call_answer(GtkMenuItem *menuItem,
+ struct gtk_mod *mod)
+{
+ struct call *call = g_object_get_data(G_OBJECT(menuItem), "call");
+ denotify_incoming_call(mod, call);
+ mqueue_push(mod->mq, MQ_ANSWER, call);
+}
+
+
+static void menu_on_incoming_call_reject(GtkMenuItem *menuItem,
+ struct gtk_mod *mod)
+{
+ struct call *call = g_object_get_data(G_OBJECT(menuItem), "call");
+ denotify_incoming_call(mod, call);
+ mqueue_push(mod->mq, MQ_HANGUP, call);
+}
+#endif
+
+
+static GtkMenuItem *accounts_menu_add_item(struct gtk_mod *mod,
+ struct ua *ua)
+{
+ GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu);
+ GtkWidget *item;
+ GSList *group = mod->accounts_menu_group;
+ struct ua *ua_current = uag_current();
+ char buf[256];
+
+ re_snprintf(buf, sizeof buf, "%s%s", ua_aor(ua),
+ ua_isregistered(ua) ? " (OK)" : "");
+ item = gtk_radio_menu_item_new_with_label(group, buf);
+ group = gtk_radio_menu_item_get_group(
+ GTK_RADIO_MENU_ITEM (item));
+ if (ua == ua_current)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
+ TRUE);
+ g_object_set_data(G_OBJECT(item), "ua", ua);
+ g_signal_connect(item, "toggled",
+ G_CALLBACK(menu_on_account_toggled), mod);
+ gtk_menu_shell_append(accounts_menu, item);
+ mod->accounts_menu_group = group;
+
+ return GTK_MENU_ITEM(item);
+}
+
+
+static GtkMenuItem *accounts_menu_get_item(struct gtk_mod *mod,
+ struct ua *ua)
+{
+ GtkMenuItem *item;
+ GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu);
+ GList *items = accounts_menu->children;
+
+ for (; items; items = items->next) {
+ item = items->data;
+ if (ua == g_object_get_data(G_OBJECT(item), "ua"))
+ return item;
+ }
+
+ /* Add new account not yet in menu */
+ return accounts_menu_add_item(mod, ua);
+}
+
+
+static void update_current_accounts_menu_item(struct gtk_mod *mod)
+{
+ GtkMenuItem *item = accounts_menu_get_item(mod,
+ uag_current());
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
+}
+
+
+static void update_ua_presence(struct gtk_mod *mod)
+{
+ GtkCheckMenuItem *item = 0;
+ enum presence_status cur_status;
+ void *status;
+ GtkMenuShell *status_menu = GTK_MENU_SHELL(mod->status_menu);
+ GList *items = status_menu->children;
+
+ cur_status = ua_presence_status(uag_current());
+
+ for (; items; items = items->next) {
+ item = items->data;
+ status = g_object_get_data(G_OBJECT(item), "presence");
+ if (cur_status == GPOINTER_TO_UINT(status))
+ break;
+ }
+ if (!item)
+ return;
+
+ gtk_check_menu_item_set_active(item, TRUE);
+}
+
+
+static const char *ua_event_reg_str(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING: return "registering";
+ case UA_EVENT_REGISTER_OK: return "OK";
+ case UA_EVENT_REGISTER_FAIL: return "ERR";
+ case UA_EVENT_UNREGISTERING: return "unregistering";
+ default: return "?";
+ }
+}
+
+
+static void accounts_menu_set_status(struct gtk_mod *mod,
+ struct ua *ua, enum ua_event ev)
+{
+ GtkMenuItem *item = accounts_menu_get_item(mod, ua);
+ char buf[256];
+ re_snprintf(buf, sizeof buf, "%s (%s)", ua_aor(ua),
+ ua_event_reg_str(ev));
+ gtk_menu_item_set_label(item, buf);
+}
+
+
+#ifdef USE_NOTIFICATIONS
+static void notify_incoming_call(struct gtk_mod *mod,
+ struct call *call)
+{
+ static const char *title = "Incoming call";
+ const char *msg = call_peeruri(call);
+ GtkWidget *call_menu;
+ GtkWidget *menu_item;
+#if defined(USE_LIBNOTIFY)
+ NotifyNotification *notification;
+
+ if (!notify_is_initted())
+ return;
+ notification = notify_notification_new(title, msg, "baresip");
+ notify_notification_set_urgency(notification, NOTIFY_URGENCY_CRITICAL);
+ notify_notification_show(notification, NULL);
+ g_object_unref(notification);
+
+#elif GLIB_CHECK_VERSION(2,40,0)
+ char id[64];
+ GVariant *target;
+ GNotification *notification = g_notification_new(title);
+
+ re_snprintf(id, sizeof id, "incoming-call-%p", call);
+ id[sizeof id - 1] = '\0';
+
+#if GLIB_CHECK_VERSION(2,42,0)
+ g_notification_set_priority(notification,
+ G_NOTIFICATION_PRIORITY_URGENT);
+#else
+ g_notification_set_urgent(notification, TRUE);
+#endif
+
+ target = g_variant_new_int64(GPOINTER_TO_INT(call));
+ g_notification_set_body(notification, msg);
+ g_notification_add_button_with_target_value(notification,
+ "Answer", "app.answer", target);
+ g_notification_add_button_with_target_value(notification,
+ "Reject", "app.reject", target);
+ g_application_send_notification(mod->app, id, notification);
+ g_object_unref(notification);
+
+#else
+ (void)msg;
+ (void)title;
+#endif
+
+ /* Add incoming call to the app menu */
+ call_menu = gtk_menu_new();
+ menu_item = gtk_menu_item_new_with_mnemonic("_Incoming call");
+ g_object_set_data(G_OBJECT(menu_item), "call", call);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+ call_menu);
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(mod->app_menu), menu_item);
+ mod->incoming_call_menus = g_slist_append(mod->incoming_call_menus,
+ menu_item);
+
+ menu_item = gtk_menu_item_new_with_label(call_peeruri(call));
+ gtk_widget_set_sensitive(menu_item, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
+
+ menu_item = gtk_menu_item_new_with_mnemonic("_Accept");
+ g_object_set_data(G_OBJECT(menu_item), "call", call);
+ g_signal_connect(G_OBJECT(menu_item), "activate",
+ G_CALLBACK(menu_on_incoming_call_answer), mod);
+ gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
+
+ menu_item = gtk_menu_item_new_with_mnemonic("_Reject");
+ g_object_set_data(G_OBJECT(menu_item), "call", call);
+ g_signal_connect(G_OBJECT(menu_item), "activate",
+ G_CALLBACK(menu_on_incoming_call_reject), mod);
+ gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
+}
+#endif
+
+
+static void denotify_incoming_call(struct gtk_mod *mod, struct call *call)
+{
+ GSList *item, *next;
+
+#if GLIB_CHECK_VERSION(2,40,0)
+ char id[64];
+
+ re_snprintf(id, sizeof id, "incoming-call-%p", call);
+ id[sizeof id - 1] = '\0';
+ g_application_withdraw_notification(mod->app, id);
+#endif
+
+ /* Remove call submenu */
+ for (item = mod->incoming_call_menus; item; item = next) {
+ GtkWidget *menu_item = item->data;
+ next = item->next;
+
+ if (call == g_object_get_data(G_OBJECT(menu_item), "call")) {
+ gtk_widget_destroy(menu_item);
+ mod->incoming_call_menus =
+ g_slist_delete_link(mod->incoming_call_menus,
+ item);
+ }
+ }
+}
+
+
+static void answer_activated(GSimpleAction *action, GVariant *parameter,
+ gpointer arg)
+{
+ struct gtk_mod *mod = arg;
+ struct call *call = get_call_from_gvariant(parameter);
+ (void)action;
+
+ if (call) {
+ denotify_incoming_call(mod, call);
+ mqueue_push(mod->mq, MQ_ANSWER, call);
+ }
+}
+
+
+static void reject_activated(GSimpleAction *action, GVariant *parameter,
+ gpointer arg)
+{
+ struct gtk_mod *mod = arg;
+ struct call *call = get_call_from_gvariant(parameter);
+ (void)action;
+
+ if (call) {
+ denotify_incoming_call(mod, call);
+ mqueue_push(mod->mq, MQ_HANGUP, call);
+ }
+}
+
+
+static struct call_window *new_call_window(struct gtk_mod *mod,
+ struct call *call)
+{
+ struct call_window *win = call_window_new(call, mod);
+ if (call) {
+ mod->call_windows = g_slist_append(mod->call_windows, win);
+ }
+ return win;
+}
+
+
+static struct call_window *get_call_window(struct gtk_mod *mod,
+ struct call *call)
+{
+ GSList *wins;
+
+ for (wins = mod->call_windows; wins; wins = wins->next) {
+ struct call_window *win = wins->data;
+ if (call_window_is_for_call(win, call))
+ return win;
+ }
+ return NULL;
+}
+
+
+static struct call_window *get_create_call_window(struct gtk_mod *mod,
+ struct call *call)
+{
+ struct call_window *win = get_call_window(mod, call);
+ if (!win)
+ win = new_call_window(mod, call);
+ return win;
+}
+
+
+void gtk_mod_call_window_closed(struct gtk_mod *mod, struct call_window *win)
+{
+ if (!mod)
+ return;
+ mod->call_windows = g_slist_remove(mod->call_windows, win);
+}
+
+
+static void ua_event_handler(struct ua *ua,
+ enum ua_event ev,
+ struct call *call,
+ const char *prm,
+ void *arg )
+{
+ struct gtk_mod *mod = arg;
+ struct call_window *win;
+
+ gdk_threads_enter();
+
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING:
+ case UA_EVENT_UNREGISTERING:
+ case UA_EVENT_REGISTER_OK:
+ case UA_EVENT_REGISTER_FAIL:
+ accounts_menu_set_status(mod, ua, ev);
+ break;
+
+#ifdef USE_NOTIFICATIONS
+ case UA_EVENT_CALL_INCOMING:
+ notify_incoming_call(mod, call);
+ break;
+#endif
+
+ case UA_EVENT_CALL_CLOSED:
+ win = get_call_window(mod, call);
+ if (win)
+ call_window_closed(win, prm);
+ else
+ denotify_incoming_call(mod, call);
+ break;
+
+ case UA_EVENT_CALL_RINGING:
+ win = get_create_call_window(mod, call);
+ if (win)
+ call_window_ringing(win);
+ break;
+
+ case UA_EVENT_CALL_PROGRESS:
+ win = get_create_call_window(mod, call);
+ if (win)
+ call_window_progress(win);
+ break;
+
+ case UA_EVENT_CALL_ESTABLISHED:
+ win = get_create_call_window(mod, call);
+ if (win)
+ call_window_established(win);
+ break;
+
+ case UA_EVENT_CALL_TRANSFER_FAILED:
+ win = get_create_call_window(mod, call);
+ if (win)
+ call_window_transfer_failed(win, prm);
+ break;
+
+ default:
+ break;
+ }
+
+ gdk_threads_leave();
+}
+
+
+#ifdef USE_NOTIFICATIONS
+static void message_handler(const struct pl *peer, const struct pl *ctype,
+ struct mbuf *body, void *arg)
+{
+ struct gtk_mod *mod = arg;
+ char title[128];
+ char msg[512];
+
+#if GLIB_CHECK_VERSION(2,40,0)
+ GNotification *notification;
+#elif defined(USE_LIBNOTIFY)
+ NotifyNotification *notification;
+#endif
+
+ (void)ctype;
+
+
+ /* Display notification of chat */
+
+ re_snprintf(title, sizeof title, "Chat from %r", peer);
+ title[sizeof title - 1] = '\0';
+
+ re_snprintf(msg, sizeof msg, "%b",
+ mbuf_buf(body), mbuf_get_left(body));
+
+#if GLIB_CHECK_VERSION(2,40,0)
+ notification = g_notification_new(title);
+ g_notification_set_body(notification, msg);
+ g_application_send_notification(mod->app, NULL, notification);
+ g_object_unref(notification);
+
+#elif defined(USE_LIBNOTIFY)
+ (void)mod;
+
+ if (!notify_is_initted())
+ return;
+ notification = notify_notification_new(title, msg, "baresip");
+ notify_notification_show(notification, NULL);
+ g_object_unref(notification);
+#endif
+}
+#endif
+
+
+static void popup_menu(struct gtk_mod *mod, GtkMenuPositionFunc position,
+ gpointer position_arg, guint button, guint32 activate_time)
+{
+ if (!mod->contacts_inited) {
+ init_contacts_menu(mod);
+ mod->contacts_inited = TRUE;
+ }
+
+ /* Update things that may have been changed through another UI */
+ update_current_accounts_menu_item(mod);
+ update_ua_presence(mod);
+
+ gtk_widget_show_all(mod->app_menu);
+
+ gtk_menu_popup(GTK_MENU(mod->app_menu), NULL, NULL,
+ position, position_arg,
+ button, activate_time);
+}
+
+
+static gboolean status_icon_on_button_press(GtkStatusIcon *status_icon,
+ GdkEventButton *event,
+ struct gtk_mod *mod)
+{
+ popup_menu(mod, gtk_status_icon_position_menu, status_icon,
+ event->button, event->time);
+ return TRUE;
+}
+
+
+void gtk_mod_connect(struct gtk_mod *mod, const char *uri)
+{
+ if (!mod)
+ return;
+ mqueue_push(mod->mq, MQ_CONNECT, (char *)uri);
+}
+
+
+static void warning_dialog(const char *title, const char *fmt, ...)
+{
+ va_list ap;
+ char msg[512];
+ GtkWidget *dialog;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(msg, sizeof msg, fmt, ap);
+ va_end(ap);
+
+ dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE, "%s", title);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ "%s", msg);
+ g_signal_connect_swapped(G_OBJECT(dialog), "response",
+ G_CALLBACK(gtk_widget_destroy), dialog);
+ gtk_window_set_title(GTK_WINDOW(dialog), title);
+ gtk_widget_show(dialog);
+}
+
+
+static void mqueue_handler(int id, void *data, void *arg)
+{
+ struct gtk_mod *mod = arg;
+ const char *uri;
+ struct call *call;
+ int err;
+ struct ua *ua = uag_current();
+ (void)mod;
+
+ switch ((enum gtk_mod_events)id) {
+
+ case MQ_POPUP:
+ gdk_threads_enter();
+ popup_menu(mod, NULL, NULL, 0, GPOINTER_TO_UINT(data));
+ gdk_threads_leave();
+ break;
+
+ case MQ_CONNECT:
+ uri = data;
+ err = ua_connect(ua, &call, NULL, uri, NULL, VIDMODE_ON);
+ if (err) {
+ gdk_threads_enter();
+ warning_dialog("Call failed",
+ "Connecting to \"%s\" failed.\n"
+ "Error: %m", uri, err);
+ gdk_threads_leave();
+ break;
+ }
+ gdk_threads_enter();
+ err = new_call_window(mod, call) == NULL;
+ gdk_threads_leave();
+ if (err) {
+ ua_hangup(ua, call, 500, "Server Error");
+ }
+ break;
+
+ case MQ_HANGUP:
+ call = data;
+ ua_hangup(ua, call, 0, NULL);
+ break;
+
+ case MQ_QUIT:
+ ua_stop_all(false);
+ break;
+
+ case MQ_ANSWER:
+ call = data;
+ err = ua_answer(ua, call);
+ if (err) {
+ gdk_threads_enter();
+ warning_dialog("Call failed",
+ "Answering the call "
+ "from \"%s\" failed.\n"
+ "Error: %m",
+ call_peername(call), err);
+ gdk_threads_leave();
+ break;
+ }
+
+ gdk_threads_enter();
+ err = new_call_window(mod, call) == NULL;
+ gdk_threads_leave();
+ if (err) {
+ ua_hangup(ua, call, 500, "Server Error");
+ }
+ break;
+
+ case MQ_SELECT_UA:
+ ua = data;
+ uag_current_set(ua);
+ break;
+ }
+}
+
+
+static void *gtk_thread(void *arg)
+{
+ struct gtk_mod *mod = arg;
+ GtkMenuShell *app_menu;
+ GtkWidget *item;
+ GError *err = NULL;
+ struct le *le;
+
+ gdk_threads_init();
+ gtk_init(0, NULL);
+
+ g_set_application_name("baresip");
+ mod->app = g_application_new ("com.creytiv.baresip",
+ G_APPLICATION_FLAGS_NONE);
+
+ g_application_register (G_APPLICATION (mod->app), NULL, &err);
+ if (err != NULL) {
+ warning ("Unable to register GApplication: %s",
+ err->message);
+ g_error_free (err);
+ err = NULL;
+ }
+
+#ifdef USE_LIBNOTIFY
+ notify_init("baresip");
+#endif
+
+ mod->status_icon = gtk_status_icon_new_from_icon_name("call-start");
+ gtk_status_icon_set_tooltip_text (mod->status_icon, "baresip");
+
+ g_signal_connect(G_OBJECT(mod->status_icon),
+ "button_press_event",
+ G_CALLBACK(status_icon_on_button_press), mod);
+ gtk_status_icon_set_visible(mod->status_icon, TRUE);
+
+ mod->contacts_inited = false;
+ mod->dial_dialog = NULL;
+ mod->call_windows = NULL;
+ mod->incoming_call_menus = NULL;
+
+ /* App menu */
+ mod->app_menu = gtk_menu_new();
+ app_menu = GTK_MENU_SHELL(mod->app_menu);
+
+ /* Account submenu */
+ mod->accounts_menu = gtk_menu_new();
+ mod->accounts_menu_group = NULL;
+ item = gtk_menu_item_new_with_mnemonic("_Account");
+ gtk_menu_shell_append(app_menu, item);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
+ mod->accounts_menu);
+
+ /* Add accounts to submenu */
+ for (le = list_head(uag_list()); le; le = le->next) {
+ struct ua *ua = le->data;
+ accounts_menu_add_item(mod, ua);
+ }
+
+ /* Status submenu */
+ mod->status_menu = gtk_menu_new();
+ item = gtk_menu_item_new_with_mnemonic("_Status");
+ gtk_menu_shell_append(GTK_MENU_SHELL(app_menu), item);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), mod->status_menu);
+
+ /* Open */
+ item = gtk_radio_menu_item_new_with_label(NULL, "Open");
+ g_object_set_data(G_OBJECT(item), "presence",
+ GINT_TO_POINTER(PRESENCE_OPEN));
+ g_signal_connect(item, "activate",
+ G_CALLBACK(menu_on_presence_set), mod);
+ gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item);
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
+
+ /* Closed */
+ item = gtk_radio_menu_item_new_with_label_from_widget(
+ GTK_RADIO_MENU_ITEM(item), "Closed");
+ g_object_set_data(G_OBJECT(item), "presence",
+ GINT_TO_POINTER(PRESENCE_CLOSED));
+ g_signal_connect(item, "activate",
+ G_CALLBACK(menu_on_presence_set), mod);
+ gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item);
+
+ gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
+
+ /* Dial */
+ item = gtk_menu_item_new_with_mnemonic("_Dial...");
+ gtk_menu_shell_append(app_menu, item);
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(menu_on_dial), mod);
+
+ /* Dial contact */
+ mod->contacts_menu = gtk_menu_new();
+ item = gtk_menu_item_new_with_mnemonic("Dial _contact");
+ gtk_menu_shell_append(app_menu, item);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
+ mod->contacts_menu);
+
+ gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
+
+ /* About */
+ item = gtk_menu_item_new_with_mnemonic("A_bout");
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(menu_on_about), mod);
+ gtk_menu_shell_append(app_menu, item);
+
+ gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
+
+ /* Quit */
+ item = gtk_menu_item_new_with_mnemonic("_Quit");
+ g_signal_connect(G_OBJECT(item), "activate",
+ G_CALLBACK(menu_on_quit), mod);
+ gtk_menu_shell_append(app_menu, item);
+
+ g_action_map_add_action_entries(G_ACTION_MAP(mod->app),
+ app_entries, G_N_ELEMENTS(app_entries), mod);
+
+ info("gtk_menu starting\n");
+
+ uag_event_register( ua_event_handler, mod );
+ mod->run = true;
+ gtk_main();
+ mod->run = false;
+ uag_event_unregister(ua_event_handler);
+
+ if (mod->dial_dialog) {
+ mem_deref(mod->dial_dialog);
+ mod->dial_dialog = NULL;
+ }
+
+ return NULL;
+}
+
+
+static void vu_enc_destructor(void *arg)
+{
+ struct vumeter_enc *st = arg;
+
+ list_unlink(&st->af.le);
+}
+
+
+static void vu_dec_destructor(void *arg)
+{
+ struct vumeter_dec *st = arg;
+
+ list_unlink(&st->af.le);
+}
+
+
+static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc)
+{
+ int32_t v = 0;
+ size_t i;
+
+ if (!sampv || !sampc)
+ return 0;
+
+ for (i=0; i<sampc; i++)
+ v += abs(sampv[i]);
+
+ return v/sampc;
+}
+
+
+static int vu_encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_enc *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), vu_enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ gdk_threads_enter();
+ call_window_got_vu_enc(st);
+ gdk_threads_leave();
+
+ *stp = (struct aufilt_enc_st *)st;
+
+ return 0;
+}
+
+
+static int vu_decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_dec *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), vu_dec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ gdk_threads_enter();
+ call_window_got_vu_dec(st);
+ gdk_threads_leave();
+
+ *stp = (struct aufilt_dec_st *)st;
+
+ return 0;
+}
+
+
+static int vu_encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_enc *vu = (struct vumeter_enc *)st;
+
+ vu->avg_rec = calc_avg_s16(sampv, *sampc);
+ vu->started = true;
+
+ return 0;
+}
+
+
+static int vu_decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_dec *vu = (struct vumeter_dec *)st;
+
+ vu->avg_play = calc_avg_s16(sampv, *sampc);
+ vu->started = true;
+
+ return 0;
+}
+
+
+static struct aufilt vumeter = {
+ LE_INIT, "gtk_vumeter",
+ vu_encode_update, vu_encode,
+ vu_decode_update, vu_decode
+};
+
+
+static int cmd_popup_menu(struct re_printf *pf, void *unused)
+{
+ (void)pf;
+ (void)unused;
+
+ mqueue_push(mod_obj.mq, MQ_POPUP, GUINT_TO_POINTER(GDK_CURRENT_TIME));
+
+ return 0;
+}
+
+
+static const struct cmd cmdv[] = {
+ {"gtk", 'G', 0, "Pop up GTK+ menu", cmd_popup_menu },
+};
+
+
+static int module_init(void)
+{
+ int err = 0;
+
+ err = mqueue_alloc(&mod_obj.mq, mqueue_handler, &mod_obj);
+ if (err)
+ return err;
+
+ aufilt_register(baresip_aufiltl(), &vumeter);
+
+#ifdef USE_NOTIFICATIONS
+ err = message_listen(&mod_obj.message, baresip_message(),
+ message_handler, &mod_obj);
+ if (err) {
+ warning("gtk: message_init failed (%m)\n", err);
+ return err;
+ }
+#endif
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ return err;
+
+ /* start the thread last */
+ err = pthread_create(&mod_obj.thread, NULL, gtk_thread,
+ &mod_obj);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ cmd_unregister(baresip_commands(), cmdv);
+ if (mod_obj.run) {
+ gdk_threads_enter();
+ gtk_main_quit();
+ gdk_threads_leave();
+ }
+ if (mod_obj.thread)
+ pthread_join(mod_obj.thread, NULL);
+ mod_obj.mq = mem_deref(mod_obj.mq);
+ aufilt_unregister(&vumeter);
+ mod_obj.message = mem_deref(mod_obj.message);
+
+#ifdef USE_LIBNOTIFY
+ if (notify_is_initted())
+ notify_uninit();
+#endif
+
+ g_slist_free(mod_obj.accounts_menu_group);
+ g_slist_free(mod_obj.call_windows);
+ g_slist_free(mod_obj.incoming_call_menus);
+
+ uag_event_unregister(ua_event_handler);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(gtk) = {
+ "gtk",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/gtk/gtk_mod.h b/modules/gtk/gtk_mod.h
new file mode 100644
index 0000000..ab123ca
--- /dev/null
+++ b/modules/gtk/gtk_mod.h
@@ -0,0 +1,51 @@
+/**
+ * @file gtk/gtk_mod.h GTK+ UI module -- internal API
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ */
+
+struct gtk_mod;
+struct call_window;
+struct dial_dialog;
+struct transfer_dialog;
+
+struct vumeter_enc {
+ struct aufilt_enc_st af; /* inheritance */
+ int16_t avg_rec;
+ volatile bool started;
+};
+
+struct vumeter_dec {
+ struct aufilt_dec_st af; /* inheritance */
+ int16_t avg_play;
+ volatile bool started;
+};
+
+/* Main menu */
+void gtk_mod_connect(struct gtk_mod *, const char *uri);
+void gtk_mod_call_window_closed(struct gtk_mod *, struct call_window *);
+
+/* Call Window */
+struct call_window *call_window_new(struct call *call, struct gtk_mod *mod);
+void call_window_got_vu_dec(struct vumeter_dec *);
+void call_window_got_vu_enc(struct vumeter_enc *);
+void call_window_transfer(struct call_window *, const char *uri);
+void call_window_closed(struct call_window *, const char *reason);
+void call_window_ringing(struct call_window *);
+void call_window_progress(struct call_window *);
+void call_window_established(struct call_window *);
+void call_window_transfer_failed(struct call_window *, const char *reason);
+bool call_window_is_for_call(struct call_window *, struct call *);
+
+/* Dial Dialog */
+struct dial_dialog *dial_dialog_alloc(struct gtk_mod *);
+void dial_dialog_show(struct dial_dialog *);
+
+/* Call transfer dialog */
+struct transfer_dialog *transfer_dialog_alloc(struct call_window *);
+void transfer_dialog_show(struct transfer_dialog *);
+void transfer_dialog_fail(struct transfer_dialog *, const char *reason);
+
+/* URI entry combo box */
+GtkWidget *uri_combo_box_new(void);
+const char *uri_combo_box_get_text(GtkComboBox *box);
diff --git a/modules/gtk/module.mk b/modules/gtk/module.mk
new file mode 100644
index 0000000..3735cf8
--- /dev/null
+++ b/modules/gtk/module.mk
@@ -0,0 +1,22 @@
+#
+# module.mk - GTK+ Menu-based UI
+#
+# Copyright (C) 2010 Creytiv.com
+# Copyright (C) 2015 Charles E. Lehner
+#
+
+MOD := gtk
+$(MOD)_SRCS += gtk_mod.c call_window.c dial_dialog.c transfer_dialog.c \
+ uri_entry.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs gtk+-2.0 $($(MOD)_EXTRA))
+$(MOD)_CFLAGS += \
+ $(shell pkg-config --cflags gtk+-2.0 $($(MOD)_EXTRA) | \
+ sed -e 's/-I/-isystem/g' )
+$(MOD)_CFLAGS += -Wno-strict-prototypes
+
+ifneq ($(USE_LIBNOTIFY),)
+$(MOD)_EXTRA = libnotify
+$(MOD)_CFLAGS += -DUSE_LIBNOTIFY=1
+endif
+
+include mk/mod.mk
diff --git a/modules/gtk/transfer_dialog.c b/modules/gtk/transfer_dialog.c
new file mode 100644
index 0000000..2bd199f
--- /dev/null
+++ b/modules/gtk/transfer_dialog.c
@@ -0,0 +1,134 @@
+/**
+ * @file transfer_dialog.c GTK+ call transfer dialog
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ */
+#include <re.h>
+#include <baresip.h>
+#include <gtk/gtk.h>
+#include "gtk_mod.h"
+
+struct transfer_dialog {
+ struct call_window *call_win;
+ GtkWidget *dialog;
+ GtkComboBox *uri_combobox;
+ GtkLabel *status_label;
+ GtkWidget *spinner;
+};
+
+static const char *status_progress = "progress";
+
+
+static void set_status(struct transfer_dialog *td, const char *status)
+{
+ if (status == status_progress) {
+ gtk_widget_show(td->spinner);
+ gtk_spinner_start(GTK_SPINNER(td->spinner));
+ gtk_label_set_text(td->status_label, NULL);
+ }
+ else {
+ gtk_widget_hide(td->spinner);
+ gtk_spinner_stop(GTK_SPINNER(td->spinner));
+ gtk_label_set_text(td->status_label, status);
+ }
+}
+
+
+static void on_dialog_response(GtkDialog *dialog, gint response_id,
+ struct transfer_dialog *win)
+{
+ char *uri;
+
+ if (response_id == GTK_RESPONSE_ACCEPT) {
+ uri = (char *)uri_combo_box_get_text(win->uri_combobox);
+ set_status(win, status_progress);
+ call_window_transfer(win->call_win, uri);
+ }
+ else {
+ set_status(win, NULL);
+ gtk_widget_hide(GTK_WIDGET(dialog));
+ }
+}
+
+
+static void destructor(void *arg)
+{
+ struct transfer_dialog *td = arg;
+
+ gtk_widget_destroy(td->dialog);
+}
+
+
+struct transfer_dialog *transfer_dialog_alloc(struct call_window *call_win)
+{
+ struct transfer_dialog *win;
+ GtkWidget *dialog, *content, *button, *image, *hbox, *spinner, *label;
+ GtkWidget *uri_combobox;
+
+ win = mem_zalloc(sizeof(*win), destructor);
+ if (!win)
+ return NULL;
+
+ dialog = gtk_dialog_new_with_buttons("Transfer", NULL, 0,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+
+ /* Transfer button */
+ button = gtk_button_new_with_label("Transfer");
+ image = gtk_image_new_from_icon_name("forward",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
+ GTK_RESPONSE_ACCEPT);
+ gtk_widget_set_can_default(button, TRUE);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog),
+ GTK_RESPONSE_ACCEPT);
+ /* Label */
+ content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ label = gtk_label_new("Transfer call to:");
+ gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0);
+
+ /* URI entry */
+ uri_combobox = uri_combo_box_new();
+ gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5);
+
+ g_signal_connect(dialog, "response",
+ G_CALLBACK(on_dialog_response), win);
+ g_signal_connect(dialog, "delete-event",
+ G_CALLBACK(gtk_widget_hide_on_delete), win);
+
+ /* Spinner and status */
+ hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 0);
+
+ spinner = gtk_spinner_new();
+ gtk_box_pack_start(GTK_BOX(hbox), spinner, TRUE, TRUE, 0);
+
+ label = gtk_label_new(NULL);
+ gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0);
+ win->status_label = GTK_LABEL(label);
+
+ win->dialog = dialog;
+ win->uri_combobox = GTK_COMBO_BOX(uri_combobox);
+ win->call_win = call_win;
+ win->spinner = spinner;
+
+ gtk_widget_show_all(dialog);
+ gtk_widget_hide(spinner);
+
+ return win;
+}
+
+void transfer_dialog_show(struct transfer_dialog *td)
+{
+ gtk_window_present(GTK_WINDOW(td->dialog));
+ gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(td->uri_combobox)));
+ set_status(td, NULL);
+}
+
+void transfer_dialog_fail(struct transfer_dialog *td, const char *reason)
+{
+ char buf[256];
+ re_snprintf(buf, sizeof buf, "Transfer failed: %s", reason);
+ set_status(td, buf);
+}
diff --git a/modules/gtk/uri_entry.c b/modules/gtk/uri_entry.c
new file mode 100644
index 0000000..0ca1a3d
--- /dev/null
+++ b/modules/gtk/uri_entry.c
@@ -0,0 +1,45 @@
+/**
+ * @file uri_entry.c GTK+ URI entry combo box
+ *
+ * Copyright (C) 2015 Charles E. Lehner
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <gtk/gtk.h>
+#include "gtk_mod.h"
+
+/**
+ * Create a URI combox box.
+ *
+ * The combo box has a menu of contacts, and a text entry for a URI.
+ *
+ * @return the combo box
+ */
+GtkWidget *uri_combo_box_new(void)
+{
+ struct contacts *contacts = baresip_contacts();
+ struct le *le;
+ GtkEntry *uri_entry;
+ GtkWidget *uri_combobox;
+
+ uri_combobox = gtk_combo_box_text_new_with_entry();
+ uri_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uri_combobox)));
+ gtk_entry_set_activates_default(uri_entry, TRUE);
+
+ for (le = list_head(contact_list(contacts)); le; le = le->next) {
+ struct contact *c = le->data;
+ gtk_combo_box_text_append_text(
+ GTK_COMBO_BOX_TEXT(uri_combobox),
+ contact_str(c));
+ }
+
+ return uri_combobox;
+}
+
+const char *uri_combo_box_get_text(GtkComboBox *box)
+{
+ GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(box)));
+ GtkEntryBuffer *buf = gtk_entry_get_buffer(entry);
+ return gtk_entry_buffer_get_text(buf);
+}
diff --git a/modules/gzrtp/gzrtp.cpp b/modules/gzrtp/gzrtp.cpp
new file mode 100644
index 0000000..19aaf9d
--- /dev/null
+++ b/modules/gzrtp/gzrtp.cpp
@@ -0,0 +1,220 @@
+/**
+ * @file gzrtp.cpp GNU ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <stdint.h>
+
+#include <re.h>
+#include <baresip.h>
+
+#include <string.h>
+
+#include <libzrtpcpp/ZRtp.h>
+
+#include "session.h"
+#include "stream.h"
+
+
+/**
+ * @defgroup gzrtp gzrtp
+ *
+ * ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Experimental support for ZRTP
+ *
+ * See http://tools.ietf.org/html/rfc6189
+ *
+ *
+ * This module is using GNU ZRTP C++ library
+ *
+ * https://github.com/wernerd/ZRTPCPP
+ *
+ * Configuration options:
+ *
+ \verbatim
+ zrtp_parallel {yes,no} # Start all streams at once
+ \endverbatim
+ *
+ */
+
+
+static ZRTPConfig *s_zrtp_config = NULL;
+
+
+struct menc_sess {
+ Session *session;
+};
+
+
+struct menc_media {
+ Stream *stream;
+};
+
+
+static void session_destructor(void *arg)
+{
+ struct menc_sess *st = (struct menc_sess *)arg;
+
+ delete st->session;
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct menc_media *st = (struct menc_media *)arg;
+
+ delete st->stream;
+}
+
+
+static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp,
+ bool offerer, menc_error_h *errorh, void *arg)
+{
+ struct menc_sess *st;
+ (void)offerer;
+ (void)errorh;
+ (void)arg;
+ int err = 0;
+
+ if (!sessp || !sdp)
+ return EINVAL;
+
+ st = (struct menc_sess *)mem_zalloc(sizeof(*st), session_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->session = new Session(*s_zrtp_config);
+ if (!st->session)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *sessp = st;
+
+ return err;
+}
+
+
+static int media_alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_media *st;
+ int err = 0;
+ StreamMediaType med_type;
+ const char *med_name;
+
+ if (!stp || !sess || !sess->session || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ st = *stp;
+ if (st)
+ goto start;
+
+ st = (struct menc_media *)mem_zalloc(sizeof(*st), media_destructor);
+ if (!st)
+ return ENOMEM;
+
+ med_name = sdp_media_name(sdpm);
+ if (str_cmp(med_name, "audio") == 0)
+ med_type = MT_AUDIO;
+ else if (str_cmp(med_name, "video") == 0)
+ med_type = MT_VIDEO;
+ else if (str_cmp(med_name, "text") == 0)
+ med_type = MT_TEXT;
+ else if (str_cmp(med_name, "application") == 0)
+ med_type = MT_APPLICATION;
+ else if (str_cmp(med_name, "message") == 0)
+ med_type = MT_MESSAGE;
+ else
+ med_type = MT_UNKNOWN;
+
+ st->stream = sess->session->create_stream(
+ *s_zrtp_config,
+ (struct udp_sock *)rtpsock,
+ (struct udp_sock *)rtcpsock,
+ rtp_sess_ssrc(rtp), med_type);
+ if (!st->stream) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->stream->sdp_encode(sdpm);
+
+ out:
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+ else
+ *stp = st;
+
+ start:
+ if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) {
+ st->stream->sdp_decode(sdpm);
+ err = sess->session->start_stream(st->stream);
+ if (err) {
+ warning("zrtp: stream start failed: %d\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+static struct menc menc_zrtp = {
+ LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc
+};
+
+
+static const struct cmd cmdv[] = {
+ {"zrtp_verify", 0, CMD_PRM, "Verify ZRTP SAS <session ID>",
+ Session::cmd_verify_sas },
+ {"zrtp_unverify", 0, CMD_PRM, "Unverify ZRTP SAS <session ID>",
+ Session::cmd_unverify_sas },
+};
+
+
+static int module_init(void)
+{
+ char config_path[256];
+ int err = 0;
+
+ err = conf_path_get(config_path, sizeof(config_path));
+ if (err) {
+ warning("zrtp: could not get config path: %m\n", err);
+ return err;
+ }
+
+ s_zrtp_config = new ZRTPConfig(conf_cur(), config_path);
+ if (!s_zrtp_config)
+ return ENOMEM;
+
+ menc_register(baresip_mencl(), &menc_zrtp);
+
+ return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ delete s_zrtp_config;
+ s_zrtp_config = NULL;
+
+ cmd_unregister(baresip_commands(), cmdv);
+
+ menc_unregister(&menc_zrtp);
+
+ return 0;
+}
+
+
+extern "C" EXPORT_SYM const struct mod_export DECL_EXPORTS(gzrtp) = {
+ "gzrtp",
+ "menc",
+ module_init,
+ module_close
+};
diff --git a/modules/gzrtp/messages.cpp b/modules/gzrtp/messages.cpp
new file mode 100644
index 0000000..db3a7db
--- /dev/null
+++ b/modules/gzrtp/messages.cpp
@@ -0,0 +1,256 @@
+/**
+ * @file messages.cpp GNU ZRTP: Engine messages
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <stdint.h>
+
+#include <re.h>
+#include <baresip.h>
+
+#include <libzrtpcpp/ZRtp.h>
+
+#include "stream.h"
+
+
+using namespace GnuZrtpCodes;
+
+
+#define NO_MESSAGE "NO MESSAGE DEFINED"
+
+
+static const char *info_msg(int32_t subcode)
+{
+ const char *msg;
+
+ switch (subcode) {
+ case InfoHelloReceived:
+ msg = "Hello received and prepared a Commit, "
+ "ready to get peer's hello hash";
+ break;
+ case InfoCommitDHGenerated:
+ msg = "Commit: Generated a public DH key";
+ break;
+ case InfoRespCommitReceived:
+ msg = "Responder: Commit received, preparing DHPart1";
+ break;
+ case InfoDH1DHGenerated:
+ msg = "DH1Part: Generated a public DH key";
+ break;
+ case InfoInitDH1Received:
+ msg = "Initiator: DHPart1 received, preparing DHPart2";
+ break;
+ case InfoRespDH2Received:
+ msg = "Responder: DHPart2 received, preparing Confirm1";
+ break;
+ case InfoInitConf1Received:
+ msg = "Initiator: Confirm1 received, preparing Confirm2";
+ break;
+ case InfoRespConf2Received:
+ msg = "Responder: Confirm2 received, preparing Conf2Ack";
+ break;
+ case InfoRSMatchFound:
+ msg = "At least one retained secret matches - security OK";
+ break;
+ case InfoSecureStateOn:
+ msg = "Entered secure state";
+ break;
+ case InfoSecureStateOff:
+ msg = "No more security for this session";
+ break;
+ default:
+ msg = NO_MESSAGE;
+ break;
+ }
+
+ return msg;
+}
+
+
+static const char *warning_msg(int32_t subcode)
+{
+ const char *msg;
+
+ switch (subcode) {
+ case WarningDHAESmismatch:
+ msg = "Commit contains an AES256 cipher but does not offer a "
+ "Diffie-Helman 4096 - not used DH4096 was discarded";
+ break;
+ case WarningGoClearReceived:
+ msg = "Received a GoClear message";
+ break;
+ case WarningDHShort:
+ msg = "Hello offers an AES256 cipher but does not offer a "
+ "Diffie-Helman 4096- not used DH4096 was discarded";
+ break;
+ case WarningNoRSMatch:
+ msg = "No retained shared secrets available - must verify SAS";
+ break;
+ case WarningCRCmismatch:
+ msg = "Internal ZRTP packet checksum mismatch - "
+ "packet dropped";
+ break;
+ case WarningSRTPauthError:
+ msg = "Dropping packet because SRTP authentication failed!";
+ break;
+ case WarningSRTPreplayError:
+ msg = "Dropping packet because SRTP replay check failed!";
+ break;
+ case WarningNoExpectedRSMatch:
+ msg = "Valid retained shared secrets availabe but no matches "
+ "found - must verify SAS";
+ break;
+ case WarningNoExpectedAuxMatch:
+ msg = "Our AUX secret was set but the other peer's AUX secret "
+ "does not match ours";
+ break;
+ default:
+ msg = NO_MESSAGE;
+ break;
+ }
+
+ return msg;
+}
+
+
+static const char *severe_msg(int32_t subcode)
+{
+ const char *msg;
+
+ switch (subcode) {
+ case SevereHelloHMACFailed:
+ msg = "Hash HMAC check of Hello failed!";
+ break;
+ case SevereCommitHMACFailed:
+ msg = "Hash HMAC check of Commit failed!";
+ break;
+ case SevereDH1HMACFailed:
+ msg = "Hash HMAC check of DHPart1 failed!";
+ break;
+ case SevereDH2HMACFailed:
+ msg = "Hash HMAC check of DHPart2 failed!";
+ break;
+ case SevereCannotSend:
+ msg = "Cannot send data - connection or peer down?";
+ break;
+ case SevereProtocolError:
+ msg = "Internal protocol error occured!";
+ break;
+ case SevereNoTimer:
+ msg = "Cannot start a timer - internal resources exhausted?";
+ break;
+ case SevereTooMuchRetries:
+ msg = "Too much retries during ZRTP negotiation - connection "
+ "or peer down?";
+ break;
+ default:
+ msg = NO_MESSAGE;
+ break;
+ }
+
+ return msg;
+}
+
+
+static const char *zrtp_msg(int32_t subcode)
+{
+ const char *msg;
+
+ switch (subcode) {
+ case MalformedPacket:
+ msg = "Malformed packet (CRC OK, but wrong structure)";
+ break;
+ case CriticalSWError:
+ msg = "Critical software error";
+ break;
+ case UnsuppZRTPVersion:
+ msg = "Unsupported ZRTP version";
+ break;
+ case HelloCompMismatch:
+ msg = "Hello components mismatch";
+ break;
+ case UnsuppHashType:
+ msg = "Hash type not supported";
+ break;
+ case UnsuppCiphertype:
+ msg = "Cipher type not supported";
+ break;
+ case UnsuppPKExchange:
+ msg = "Public key exchange not supported";
+ break;
+ case UnsuppSRTPAuthTag:
+ msg = "SRTP auth. tag not supported";
+ break;
+ case UnsuppSASScheme:
+ msg = "SAS scheme not supported";
+ break;
+ case NoSharedSecret:
+ msg = "No shared secret available, DH mode required";
+ break;
+ case DHErrorWrongPV:
+ msg = "DH Error: bad pvi or pvr ( == 1, 0, or p-1)";
+ break;
+ case DHErrorWrongHVI:
+ msg = "DH Error: hvi != hashed data";
+ break;
+ case SASuntrustedMiTM:
+ msg = "Received relayed SAS from untrusted MiTM";
+ break;
+ case ConfirmHMACWrong:
+ msg = "Auth. Error: Bad Confirm pkt HMAC";
+ break;
+ case NonceReused:
+ msg = "Nonce reuse";
+ break;
+ case EqualZIDHello:
+ msg = "Equal ZIDs in Hello";
+ break;
+ case GoCleatNotAllowed:
+ msg = "GoClear packet received, but not allowed";
+ break;
+ default:
+ msg = NO_MESSAGE;
+ break;
+ }
+
+ return msg;
+}
+
+
+void Stream::print_message(GnuZrtpCodes::MessageSeverity severity,
+ int32_t subcode)
+{
+ switch (severity) {
+ case Info:
+ debug("zrtp: INFO<%s>: %s\n",
+ media_name(), info_msg(subcode));
+ break;
+ case Warning:
+ warning("zrtp: WARNING<%s>: %s\n",
+ media_name(), warning_msg(subcode));
+ break;
+ case Severe:
+ warning("zrtp: SEVERE<%s>: %s\n",
+ media_name(), severe_msg(subcode));
+ break;
+ case ZrtpError:
+ warning("zrtp: ZRTP_ERR<%s>: %s\n",
+ media_name(), zrtp_msg(subcode));
+ break;
+ default:
+ return;
+ }
+}
+
+
+const char *Stream::media_name() const
+{
+ switch (m_media_type) {
+ case MT_AUDIO: return "audio";
+ case MT_VIDEO: return "video";
+ case MT_TEXT: return "text";
+ case MT_APPLICATION: return "application";
+ case MT_MESSAGE: return "message";
+ default: return "UNKNOWN";
+ }
+}
diff --git a/modules/gzrtp/module.mk b/modules/gzrtp/module.mk
new file mode 100644
index 0000000..42e408e
--- /dev/null
+++ b/modules/gzrtp/module.mk
@@ -0,0 +1,38 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2017 Creytiv.com
+#
+
+#
+# To build libzrtpcppcore run the following commands:
+#
+# git clone https://github.com/wernerd/ZRTPCPP.git
+# cd ZRTPCPP
+# mkdir build
+# cd build
+# cmake -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DCORE_LIB=1 -DSDES=1 \
+# -DBUILD_STATIC=1 ..
+# make
+#
+
+# GNU ZRTP C++ library (ZRTPCPP) source directory
+ZRTP_PATH ?= ../ZRTPCPP
+
+ZRTP_LIB := $(shell find $(ZRTP_PATH) -name libzrtpcppcore.a)
+
+MOD := gzrtp
+$(MOD)_SRCS += gzrtp.cpp session.cpp stream.cpp messages.cpp srtp.cpp
+$(MOD)_LFLAGS += $(ZRTP_LIB) -lstdc++
+$(MOD)_CXXFLAGS += \
+ -I$(ZRTP_PATH) \
+ -I$(ZRTP_PATH)/zrtp \
+ -I$(ZRTP_PATH)/srtp
+
+$(MOD)_CXXFLAGS += -O2 -Wall -fPIC
+
+# Uncomment this if you want to use libre SRTP facilities instead of the ones
+# provided by ZRTPCPP. In this case only standard ciphers (AES) are supported.
+#$(MOD)_CXXFLAGS += -DGZRTP_USE_RE_SRTP=1
+
+include mk/mod.mk
diff --git a/modules/gzrtp/session.cpp b/modules/gzrtp/session.cpp
new file mode 100644
index 0000000..422bee4
--- /dev/null
+++ b/modules/gzrtp/session.cpp
@@ -0,0 +1,214 @@
+/**
+ * @file session.h GNU ZRTP: Session class implementation
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <stdint.h>
+
+#include <re.h>
+#include <baresip.h>
+
+#include "session.h"
+
+
+std::vector<Session *> Session::s_sessl;
+
+
+Session::Session(const ZRTPConfig& config)
+ : m_start_parallel(config.start_parallel)
+ , m_master(NULL)
+ , m_encrypted(0)
+{
+ int newid = 1;
+ for (std::vector<Session *>::iterator it = s_sessl.begin();
+ it != s_sessl.end(); ++it) {
+
+ if ((*it)->id() >= newid)
+ newid = (*it)->id() + 1;
+ }
+
+ m_id = newid;
+
+ s_sessl.push_back(this);
+
+ debug("zrtp: New session <%d>\n", id());
+}
+
+
+Session::~Session()
+{
+ for (std::vector<Session *>::iterator it = s_sessl.begin();
+ it != s_sessl.end(); ++it) {
+
+ if (*it == this) {
+ s_sessl.erase(it);
+ break;
+ }
+ }
+
+ debug("zrtp: Session <%d> is destroyed\n", id());
+}
+
+
+Stream *Session::create_stream(const ZRTPConfig& config,
+ udp_sock *rtpsock,
+ udp_sock *rtcpsock,
+ uint32_t local_ssrc,
+ StreamMediaType media_type)
+{
+ int err = 0;
+
+ Stream *st = new Stream (err, config, this, rtpsock, rtcpsock,
+ local_ssrc, media_type);
+ if (!st || err) {
+ delete st;
+ return NULL;
+ }
+
+ return st;
+}
+
+
+int Session::start_stream(Stream *stream)
+{
+ if (stream->started())
+ return 0;
+
+ m_streams.push_back(stream);
+
+ // Start all streams in parallel using DH mode. This is a kind of
+ // probing. The first stream to receive HelloACK will be the master
+ // stream. If disabled, only the first stream starts in DH (master)
+ // mode.
+ if (m_start_parallel) {
+ if (m_master && m_encrypted)
+ // If we already have a master in secure state,
+ // start in multistream mode
+ return stream->start(m_master);
+ else
+ // Start a new stream in DH mode
+ return stream->start(NULL);
+ }
+ else {
+ if (!m_master) {
+ // Start the first stream in DH mode
+ m_master = stream;
+ return stream->start(NULL);
+ }
+ else if (m_encrypted) {
+ // Master is in secure state; multistream
+ return stream->start(m_master);
+ }
+ }
+
+ return 0;
+}
+
+
+bool Session::request_master(Stream *stream)
+{
+ if (!m_start_parallel)
+ return true;
+
+ if (m_master)
+ return false;
+
+ // This is the first stream to receive HelloACK. It will be
+ // used as the master for the other streams in the session.
+ m_master = stream;
+ // Stop other DH-mode streams. They will be started in the
+ // multistream mode after the master enters secure state.
+ for (std::vector<Stream *>::iterator it = m_streams.begin();
+ it != m_streams.end(); ++it) {
+
+ if (*it != m_master) {
+ (*it)->stop();
+ }
+ }
+
+ return true;
+}
+
+
+void Session::on_secure(Stream *stream)
+{
+ ++m_encrypted;
+
+ if (m_encrypted == m_streams.size() && m_master) {
+ info("zrtp: All streams are encrypted (%s), "
+ "SAS is [%s] (%s)\n",
+ m_master->get_ciphers(),
+ m_master->get_sas(),
+ (m_master->sas_verified())? "verified" : "NOT VERIFIED");
+ return;
+ }
+
+ if (stream != m_master)
+ return;
+
+ // Master stream has just entered secure state. Start other
+ // streams in the multistream mode.
+
+ debug("zrtp: Starting other streams (%d)\n", m_streams.size() - 1);
+
+ for (std::vector<Stream *>::iterator it = m_streams.begin();
+ it != m_streams.end(); ++it) {
+
+ if (*it != m_master) {
+ (*it)->start(m_master);
+ }
+ }
+}
+
+
+int Session::cmd_verify_sas(struct re_printf *pf, void *arg)
+{
+ return cmd_sas(true, pf, arg);
+}
+
+
+int Session::cmd_unverify_sas(struct re_printf *pf, void *arg)
+{
+ return cmd_sas(false, pf, arg);
+}
+
+
+int Session::cmd_sas(bool verify, struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = (struct cmd_arg *)arg;
+ (void)pf;
+ int id = -1;
+ Session *sess = NULL;
+
+ if (str_isset(carg->prm))
+ id = atoi(carg->prm);
+
+ for (std::vector<Session *>::iterator it = s_sessl.begin();
+ it != s_sessl.end(); ++it) {
+
+ if ((*it)->id() == id) {
+ sess = *it;
+ break;
+ }
+ }
+
+ if (!sess) {
+ warning("zrtp: No session with id %d\n", id);
+ return EINVAL;
+ }
+
+ if (!sess->m_master) {
+ warning("zrtp: No master stream for the session with id %d\n",
+ sess->id());
+ return EFAULT;
+ }
+
+ sess->m_master->verify_sas(verify);
+
+ info("zrtp: Session <%d>: SAS [%s] is %s\n", sess->id(),
+ sess->m_master->get_sas(),
+ (sess->m_master->sas_verified())? "verified" : "NOT VERIFIED");
+
+ return 0;
+}
+
diff --git a/modules/gzrtp/session.h b/modules/gzrtp/session.h
new file mode 100644
index 0000000..90c12d6
--- /dev/null
+++ b/modules/gzrtp/session.h
@@ -0,0 +1,50 @@
+/**
+ * @file session.h GNU ZRTP: Session class
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#ifndef __SESSION_H
+#define __SESSION_H
+
+
+#include "stream.h"
+
+
+class Stream;
+class ZRTPConfig;
+
+class Session {
+public:
+ Session(const ZRTPConfig& config);
+
+ ~Session();
+
+ Stream *create_stream(const ZRTPConfig& config,
+ udp_sock *rtpsock,
+ udp_sock *rtcpsock,
+ uint32_t local_ssrc,
+ StreamMediaType media_type);
+
+ int start_stream(Stream *stream);
+ int id() const { return m_id; }
+
+ bool request_master(Stream *stream);
+ void on_secure(Stream *stream);
+
+ static int cmd_verify_sas(struct re_printf *pf, void *arg);
+ static int cmd_unverify_sas(struct re_printf *pf, void *arg);
+ static int cmd_sas(bool verify, struct re_printf *pf, void *arg);
+
+private:
+ static std::vector<Session *> s_sessl;
+
+ const bool m_start_parallel;
+ int m_id;
+ std::vector<Stream *> m_streams;
+ Stream *m_master;
+ unsigned int m_encrypted;
+};
+
+
+#endif // __SESSION_H
+
diff --git a/modules/gzrtp/srtp.cpp b/modules/gzrtp/srtp.cpp
new file mode 100644
index 0000000..9aaa4b3
--- /dev/null
+++ b/modules/gzrtp/srtp.cpp
@@ -0,0 +1,327 @@
+/**
+ * @file srtp.cpp GNU ZRTP: SRTP processing
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <stdint.h>
+
+#include <re.h>
+#include <baresip.h>
+
+#ifdef GZRTP_USE_RE_SRTP
+#include <string.h>
+#else
+#include <srtp/CryptoContext.h>
+#include <srtp/CryptoContextCtrl.h>
+#include <srtp/SrtpHandler.h>
+#endif
+
+#include "srtp.h"
+
+
+Srtp::Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part)
+
+{
+ const uint8_t *key, *salt;
+ uint32_t key_len, salt_len;
+
+ err = EPERM;
+
+#ifdef GZRTP_USE_RE_SRTP
+ m_srtp = NULL;
+#else
+ m_cc = NULL;
+ m_cc_ctrl = NULL;
+#endif
+
+ if (part == ForSender) {
+ // To encrypt packets: intiator uses initiator keys,
+ // responder uses responder keys
+ if (secrets->role == Initiator) {
+ key = secrets->keyInitiator;
+ key_len = secrets->initKeyLen / 8;
+ salt = secrets->saltInitiator;
+ salt_len = secrets->initSaltLen / 8;
+ }
+ else {
+ key = secrets->keyResponder;
+ key_len = secrets->respKeyLen / 8;
+ salt = secrets->saltResponder;
+ salt_len = secrets->respSaltLen / 8;
+ }
+ }
+ else if (part == ForReceiver) {
+ // To decrypt packets: intiator uses responder keys,
+ // responder initiator keys
+ if (secrets->role == Initiator) {
+ key = secrets->keyResponder;
+ key_len = secrets->respKeyLen / 8;
+ salt = secrets->saltResponder;
+ salt_len = secrets->respSaltLen / 8;
+ }
+ else {
+ key = secrets->keyInitiator;
+ key_len = secrets->initKeyLen / 8;
+ salt = secrets->saltInitiator;
+ salt_len = secrets->initSaltLen / 8;
+ }
+ }
+ else {
+ err = EINVAL;
+ return;
+ }
+
+#ifdef GZRTP_USE_RE_SRTP
+
+ uint8_t key_buf[32 + 14]; // max key + salt
+ enum srtp_suite suite;
+ struct srtp *st;
+
+ if (secrets->symEncAlgorithm == Aes &&
+ secrets->authAlgorithm == Sha1) {
+
+ if (key_len == 16 && secrets->srtpAuthTagLen == 32)
+ suite = SRTP_AES_CM_128_HMAC_SHA1_32;
+
+ else if (key_len == 16 && secrets->srtpAuthTagLen == 80)
+ suite = SRTP_AES_CM_128_HMAC_SHA1_80;
+
+ else if (key_len == 32 && secrets->srtpAuthTagLen == 32)
+ suite = SRTP_AES_256_CM_HMAC_SHA1_32;
+
+ else if (key_len == 32 && secrets->srtpAuthTagLen == 80)
+ suite = SRTP_AES_256_CM_HMAC_SHA1_80;
+
+ else {
+ err = ENOTSUP;
+ return;
+ }
+ }
+ else {
+ err = ENOTSUP;
+ return;
+ }
+
+ if (salt_len != 14) {
+ err = EINVAL;
+ return;
+ }
+
+ memcpy(key_buf, key, key_len);
+ memcpy(key_buf + key_len, salt, salt_len);
+
+ err = srtp_alloc(&st, suite, key_buf, key_len + salt_len, 0);
+ if (err)
+ return;
+
+ m_auth_tag_len = secrets->srtpAuthTagLen / 8;
+ m_srtp = st;
+
+ err = 0;
+#else
+
+ CryptoContext *cc = NULL;
+ CryptoContextCtrl *cc_ctrl = NULL;
+ int cipher;
+ int authn;
+ int auth_key_len;
+
+ switch (secrets->authAlgorithm) {
+ case Sha1:
+ authn = SrtpAuthenticationSha1Hmac;
+ auth_key_len = 20;
+ break;
+ case Skein:
+ authn = SrtpAuthenticationSkeinHmac;
+ auth_key_len = 32;
+ break;
+ default:
+ err = ENOTSUP;
+ return;
+ }
+
+ switch (secrets->symEncAlgorithm) {
+ case Aes:
+ cipher = SrtpEncryptionAESCM;
+ break;
+ case TwoFish:
+ cipher = SrtpEncryptionTWOCM;
+ break;
+ default:
+ err = ENOTSUP;
+ return;
+ }
+
+ cc = new CryptoContext(
+ 0, // SSRC (used for lookup)
+ 0, // Roll-Over-Counter (ROC)
+ 0L, // keyderivation << 48,
+ cipher, // encryption algo
+ authn, // authtentication algo
+ (uint8_t *)key, // Master Key
+ key_len, // Master Key length
+ (uint8_t *)salt, // Master Salt
+ salt_len, // Master Salt length
+ key_len, // encryption keyl
+ auth_key_len, // authentication key len
+ salt_len, // session salt len
+ secrets->srtpAuthTagLen / 8); // authentication tag lenA
+
+ cc_ctrl = new CryptoContextCtrl(
+ 0, // SSRC (used for lookup)
+ cipher, // encryption algo
+ authn, // authtentication algo
+ (uint8_t *)key, // Master Key
+ key_len, // Master Key length
+ (uint8_t *)salt, // Master Salt
+ salt_len, // Master Salt length
+ key_len, // encryption keyl
+ auth_key_len, // authentication key len
+ salt_len, // session salt len
+ secrets->srtpAuthTagLen / 8); // authentication tag lenA
+
+ if (!cc || !cc_ctrl) {
+ delete cc;
+ delete cc_ctrl;
+
+ err = ENOMEM;
+ return;
+ }
+
+ cc->deriveSrtpKeys(0L);
+ cc_ctrl->deriveSrtcpKeys();
+
+ m_cc = cc;
+ m_cc_ctrl = cc_ctrl;
+
+ err = 0;
+#endif
+}
+
+
+Srtp::~Srtp()
+{
+#ifdef GZRTP_USE_RE_SRTP
+ mem_deref(m_srtp);
+#else
+ delete m_cc;
+ delete m_cc_ctrl;
+#endif
+}
+
+
+int Srtp::protect_int(struct mbuf *mb, bool control)
+{
+ size_t len = mbuf_get_left(mb);
+
+ int32_t extra = (mbuf_get_space(mb) > len)?
+ mbuf_get_space(mb) - len : 0;
+
+#ifdef GZRTP_USE_RE_SRTP
+ if (m_auth_tag_len + (control? 4 : 0) > extra)
+ return ENOMEM;
+
+ if (control)
+ return srtcp_encrypt(m_srtp, mb);
+ else
+ return srtp_encrypt(m_srtp, mb);
+#else
+ if (control) {
+ if (m_cc_ctrl->getTagLength() + 4 +
+ m_cc_ctrl->getMkiLength() > extra)
+ return ENOMEM;
+ }
+ else {
+ if (m_cc->getTagLength() +
+ m_cc->getMkiLength() > extra)
+ return ENOMEM;
+ }
+
+ bool rc;
+
+ if (control)
+ rc = SrtpHandler::protectCtrl(m_cc_ctrl, mbuf_buf(mb),
+ len, &len);
+ else
+ rc = SrtpHandler::protect(m_cc, mbuf_buf(mb), len, &len);
+ if (!rc)
+ return EPROTO;
+
+ if (len > mbuf_get_space(mb)) {
+ // this should never happen
+ error_msg("zrtp: protect: length > space (%u > %u)\n",
+ len, mbuf_get_space(mb));
+ abort();
+ }
+
+ mb->end = mb->pos + len;
+
+ return 0;
+#endif
+}
+
+
+int Srtp::protect(struct mbuf *mb)
+{
+ return protect_int(mb, false);
+}
+
+
+int Srtp::protect_ctrl(struct mbuf *mb)
+{
+ return protect_int(mb, true);
+}
+
+
+// return value:
+// 0 - OK
+// EBADMSG - SRTP/RTP packet decode error
+// EAUTH - SRTP authentication failed
+// EALREADY - SRTP replay check failed
+// other errors
+int Srtp::unprotect_int(struct mbuf *mb, bool control)
+{
+#ifdef GZRTP_USE_RE_SRTP
+ if (control)
+ return srtcp_decrypt(m_srtp, mb);
+ else
+ return srtp_decrypt(m_srtp, mb);
+#else
+ size_t len = mbuf_get_left(mb);
+ uint32_t rc;
+ int err;
+
+ if (control)
+ rc = SrtpHandler::unprotectCtrl(m_cc_ctrl, mbuf_buf(mb),
+ len, &len);
+ else
+ rc = SrtpHandler::unprotect(m_cc, mbuf_buf(mb),
+ len, &len, NULL);
+
+ switch (rc) {
+ case 1: err = 0; break;
+ case 0: err = EBADMSG; break;
+ case -1: err = EAUTH; break;
+ case -2: err = EALREADY; break;
+ default: err = EINVAL;
+ }
+
+ if (!err)
+ mb->end = mb->pos + len;
+
+ return err;
+#endif
+}
+
+
+int Srtp::unprotect(struct mbuf *mb)
+{
+ return unprotect_int(mb, false);
+}
+
+
+int Srtp::unprotect_ctrl(struct mbuf *mb)
+{
+ return unprotect_int(mb, true);
+}
+
diff --git a/modules/gzrtp/srtp.h b/modules/gzrtp/srtp.h
new file mode 100644
index 0000000..d42f4cf
--- /dev/null
+++ b/modules/gzrtp/srtp.h
@@ -0,0 +1,46 @@
+/**
+ * @file srtp.h GNU ZRTP: SRTP processing
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#ifndef __SRTP_H
+#define __SRTP_H
+
+
+#include <libzrtpcpp/ZrtpCallback.h>
+
+
+#ifdef GZRTP_USE_RE_SRTP
+struct srtp;
+#else
+class CryptoContext;
+class CryptoContextCtrl;
+#endif
+
+
+class Srtp {
+public:
+ Srtp(int& err, const SrtpSecret_t *secrets, EnableSecurity part);
+ ~Srtp();
+
+ int protect(struct mbuf *mb);
+ int protect_ctrl(struct mbuf *mb);
+ int unprotect(struct mbuf *mb);
+ int unprotect_ctrl(struct mbuf *mb);
+
+private:
+ int protect_int(struct mbuf *mb, bool control);
+ int unprotect_int(struct mbuf *mb, bool control);
+
+#ifdef GZRTP_USE_RE_SRTP
+ int32_t m_auth_tag_len;
+ struct srtp *m_srtp;
+#else
+ CryptoContext *m_cc;
+ CryptoContextCtrl *m_cc_ctrl;
+#endif
+};
+
+
+#endif // __SRTP_H
+
diff --git a/modules/gzrtp/stream.cpp b/modules/gzrtp/stream.cpp
new file mode 100644
index 0000000..84ad5e4
--- /dev/null
+++ b/modules/gzrtp/stream.cpp
@@ -0,0 +1,707 @@
+/**
+ * @file stream.cpp GNU ZRTP: Stream class implementation
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <stdint.h>
+#include <pthread.h>
+
+#include <re.h>
+#include <baresip.h>
+
+#include <libzrtpcpp/ZRtp.h>
+#include <libzrtpcpp/ZrtpStateClass.h>
+
+#include "session.h"
+#include "stream.h"
+#include "srtp.h"
+
+
+// A burst of SRTP/SRTCP errors enough to display a warning
+// Set to 1 to display all warnings
+#define SRTP_ERR_BURST_THRESHOLD 20
+
+
+enum {
+ PRESZ = 36 /* Preamble size for TURN/STUN header */
+};
+
+
+enum pkt_type {
+ PKT_TYPE_UNKNOWN = 0,
+ PKT_TYPE_RTP = 1,
+ PKT_TYPE_RTCP = 2,
+ PKT_TYPE_ZRTP = 4
+};
+
+
+static enum pkt_type get_packet_type(const struct mbuf *mb)
+{
+ uint8_t b, pt;
+ uint32_t magic;
+
+ if (mbuf_get_left(mb) < 8)
+ return PKT_TYPE_UNKNOWN;
+
+ b = mbuf_buf(mb)[0];
+
+ if (127 < b && b < 192) {
+ pt = mbuf_buf(mb)[1] & 0x7f;
+ if (72 <= pt && pt <= 76)
+ return PKT_TYPE_RTCP;
+ else
+ return PKT_TYPE_RTP;
+ }
+ else {
+ memcpy(&magic, &mbuf_buf(mb)[4], 4);
+ magic = ntohl(magic);
+ if (magic == ZRTP_MAGIC)
+ return PKT_TYPE_ZRTP;
+ }
+
+ return PKT_TYPE_UNKNOWN;
+}
+
+
+ZRTPConfig::ZRTPConfig(const struct conf *conf, const char *conf_dir)
+{
+#ifdef GZRTP_USE_RE_SRTP
+ // Standard ciphers only
+ zrtp.clear();
+
+ zrtp.addAlgo(HashAlgorithm, zrtpHashes.getByName(s256));
+
+ zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes3));
+ zrtp.addAlgo(CipherAlgorithm, zrtpSymCiphers.getByName(aes1));
+
+ zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec25));
+ zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh3k));
+ zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(ec38));
+ zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(dh2k));
+ zrtp.addAlgo(PubKeyAlgorithm, zrtpPubKeys.getByName(mult));
+
+ zrtp.addAlgo(SasType, zrtpSasTypes.getByName(b32));
+
+ zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs32));
+ zrtp.addAlgo(AuthLength, zrtpAuthLengths.getByName(hs80));
+#else
+ zrtp.setStandardConfig();
+#endif
+
+ str_ncpy(client_id, "baresip/gzrtp", sizeof(client_id));
+
+ re_snprintf(zid_filename, sizeof(zid_filename),
+ "%s/gzrtp.zid", conf_dir);
+
+ start_parallel = true;
+ (void)conf_get_bool(conf, "zrtp_parallel", &start_parallel);
+}
+
+SRTPStat::SRTPStat(const Stream *st, bool srtcp, uint64_t threshold)
+ : m_stream(st)
+ , m_control(srtcp)
+ , m_threshold(threshold)
+{
+ reset();
+}
+
+
+void SRTPStat::update(int ret_code, bool quiet)
+{
+ const char *err_msg;
+ uint64_t *burst;
+
+ // Srtp::unprotect/unprotect_ctrl return codes
+ switch (ret_code) {
+ case 0:
+ ++m_ok;
+ m_decode_burst = 0;
+ m_auth_burst = 0;
+ m_replay_burst = 0;
+ return;
+ case EBADMSG:
+ ++m_decode;
+ burst = &m_decode_burst;
+ err_msg = "packet decode error";
+ break;
+ case EAUTH:
+ ++m_auth;
+ burst = &m_auth_burst;
+ err_msg = "authentication failed";
+ break;
+ case EALREADY:
+ ++m_replay;
+ burst = &m_replay_burst;
+ err_msg = "replay check failed";
+ break;
+ default:
+ warning("zrtp: %s unprotect failed: %m\n",
+ (m_control)? "SRTCP" : "SRTP", ret_code);
+ return;
+ }
+
+ ++(*burst);
+ if (*burst == m_threshold) {
+ *burst = 0;
+
+ if (!quiet)
+ warning("zrtp: Stream <%s>: %s %s, %d packets\n",
+ m_stream->media_name(),
+ (m_control)? "SRTCP" : "SRTP",
+ err_msg,
+ m_threshold);
+ }
+}
+
+
+void SRTPStat::reset()
+{
+ m_ok = 0;
+ m_decode = 0; m_auth = 0; m_replay = 0;
+ m_decode_burst = 0; m_auth_burst = 0; m_replay_burst = 0;
+}
+
+
+Stream::Stream(int& err, const ZRTPConfig& config, Session *session,
+ udp_sock *rtpsock, udp_sock *rtcpsock,
+ uint32_t local_ssrc, StreamMediaType media_type)
+ : m_session(session)
+ , m_zrtp(NULL)
+ , m_started(false)
+ , m_local_ssrc(local_ssrc)
+ , m_peer_ssrc(0)
+ , m_rtpsock(NULL)
+ , m_rtcpsock(NULL)
+ , m_uh_rtp(NULL)
+ , m_uh_rtcp(NULL)
+ , m_media_type(media_type)
+ , m_send_srtp(NULL)
+ , m_recv_srtp(NULL)
+ , m_srtp_stat(this, false, SRTP_ERR_BURST_THRESHOLD)
+ , m_srtcp_stat(this, true, SRTP_ERR_BURST_THRESHOLD)
+{
+ err = 0;
+
+ m_zrtp_seq = 1; // TODO: randomize
+ sa_init(&m_raddr, AF_INET);
+ tmr_init(&m_zrtp_timer);
+
+ pthread_mutexattr_t attr;
+ err = pthread_mutexattr_init(&attr);
+ err |= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ err |= pthread_mutex_init(&m_zrtp_mutex, &attr);
+ err |= pthread_mutex_init(&m_send_mutex, &attr);
+ if (err)
+ return;
+
+ int layer = 10; // above zero
+ if (rtpsock) {
+ m_rtpsock = (struct udp_sock *)mem_ref(rtpsock);
+ err |= udp_register_helper(&m_uh_rtp, rtpsock, layer,
+ Stream::udp_helper_send_cb,
+ Stream::udp_helper_recv_cb,
+ this);
+ }
+ if (rtcpsock && (rtcpsock != rtpsock)) {
+ m_rtcpsock = (struct udp_sock *)mem_ref(rtcpsock);
+ err |= udp_register_helper(&m_uh_rtcp, rtcpsock, layer,
+ Stream::udp_helper_send_cb,
+ Stream::udp_helper_recv_cb,
+ this);
+ }
+ if (err)
+ return;
+
+ ZIDCache* zf = getZidCacheInstance();
+ if (!zf->isOpen()) {
+ if (zf->open((char *)config.zid_filename) == -1) {
+ warning("zrtp: Couldn't open/create ZID file %s\n",
+ config.zid_filename);
+ err = ENOENT;
+ return;
+ }
+ }
+
+ m_zrtp = new ZRtp((uint8_t *)zf->getZid(), this, config.client_id,
+ (ZrtpConfigure *)&config.zrtp, false, false);
+ if (!m_zrtp) {
+ err = ENOMEM;
+ return;
+ }
+
+ return;
+}
+
+
+Stream::~Stream()
+{
+ stop();
+
+ delete m_zrtp;
+
+ mem_deref(m_uh_rtp);
+ mem_deref(m_uh_rtcp);
+ mem_deref(m_rtpsock);
+ mem_deref(m_rtcpsock);
+
+ pthread_mutex_destroy(&m_zrtp_mutex);
+ pthread_mutex_destroy(&m_send_mutex);
+
+ tmr_cancel(&m_zrtp_timer);
+}
+
+
+int Stream::start(Stream *master)
+{
+ if (started())
+ return EPERM;
+
+ if (master) {
+ ZRtp *zrtp_master;
+
+ std::string params =
+ master->m_zrtp->getMultiStrParams(&zrtp_master);
+ if (params.empty())
+ return EPROTO;
+
+ m_zrtp->setMultiStrParams(params, zrtp_master);
+ }
+
+ debug("zrtp: Starting <%s> stream%s\n", media_name(),
+ (m_zrtp->isMultiStream())? " (multistream)" : "");
+
+ m_srtp_stat.reset();
+ m_srtcp_stat.reset();
+ m_sas.clear();
+ m_ciphers.clear();
+
+ m_started = true;
+ m_zrtp->startZrtpEngine();
+
+ return 0;
+}
+
+
+void Stream::stop()
+{
+ if (!started())
+ return;
+
+ m_started = false;
+
+ // If we got only a small amount of valid SRTP packets after ZRTP
+ // negotiation then assume that our peer couldn't store the RS data,
+ // thus make sure we have a second retained shared secret available.
+ // Refer to RFC 6189bis, chapter 4.6.1 50 packets are about 1 second
+ // of audio data
+ if (!m_zrtp->isMultiStream() && m_recv_srtp && m_srtp_stat.ok() < 20) {
+
+ debug("zrtp: Stream <%s>: received too few valid SRTP "
+ "packets (%u), storing RS2\n",
+ media_name(), m_srtp_stat.ok());
+
+ m_zrtp->setRs2Valid();
+ }
+
+ debug("zrtp: Stopping <%s> stream\n", media_name());
+
+ m_zrtp->stopZrtp();
+
+ pthread_mutex_lock(&m_send_mutex);
+ delete m_send_srtp;
+ m_send_srtp = NULL;
+ pthread_mutex_unlock(&m_send_mutex);
+
+ delete m_recv_srtp;
+ m_recv_srtp = NULL;
+
+ debug("zrtp: Stream <%s> stopped\n", media_name());
+}
+
+
+int Stream::sdp_encode(struct sdp_media *sdpm)
+{
+ // TODO: signaling hash
+ return 0;
+}
+
+
+int Stream::sdp_decode(const struct sdp_media *sdpm)
+{
+ if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) {
+ m_raddr = *sdp_media_raddr(sdpm);
+ }
+ // TODO: signaling hash
+
+ return 0;
+}
+
+
+bool Stream::udp_helper_send_cb(int *err, struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ Stream *st = (Stream *)arg;
+
+ if (st)
+ return st->udp_helper_send(err, src, mb);
+
+ return false;
+}
+
+
+bool Stream::udp_helper_send(int *err, struct sa *src, struct mbuf *mb)
+{
+ bool ret = false;
+ enum pkt_type ptype = get_packet_type(mb);
+ size_t len = mbuf_get_left(mb);
+ int rerr = 0;
+
+ pthread_mutex_lock(&m_send_mutex);
+
+ if (ptype == PKT_TYPE_RTCP && m_send_srtp && len > 8) {
+
+ rerr = m_send_srtp->protect_ctrl(mb);
+ }
+ else if (ptype == PKT_TYPE_RTP && m_send_srtp &&
+ len > RTP_HEADER_SIZE) {
+
+ rerr = m_send_srtp->protect(mb);
+ }
+ else
+ goto out;
+
+ if (rerr) {
+ warning("zrtp: protect/protect_ctrl failed (len=%u): %m\n",
+ len, rerr);
+
+ if (rerr == ENOMEM)
+ *err = rerr;
+ // drop
+ ret = true;
+ }
+
+ out:
+ pthread_mutex_unlock(&m_send_mutex);
+
+ return ret;
+}
+
+
+bool Stream::udp_helper_recv_cb(struct sa *src, struct mbuf *mb, void *arg)
+{
+ Stream *st = (Stream *)arg;
+
+ if (st)
+ return st->udp_helper_recv(src, mb);
+
+ return false;
+}
+
+
+bool Stream::udp_helper_recv(struct sa *src, struct mbuf *mb)
+{
+ if (!started())
+ return false;
+
+ enum pkt_type ptype = get_packet_type(mb);
+ int err = 0;
+
+ if (ptype == PKT_TYPE_RTCP && m_recv_srtp) {
+
+ err = m_recv_srtp->unprotect_ctrl(mb);
+
+ m_srtcp_stat.update(err);
+ }
+ else if (ptype == PKT_TYPE_RTP && m_recv_srtp) {
+
+ err = m_recv_srtp->unprotect(mb);
+
+ m_srtp_stat.update(err);
+
+ if (!err) {
+ // Got a good SRTP, check state and if in WaitConfAck
+ // (an Initiator state) then simulate a conf2Ack,
+ // refer to RFC 6189, chapter 4.6, last paragraph
+ if (m_zrtp->inState(WaitConfAck))
+ m_zrtp->conf2AckSecure();
+ }
+ }
+ else if (ptype == PKT_TYPE_ZRTP) {
+ return recv_zrtp(mb);
+ }
+ else
+ return false;
+
+ if (err)
+ // drop
+ return true;
+
+ return false;
+}
+
+
+// <RTP> + <ext. header> + <ZRTP message type> + CRC32
+#define ZRTP_MIN_PACKET_LENGTH (RTP_HEADER_SIZE + 4 + 8 + 4)
+
+bool Stream::recv_zrtp(struct mbuf *mb)
+{
+ uint32_t crc32;
+ uint8_t *buf = mbuf_buf(mb);
+ size_t size = mbuf_get_left(mb);
+
+ if (size < ZRTP_MIN_PACKET_LENGTH) {
+ warning("zrtp: incoming packet size (%d) is too small\n",
+ size);
+ return false;
+ }
+
+ // check CRC
+ memcpy(&crc32, buf + size - 4, 4);
+ crc32 = ntohl(crc32);
+ if (!zrtpCheckCksum(buf, size - 4, crc32)) {
+ sendInfo(GnuZrtpCodes::Warning,
+ GnuZrtpCodes::WarningCRCmismatch);
+ return false;
+ }
+
+ // store peer's SSRC for creating the CryptoContext
+ memcpy(&m_peer_ssrc, buf + 8, 4);
+ m_peer_ssrc = ntohl(m_peer_ssrc);
+
+ m_zrtp->processZrtpMessage(buf + RTP_HEADER_SIZE, m_peer_ssrc, size);
+
+ return true;
+}
+
+
+void Stream::verify_sas(bool verify)
+{
+ if (verify)
+ m_zrtp->SASVerified();
+ else
+ m_zrtp->resetSASVerified();
+}
+
+
+bool Stream::sas_verified()
+{
+ return m_zrtp->isSASVerified();
+}
+
+
+//
+// callbacks
+//
+
+
+int32_t Stream::sendDataZRTP(const uint8_t* data, int32_t length)
+{
+ struct mbuf *mb;
+ uint8_t *crc_buf;
+ uint32_t crc32;
+ size_t start_pos = PRESZ;
+ int err = 0;
+
+ if (!sa_isset(&m_raddr, SA_ALL))
+ return 0;
+
+ mb = mbuf_alloc(start_pos + RTP_HEADER_SIZE + length);
+ if (!mb)
+ return 0;
+
+ mbuf_set_end(mb, start_pos);
+ mbuf_set_pos(mb, start_pos);
+ crc_buf = mbuf_buf(mb);
+
+ // write RTP header
+ err = mbuf_write_u8(mb, 0x10);
+ err |= mbuf_write_u8(mb, 0x00);
+ err |= mbuf_write_u16(mb, htons(m_zrtp_seq++));
+ err |= mbuf_write_u32(mb, htonl(ZRTP_MAGIC));
+ err |= mbuf_write_u32(mb, htonl(m_local_ssrc));
+
+ // copy ZRTP message data
+ err |= mbuf_write_mem(mb, data, length - 4);
+
+ // compute CRC
+ crc32 = zrtpGenerateCksum(crc_buf, RTP_HEADER_SIZE + length - 4);
+ crc32 = zrtpEndCksum(crc32);
+
+ // store CRC
+ err |= mbuf_write_u32(mb, htonl(crc32));
+ if (err)
+ goto out;
+
+ // send ZRTP packet using RTP socket
+ mbuf_set_pos(mb, start_pos);
+ err = udp_send_helper(m_rtpsock, &m_raddr, mb, m_uh_rtp);
+ if (err)
+ warning("zrtp: udp_send_helper: %m\n", err);
+
+ out:
+ mem_deref(mb);
+
+ return (err == 0);
+}
+
+
+void Stream::zrtp_timer_cb(void *arg)
+{
+ Stream *s = (Stream *)arg;
+
+ s->m_zrtp->processTimeout();
+}
+
+
+int32_t Stream::activateTimer(int32_t time)
+{
+ tmr_start(&m_zrtp_timer, time, &Stream::zrtp_timer_cb, this);
+ return 1;
+}
+
+
+int32_t Stream::cancelTimer()
+{
+ tmr_cancel(&m_zrtp_timer);
+ return 1;
+}
+
+
+void Stream::sendInfo(GnuZrtpCodes::MessageSeverity severity, int32_t subCode)
+{
+ print_message(severity, subCode);
+
+ if (severity == GnuZrtpCodes::Info) {
+ if (subCode == GnuZrtpCodes::InfoSecureStateOn) {
+ m_session->on_secure(this);
+ }
+ else if (subCode == GnuZrtpCodes::InfoHelloReceived &&
+ !m_zrtp->isMultiStream()) {
+
+ m_session->request_master(this);
+ }
+ }
+}
+
+
+bool Stream::srtpSecretsReady(SrtpSecret_t* secrets, EnableSecurity part)
+{
+ Srtp *s;
+ int err = 0;
+
+ debug("zrtp: Stream <%s>: secrets are ready for %s\n",
+ media_name(),
+ (part == ForSender)? "sender" : "receiver");
+
+ s = new Srtp(err, secrets, part);
+ if (!s || err) {
+ warning("zrtp: Stream <%s>: Srtp creation failed: %m\n",
+ media_name(), err);
+ delete s;
+ return false;
+ }
+
+ if (part == ForSender) {
+ pthread_mutex_lock(&m_send_mutex);
+ m_send_srtp = s;
+ pthread_mutex_unlock(&m_send_mutex);
+ }
+ else if (part == ForReceiver)
+ m_recv_srtp = s;
+ else
+ return false;
+
+ return true;
+}
+
+
+void Stream::srtpSecretsOff(EnableSecurity part)
+{
+ debug("zrtp: Stream <%s>: secrets are off for %s\n",
+ media_name(),
+ (part == ForSender)? "sender" : "receiver");
+
+ if (part == ForSender) {
+ pthread_mutex_lock(&m_send_mutex);
+ delete m_send_srtp;
+ m_send_srtp = NULL;
+ pthread_mutex_unlock(&m_send_mutex);
+ }
+
+ if (part == ForReceiver) {
+ delete m_recv_srtp;
+ m_recv_srtp = NULL;
+ }
+}
+
+
+void Stream::srtpSecretsOn(std::string c, std::string s, bool verified)
+{
+ m_sas = s;
+ m_ciphers = c;
+
+ if (s.empty()) {
+ info("zrtp: Stream <%s> is encrypted (%s)\n",
+ media_name(), c.c_str());
+ }
+ else {
+ info("zrtp: Stream <%s> is encrypted (%s), "
+ "SAS is [%s] (%s)\n",
+ media_name(), c.c_str(), s.c_str(),
+ (verified)? "verified" : "NOT VERIFIED");
+ if (!verified)
+ warning("zrtp: SAS is not verified, type "
+ "'/zrtp_verify %d' to verify\n",
+ m_session->id());
+ }
+}
+
+
+void Stream::handleGoClear()
+{
+}
+
+
+void Stream::zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity severity,
+ int32_t subCode)
+{
+}
+
+
+void Stream::zrtpNotSuppOther()
+{
+}
+
+
+void Stream::synchEnter()
+{
+ pthread_mutex_lock(&m_zrtp_mutex);
+}
+
+
+void Stream::synchLeave()
+{
+ pthread_mutex_unlock(&m_zrtp_mutex);
+}
+
+
+void Stream::zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info)
+{
+}
+
+
+void Stream::zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info)
+{
+}
+
+
+void Stream::signSAS(uint8_t* sasHash)
+{
+}
+
+
+bool Stream::checkSASSignature(uint8_t* sasHash)
+{
+ return true;
+}
+
diff --git a/modules/gzrtp/stream.h b/modules/gzrtp/stream.h
new file mode 100644
index 0000000..d25a5c1
--- /dev/null
+++ b/modules/gzrtp/stream.h
@@ -0,0 +1,138 @@
+/**
+ * @file stream.h GNU ZRTP: Stream class
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#ifndef __STREAM_H
+#define __STREAM_H
+
+
+#include <libzrtpcpp/ZRtp.h>
+
+
+enum StreamMediaType {
+ MT_UNKNOWN = 0,
+ MT_AUDIO,
+ MT_VIDEO,
+ MT_TEXT,
+ MT_APPLICATION,
+ MT_MESSAGE
+};
+
+
+class ZRTPConfig {
+public:
+ ZRTPConfig(const struct conf *conf, const char *conf_dir);
+private:
+ friend class Stream;
+ friend class Session;
+
+ ZrtpConfigure zrtp;
+
+ char client_id[CLIENT_ID_SIZE + 1];
+ char zid_filename[256];
+
+ bool start_parallel;
+};
+
+
+class Stream;
+
+class SRTPStat {
+public:
+ SRTPStat(const Stream *st, bool srtcp, uint64_t threshold);
+ void update(int ret_code, bool quiet = false);
+ void reset();
+ uint64_t ok() { return m_ok; }
+private:
+ const Stream *m_stream;
+ const bool m_control;
+ const uint64_t m_threshold;
+ uint64_t m_ok, m_decode, m_auth, m_replay;
+ uint64_t m_decode_burst, m_auth_burst, m_replay_burst;
+};
+
+
+class Session;
+class Srtp;
+
+class Stream : public ZrtpCallback {
+public:
+ Stream(int& err, const ZRTPConfig& config, Session *session,
+ udp_sock *rtpsock, udp_sock *rtcpsock,
+ uint32_t local_ssrc, StreamMediaType media_type);
+
+ virtual ~Stream();
+
+ int start(Stream *master);
+ void stop();
+ bool started() { return m_started; }
+
+ int sdp_encode(struct sdp_media *sdpm);
+ int sdp_decode(const struct sdp_media *sdpm);
+
+ const char *media_name() const;
+
+ const char *get_sas() const { return m_sas.c_str(); }
+ const char *get_ciphers() const { return m_ciphers.c_str(); }
+ bool sas_verified();
+ void verify_sas(bool verify);
+
+private:
+ static void zrtp_timer_cb(void *arg);
+ static bool udp_helper_send_cb(int *err, struct sa *src,
+ struct mbuf *mb, void *arg);
+ static bool udp_helper_recv_cb(struct sa *src, struct mbuf *mb,
+ void *arg);
+
+ bool udp_helper_send(int *err, struct sa *src, struct mbuf *mb);
+ bool udp_helper_recv(struct sa *src, struct mbuf *mb);
+ bool recv_zrtp(struct mbuf *mb);
+
+ void print_message(GnuZrtpCodes::MessageSeverity severity,
+ int32_t subcode);
+
+ Session *m_session;
+ ZRtp *m_zrtp;
+ bool m_started;
+ struct tmr m_zrtp_timer;
+ pthread_mutex_t m_zrtp_mutex;
+ uint16_t m_zrtp_seq;
+ uint32_t m_local_ssrc, m_peer_ssrc;
+ struct sa m_raddr;
+ struct udp_sock *m_rtpsock, *m_rtcpsock;
+ struct udp_helper *m_uh_rtp;
+ struct udp_helper *m_uh_rtcp;
+ StreamMediaType m_media_type;
+ Srtp *m_send_srtp, *m_recv_srtp;
+ pthread_mutex_t m_send_mutex;
+ SRTPStat m_srtp_stat, m_srtcp_stat;
+ std::string m_sas, m_ciphers;
+
+protected:
+ virtual int32_t sendDataZRTP(const uint8_t* data, int32_t length);
+ virtual int32_t activateTimer(int32_t time);
+ virtual int32_t cancelTimer();
+ virtual void sendInfo(GnuZrtpCodes::MessageSeverity severity,
+ int32_t subCode);
+ virtual bool srtpSecretsReady(SrtpSecret_t* secrets,
+ EnableSecurity part);
+ virtual void srtpSecretsOff(EnableSecurity part);
+ virtual void srtpSecretsOn(std::string c, std::string s,
+ bool verified);
+ virtual void handleGoClear();
+ virtual void zrtpNegotiationFailed(
+ GnuZrtpCodes::MessageSeverity severity,
+ int32_t subCode);
+ virtual void zrtpNotSuppOther();
+ virtual void synchEnter();
+ virtual void synchLeave();
+ virtual void zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info);
+ virtual void zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info);
+ virtual void signSAS(uint8_t* sasHash);
+ virtual bool checkSASSignature(uint8_t* sasHash);
+};
+
+
+#endif // __STREAM_H
+
diff --git a/modules/h265/README b/modules/h265/README
new file mode 100644
index 0000000..75967c0
--- /dev/null
+++ b/modules/h265/README
@@ -0,0 +1,29 @@
+README h265 module
+------------------
+
+
+Steps for building and testing:
+
+
+1. build and install x265 from https://github.com/mirror/x265.git
+2. build and install ffmpeg from git://source.ffmpeg.org/ffmpeg.git
+
+ $ ./configure --disable-everything --enable-decoder=hevc \
+ && make -j4 && sudo make install
+
+3. build baresip with H265 module:
+
+ $ cd baresip
+ $ make EXTRA_MODULES=h265
+
+4. add h265.so to $HOME/.baresip/config
+ module h265.so
+
+5. start baresip with the "vidloop" module, to test a local loop
+ from a suitable vidsrc and vidisp module:
+
+ $ ./baresip -evv
+
+
+
+[END]
diff --git a/modules/h265/TODO b/modules/h265/TODO
new file mode 100644
index 0000000..e4d03dc
--- /dev/null
+++ b/modules/h265/TODO
@@ -0,0 +1,5 @@
+TODO:
+
+done - get encoder working
+done - get decoder working
+done - add timestamp to vidcodec API
diff --git a/modules/h265/decode.c b/modules/h265/decode.c
new file mode 100644
index 0000000..95d7102
--- /dev/null
+++ b/modules/h265/decode.c
@@ -0,0 +1,337 @@
+/**
+ * @file h265/decode.c H.265 Decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libavutil/pixdesc.h>
+#include <libavcodec/avcodec.h>
+#include "h265.h"
+
+
+#if LIBAVUTIL_VERSION_MAJOR < 52
+#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
+#endif
+
+
+enum {
+ FU_HDR_SIZE = 1
+};
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+struct fu {
+ unsigned s:1;
+ unsigned e:1;
+ unsigned type:6;
+};
+
+struct viddec_state {
+ AVCodecContext *ctx;
+ AVFrame *pict;
+ struct mbuf *mb;
+ size_t frag_start;
+ bool frag;
+ uint16_t frag_seq;
+};
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->ctx) {
+ avcodec_close(vds->ctx);
+ av_free(vds->ctx);
+ }
+
+ if (vds->pict)
+ av_free(vds->pict);
+
+ mem_deref(vds->mb);
+}
+
+
+int h265_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ AVCodec *codec;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ /* HEVC = H.265 */
+ codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
+ if (!codec) {
+ warning("h265: could not find H265 decoder\n");
+ return ENOSYS;
+ }
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ vds->mb = mbuf_alloc(1024);
+ if (!vds->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->pict = av_frame_alloc();
+ if (!vds->pict) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->ctx = avcodec_alloc_context3(codec);
+ if (!vds->ctx) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (avcodec_open2(vds->ctx, codec, NULL) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static inline int fu_decode(struct fu *fu, struct mbuf *mb)
+{
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ fu->s = v>>7 & 0x1;
+ fu->e = v>>6 & 0x1;
+ fu->type = v>>0 & 0x3f;
+
+ return 0;
+}
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+static inline void fragment_rewind(struct viddec_state *vds)
+{
+ vds->mb->pos = vds->frag_start;
+ vds->mb->end = vds->frag_start;
+}
+
+
+int h265_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ static const uint8_t nal_seq[3] = {0, 0, 1};
+ int err, ret, got_picture, i;
+ struct h265_nal hdr;
+ AVPacket avpkt;
+ enum vidfmt fmt;
+
+ if (!vds || !frame || !intra || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ err = h265_nal_decode(&hdr, mbuf_buf(mb));
+ if (err)
+ return err;
+
+ mbuf_advance(mb, H265_HDR_SIZE);
+
+#if 0
+ debug("h265: decode: %s type=%2d %s\n",
+ h265_is_keyframe(hdr.nal_unit_type) ? "<KEY>" : " ",
+ hdr.nal_unit_type,
+ h265_nalunit_name(hdr.nal_unit_type));
+#endif
+
+ if (vds->frag && hdr.nal_unit_type != H265_NAL_FU) {
+ debug("h265: lost fragments; discarding previous NAL\n");
+ fragment_rewind(vds);
+ vds->frag = false;
+ }
+
+ /* handle NAL types */
+ if (0 <= hdr.nal_unit_type && hdr.nal_unit_type <= 40) {
+
+ if (h265_is_keyframe(hdr.nal_unit_type))
+ *intra = true;
+
+ mb->pos -= H265_HDR_SIZE;
+
+ err = mbuf_write_mem(vds->mb, nal_seq, 3);
+ err |= mbuf_write_mem(vds->mb, mbuf_buf(mb),mbuf_get_left(mb));
+ if (err)
+ goto out;
+ }
+ else if (H265_NAL_FU == hdr.nal_unit_type) {
+
+ struct fu fu;
+
+ err = fu_decode(&fu, mb);
+ if (err)
+ return err;
+
+ if (fu.s) {
+ if (h265_is_keyframe(fu.type))
+ *intra = true;
+
+ if (vds->frag) {
+ debug("h265: lost fragments; ignoring NAL\n");
+ fragment_rewind(vds);
+ }
+
+ vds->frag_start = vds->mb->pos;
+ vds->frag = true;
+
+ hdr.nal_unit_type = fu.type;
+
+ err = mbuf_write_mem(vds->mb, nal_seq, 3);
+ err = h265_nal_encode_mbuf(vds->mb, &hdr);
+ if (err)
+ goto out;
+ }
+ else {
+ if (!vds->frag) {
+ debug("h265: ignoring fragment\n");
+ return 0;
+ }
+
+ if (seq_diff(vds->frag_seq, seq) != 1) {
+ debug("h265: lost fragments detected\n");
+ fragment_rewind(vds);
+ vds->frag = false;
+ return 0;
+ }
+ }
+
+ err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ if (fu.e)
+ vds->frag = false;
+
+ vds->frag_seq = seq;
+ }
+ else {
+ warning("h265: unknown NAL type %u (%s) [%zu bytes]\n",
+ hdr.nal_unit_type,
+ h265_nalunit_name(hdr.nal_unit_type),
+ mbuf_get_left(mb));
+ return EPROTO;
+ }
+
+ if (!marker) {
+
+ if (vds->mb->end > DECODE_MAXSZ) {
+ warning("h265: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ if (vds->frag) {
+ err = EPROTO;
+ goto out;
+ }
+
+ av_init_packet(&avpkt);
+ avpkt.data = vds->mb->buf;
+ avpkt.size = (int)vds->mb->end;
+
+#if LIBAVCODEC_VERSION_INT >= ((57<<16)+(37<<8)+100)
+
+ ret = avcodec_send_packet(vds->ctx, &avpkt);
+ if (ret < 0) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ ret = avcodec_receive_frame(vds->ctx, vds->pict);
+ if (ret < 0) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ got_picture = true;
+
+#else
+ ret = avcodec_decode_video2(vds->ctx, vds->pict, &got_picture, &avpkt);
+ if (ret < 0) {
+ debug("h265: decode error\n");
+ err = EPROTO;
+ goto out;
+ }
+#endif
+
+ if (!got_picture) {
+ /* debug("h265: no picture\n"); */
+ goto out;
+ }
+
+ switch (vds->pict->format) {
+
+ case AV_PIX_FMT_YUV420P:
+ fmt = VID_FMT_YUV420P;
+ break;
+
+ case AV_PIX_FMT_YUV444P:
+ fmt = VID_FMT_YUV444P;
+ break;
+
+ default:
+ warning("h265: decode: bad pixel format (%i) (%s)\n",
+ vds->pict->format,
+ av_get_pix_fmt_name(vds->pict->format));
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = vds->pict->data[i];
+ frame->linesize[i] = vds->pict->linesize[i];
+ }
+
+ frame->size.w = vds->ctx->width;
+ frame->size.h = vds->ctx->height;
+ frame->fmt = fmt;
+
+ out:
+ mbuf_rewind(vds->mb);
+ vds->frag = false;
+
+ return err;
+}
diff --git a/modules/h265/encode.c b/modules/h265/encode.c
new file mode 100644
index 0000000..f4835a3
--- /dev/null
+++ b/modules/h265/encode.c
@@ -0,0 +1,292 @@
+/**
+ * @file h265/encode.c H.265 Encode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <x265.h>
+#include "h265.h"
+
+
+struct videnc_state {
+ struct vidsz size;
+ x265_param *param;
+ x265_encoder *x265;
+ int64_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ videnc_packet_h *pkth;
+ void *arg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *st = arg;
+
+ if (st->x265)
+ x265_encoder_close(st->x265);
+ if (st->param)
+ x265_param_free(st->param);
+}
+
+
+static int set_params(struct videnc_state *st, unsigned fps, unsigned bitrate)
+{
+ st->param = x265_param_alloc();
+ if (!st->param) {
+ warning("h265: x265_param_alloc failed\n");
+ return ENOMEM;
+ }
+
+ x265_param_default(st->param);
+
+ if (0 != x265_param_apply_profile(st->param, "main")) {
+ warning("h265: x265_param_apply_profile failed\n");
+ return EINVAL;
+ }
+
+ if (0 != x265_param_default_preset(st->param,
+ "ultrafast", "zerolatency")) {
+
+ warning("h265: x265_param_default_preset error\n");
+ return EINVAL;
+ }
+
+ st->param->fpsNum = fps;
+ st->param->fpsDenom = 1;
+
+ /* VPS, SPS and PPS headers should be output with each keyframe */
+ st->param->bRepeatHeaders = 1;
+
+ /* Rate Control */
+ st->param->rc.rateControlMode = X265_RC_CRF;
+ st->param->rc.bitrate = bitrate / 1000;
+ st->param->rc.vbvMaxBitrate = bitrate / 1000;
+ st->param->rc.vbvBufferSize = 2 * bitrate / fps;
+
+ return 0;
+}
+
+
+int h265_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *ves;
+ int err = 0;
+ (void)fmtp;
+
+ if (!vesp || !vc || !prm || prm->pktsize < 3 || !pkth)
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->x265 && (ves->bitrate != prm->bitrate ||
+ ves->pktsize != prm->pktsize ||
+ ves->fps != prm->fps)) {
+
+ x265_encoder_close(ves->x265);
+ ves->x265 = NULL;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ err = set_params(ves, prm->fps, prm->bitrate);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *st, const struct vidsz *size)
+{
+ if (st->x265) {
+ debug("h265: re-opening encoder\n");
+ x265_encoder_close(st->x265);
+ }
+
+ st->param->sourceWidth = size->w;
+ st->param->sourceHeight = size->h;
+
+ st->x265 = x265_encoder_open(st->param);
+ if (!st->x265) {
+ warning("h265: x265_encoder_open failed\n");
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+
+static inline int packetize(bool marker, const uint8_t *buf, size_t len,
+ size_t maxlen, uint32_t rtp_ts,
+ videnc_packet_h *pkth, void *arg)
+{
+ int err = 0;
+
+ if (len <= maxlen) {
+ err = pkth(marker, rtp_ts, NULL, 0, buf, len, arg);
+ }
+ else {
+ struct h265_nal nal;
+ uint8_t fu_hdr[3];
+ const size_t flen = maxlen - sizeof(fu_hdr);
+
+ err = h265_nal_decode(&nal, buf);
+ if (err) {
+ warning("h265: encode: could not decode"
+ " NAL of %zu bytes (%m)\n", len, err);
+ return err;
+ }
+
+ h265_nal_encode(fu_hdr, H265_NAL_FU,
+ nal.nuh_temporal_id_plus1);
+
+ fu_hdr[2] = 1<<7 | nal.nal_unit_type;
+
+ buf+=2;
+ len-=2;
+
+ while (len > flen) {
+ err |= pkth(false, rtp_ts, fu_hdr, 3, buf, flen,
+ arg);
+
+ buf += flen;
+ len -= flen;
+ fu_hdr[2] &= ~(1 << 7); /* clear Start bit */
+ }
+
+ fu_hdr[2] |= 1<<6; /* set END bit */
+
+ err |= pkth(marker, rtp_ts, fu_hdr, 3, buf, len,
+ arg);
+ }
+
+ return err;
+}
+
+
+int h265_encode(struct videnc_state *st, bool update,
+ const struct vidframe *frame)
+{
+ x265_picture *pic_in = NULL, pic_out;
+ x265_nal *nalv;
+ uint32_t i, nalc = 0;
+ int colorspace;
+ int n, err = 0;
+ uint32_t ts;
+
+ if (!st || !frame)
+ return EINVAL;
+
+ switch (frame->fmt) {
+
+ case VID_FMT_YUV420P:
+ colorspace = X265_CSP_I420;
+ break;
+
+ case VID_FMT_YUV444P:
+ colorspace = X265_CSP_I444;
+ break;
+
+ default:
+ warning("h265: encode: pixel format not supported (%s)\n",
+ vidfmt_name(frame->fmt));
+ return EINVAL;
+ }
+
+ if (!st->x265 || !vidsz_cmp(&st->size, &frame->size) ||
+ st->param->internalCsp != colorspace) {
+
+ debug("h265: encoder: reset %u x %u (%s)\n",
+ frame->size.w, frame->size.h, vidfmt_name(frame->fmt));
+
+ st->param->internalCsp = colorspace;
+
+ err = open_encoder(st, &frame->size);
+ if (err)
+ return err;
+
+ st->size = frame->size;
+ }
+
+ if (update) {
+ debug("h265: encode: picture update was requested\n");
+ }
+
+ pic_in = x265_picture_alloc();
+ if (!pic_in) {
+ warning("h265: x265_picture_alloc failed\n");
+ return ENOMEM;
+ }
+
+ x265_picture_init(st->param, pic_in);
+
+ pic_in->sliceType = update ? X265_TYPE_IDR : X265_TYPE_AUTO;
+ pic_in->pts = ++st->pts; /* XXX: add PTS to API */
+ pic_in->colorSpace = colorspace;
+
+ for (i=0; i<3; i++) {
+ pic_in->planes[i] = frame->data[i];
+ pic_in->stride[i] = frame->linesize[i];
+ }
+
+ /* NOTE: important to get the PTS of the "out" picture */
+ n = x265_encoder_encode(st->x265, &nalv, &nalc, pic_in, &pic_out);
+ if (n <= 0)
+ goto out;
+
+ ts = video_calc_rtp_timestamp(pic_out.pts, st->fps);
+
+ for (i=0; i<nalc; i++) {
+
+ x265_nal *nal = &nalv[i];
+ uint8_t *p = nal->payload;
+ size_t len = nal->sizeBytes;
+ bool marker;
+
+#if 0
+ debug("h265: encode: %s type=%2d %s\n",
+ h265_is_keyframe(nal->type) ? "<KEY>" : " ",
+ nal->type, h265_nalunit_name(nal->type));
+#endif
+
+ h265_skip_startcode(&p, &len);
+
+ /* XXX: use pic_out.pts */
+
+ marker = (i+1)==nalc; /* last NAL */
+
+ err = packetize(marker, p, len, st->pktsize,
+ ts, st->pkth, st->arg);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (pic_in)
+ x265_picture_free(pic_in);
+
+ return err;
+}
diff --git a/modules/h265/fmt.c b/modules/h265/fmt.c
new file mode 100644
index 0000000..ac71b10
--- /dev/null
+++ b/modules/h265/fmt.c
@@ -0,0 +1,165 @@
+/**
+ * @file h265/fmt.c H.265 Video Codec -- protocol format
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "h265.h"
+
+
+/*
+1.1.4 NAL Unit Header
+
+ HEVC maintains the NAL unit concept of H.264 with modifications.
+ HEVC uses a two-byte NAL unit header, as shown in Figure 1. The
+ payload of a NAL unit refers to the NAL unit excluding the NAL unit
+ header.
+
+ +---------------+---------------+
+ |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |F| Type | LayerId | TID |
+ +-------------+-----------------+
+
+ Figure 1 The structure of HEVC NAL unit header
+*/
+
+
+void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type,
+ unsigned nuh_temporal_id_plus1)
+{
+ if (!buf)
+ return;
+
+ buf[0] = (nal_unit_type & 0x3f) << 1;
+ buf[1] = nuh_temporal_id_plus1 & 0x07;
+}
+
+
+int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal)
+{
+ uint8_t buf[2];
+
+ h265_nal_encode(buf, nal->nal_unit_type, nal->nuh_temporal_id_plus1);
+
+ return mbuf_write_mem(mb, buf, sizeof(buf));
+}
+
+
+int h265_nal_decode(struct h265_nal *nal, const uint8_t *p)
+{
+ bool forbidden_zero_bit;
+ unsigned nuh_layer_id;
+
+ if (!nal || !p)
+ return EINVAL;
+
+ forbidden_zero_bit = p[0] >> 7;
+ nal->nal_unit_type = (p[0] >> 1) & 0x3f;
+ nuh_layer_id = (p[0]&1)<<5 | p[1] >> 3;
+ nal->nuh_temporal_id_plus1 = p[1] & 0x07;
+
+ if (forbidden_zero_bit) {
+ warning("h265: nal_decode: FORBIDDEN bit set\n");
+ return EBADMSG;
+ }
+ if (nuh_layer_id != 0) {
+ warning("h265: nal_decode: LayerId MUST be zero\n");
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+
+void h265_nal_print(const struct h265_nal *nal)
+{
+ re_printf("type=%u(%s), TID=%u\n",
+ nal->nal_unit_type,
+ h265_nalunit_name(nal->nal_unit_type),
+ nal->nuh_temporal_id_plus1);
+}
+
+
+static const uint8_t sc3[3] = {0, 0, 1};
+static const uint8_t sc4[4] = {0, 0, 0, 1};
+
+
+void h265_skip_startcode(uint8_t **p, size_t *n)
+{
+ if (*n < 4)
+ return;
+
+ if (0 == memcmp(*p, sc4, 4)) {
+ (*p) += 4;
+ *n -= 4;
+ }
+ else if (0 == memcmp(*p, sc3, 3)) {
+ (*p) += 3;
+ *n -= 3;
+ }
+}
+
+
+bool h265_have_startcode(const uint8_t *p, size_t len)
+{
+ if (len >= 4 && 0 == memcmp(p, sc4, 4)) return true;
+ if (len >= 3 && 0 == memcmp(p, sc3, 3)) return true;
+
+ return false;
+}
+
+
+bool h265_is_keyframe(enum h265_naltype type)
+{
+ /* between 16 and 21 (inclusive) */
+ switch (type) {
+
+ case H265_NAL_BLA_W_LP:
+ case H265_NAL_BLA_W_RADL:
+ case H265_NAL_BLA_N_LP:
+ case H265_NAL_IDR_W_RADL:
+ case H265_NAL_IDR_N_LP:
+ case H265_NAL_CRA_NUT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+const char *h265_nalunit_name(enum h265_naltype type)
+{
+ switch (type) {
+
+ /* VCL class */
+ case H265_NAL_TRAIL_N: return "TRAIL_N";
+ case H265_NAL_TRAIL_R: return "TRAIL_R";
+
+ case H265_NAL_RASL_N: return "RASL_N";
+ case H265_NAL_RASL_R: return "RASL_R";
+
+ case H265_NAL_BLA_W_LP: return "BLA_W_LP";
+ case H265_NAL_BLA_W_RADL: return "BLA_W_RADL";
+ case H265_NAL_BLA_N_LP: return "BLA_N_LP";
+ case H265_NAL_IDR_W_RADL: return "IDR_W_RADL";
+ case H265_NAL_IDR_N_LP: return "IDR_N_LP";
+ case H265_NAL_CRA_NUT: return "CRA_NUT";
+
+ /* non-VCL class */
+ case H265_NAL_VPS_NUT: return "VPS_NUT";
+ case H265_NAL_SPS_NUT: return "SPS_NUT";
+ case H265_NAL_PPS_NUT: return "PPS_NUT";
+ case H265_NAL_PREFIX_SEI_NUT: return "PREFIX_SEI_NUT";
+ case H265_NAL_SUFFIX_SEI_NUT: return "SUFFIX_SEI_NUT";
+
+ /* draft-ietf-payload-rtp-h265 */
+ case H265_NAL_AP: return "H265_NAL_AP";
+ case H265_NAL_FU: return "H265_NAL_FU";
+ }
+
+ return "???";
+}
diff --git a/modules/h265/h265.c b/modules/h265/h265.c
new file mode 100644
index 0000000..fc289e2
--- /dev/null
+++ b/modules/h265/h265.c
@@ -0,0 +1,67 @@
+/**
+ * @file h265.c H.265 Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <libavcodec/avcodec.h>
+#include <x265.h>
+#include "h265.h"
+
+
+/**
+ * @defgroup h265 h265
+ *
+ * The H.265 video codec (aka HEVC)
+ *
+ * This is an experimental module adding support for H.265 video codec.
+ * The encoder is using x265 and the decoder is using libavcodec.
+ *
+ *
+ * References:
+ *
+ * https://tools.ietf.org/html/rfc7798
+ * http://x265.org/
+ * https://www.ffmpeg.org/
+ */
+
+
+static struct vidcodec h265 = {
+ .name = "H265",
+ .fmtp = "profile-id=1",
+ .encupdh = h265_encode_update,
+ .ench = h265_encode,
+ .decupdh = h265_decode_update,
+ .dech = h265_decode,
+};
+
+
+static int module_init(void)
+{
+ info("h265: using x265 %s %s\n",
+ x265_version_str, x265_build_info_str);
+
+ avcodec_register_all();
+
+ vidcodec_register(baresip_vidcodecl(), &h265);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister(&h265);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(h265) = {
+ "h265",
+ "vidcodec",
+ module_init,
+ module_close,
+};
diff --git a/modules/h265/h265.h b/modules/h265/h265.h
new file mode 100644
index 0000000..1b1571f
--- /dev/null
+++ b/modules/h265/h265.h
@@ -0,0 +1,70 @@
+/**
+ * @file h265.h H.265 Video Codec -- internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/*
+ * H.265 format
+ */
+enum {
+ H265_HDR_SIZE = 2
+};
+
+enum h265_naltype {
+ /* VCL class */
+ H265_NAL_TRAIL_N = 0,
+ H265_NAL_TRAIL_R = 1,
+
+ H265_NAL_RASL_N = 8,
+ H265_NAL_RASL_R = 9,
+
+ H265_NAL_BLA_W_LP = 16,
+ H265_NAL_BLA_W_RADL = 17,
+ H265_NAL_BLA_N_LP = 18,
+ H265_NAL_IDR_W_RADL = 19,
+ H265_NAL_IDR_N_LP = 20,
+ H265_NAL_CRA_NUT = 21,
+
+ /* non-VCL class */
+ H265_NAL_VPS_NUT = 32,
+ H265_NAL_SPS_NUT = 33,
+ H265_NAL_PPS_NUT = 34,
+ H265_NAL_PREFIX_SEI_NUT = 39,
+ H265_NAL_SUFFIX_SEI_NUT = 40,
+
+ /* draft-ietf-payload-rtp-h265 */
+ H265_NAL_AP = 48, /* Aggregation Packets */
+ H265_NAL_FU = 49,
+};
+
+struct h265_nal {
+ unsigned nal_unit_type:6; /* NAL unit type (0-40) */
+ unsigned nuh_temporal_id_plus1:3; /* temporal identifier plus 1 */
+};
+
+void h265_nal_encode(uint8_t buf[2], unsigned nal_unit_type,
+ unsigned nuh_temporal_id_plus1);
+int h265_nal_encode_mbuf(struct mbuf *mb, const struct h265_nal *nal);
+int h265_nal_decode(struct h265_nal *nal, const uint8_t *p);
+void h265_nal_print(const struct h265_nal *nal);
+
+bool h265_have_startcode(const uint8_t *p, size_t len);
+void h265_skip_startcode(uint8_t **p, size_t *n);
+bool h265_is_keyframe(enum h265_naltype type);
+const char *h265_nalunit_name(enum h265_naltype type);
+
+
+/* encoder */
+int h265_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int h265_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+/* decoder */
+int h265_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int h265_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb);
diff --git a/modules/h265/module.mk b/modules/h265/module.mk
new file mode 100644
index 0000000..12980bf
--- /dev/null
+++ b/modules/h265/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := h265
+$(MOD)_SRCS += h265.c encode.c decode.c fmt.c
+$(MOD)_LFLAGS += -lavcodec -lavutil -lx265
+
+include mk/mod.mk
diff --git a/modules/h265/notes b/modules/h265/notes
new file mode 100644
index 0000000..0f01c5a
--- /dev/null
+++ b/modules/h265/notes
@@ -0,0 +1,75 @@
+notes:
+-----
+
+
+x265 [info]: HEVC encoder version 1.4-253-g920d714
+x265 [info]: build info [Linux][GCC 4.9.1][64 bit] 8bpp
+x265 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
+x265 [info]: Main profile, Level-2 (Main tier)
+x265 [info]: WPP streams / frame threads / pool : 8 / 2 / 4
+x265 [info]: CTU size / RQT depth inter / intra : 32 / 1 / 1
+x265 [info]: ME / range / subpel / merge : dia / 25 / 0 / 2
+x265 [info]: Keyframe min / max / scenecut : 25 / 250 / 0
+x265 [info]: Lookahead / bframes / badapt : 10 / 4 / 0
+x265 [info]: b-pyramid / weightp / weightb / refs: 1 / 0 / 0 / 1
+x265 [info]: Rate Control / AQ-Strength / CUTree : CRF-28.0 / 0.0 / 0
+x265 [info]: VBV/HRD buffer / max-rate / init : 40960 / 512 / 0.900
+x265 [info]: tools: rd=2 early-skip fast-intra tmvp
+
+
+x265 [info]: frame I: 1, Avg QP:25.40 kb/s: 639.60
+x265 [info]: frame P: 3, Avg QP:30.29 kb/s: 256.93
+x265 [info]: frame B: 12, Avg QP:32.92 kb/s: 58.68
+x265 [info]: global : 16, Avg QP:31.96 kb/s: 132.16
+x265 [info]: consecutive B-frames: 20.0% 0.0% 0.0% 0.0% 80.0%
+
+
+
+
+h265: decode: type=32 VPS_NUT
+h265: decode: type=33 SPS_NUT
+h265: decode: type=34 PPS_NUT
+h265: decode: type=39 PREFIX_SEI_NUT
+h265: decode: type=39 PREFIX_SEI_NUT
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+h265: decode: type=49 H265_NAL_FU
+
+
+
+
+Test-call between 2 peers (A) and (B):
+
+
+(A):
+
+video Transmit: Receive:
+packets: 435 2001
+avg. bitrate: 56.0 416.0 (kbit/s)
+errors: 0 0
+pkt.report: 417 1939
+lost: 0 0
+jitter: 23.3 0.4 (ms)
+
+
+(B):
+
+video Transmit: Receive:
+packets: 2012 435
+avg. bitrate: 416.0 56.0 (kbit/s)
+errors: 0 0
+pkt.report: 1939 417
+lost: 0 0
+jitter: 0.4 23.3 (ms)
+
+
+
+[END]
diff --git a/modules/httpd/httpd.c b/modules/httpd/httpd.c
new file mode 100644
index 0000000..f194d54
--- /dev/null
+++ b/modules/httpd/httpd.c
@@ -0,0 +1,181 @@
+/**
+ * @file httpd.c Webserver UI module
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup httpd httpd
+ *
+ * HTTP Server module for the User-Interface
+ *
+ * Open your favourite web browser and point it to http://127.0.0.1:8000/
+ *
+ * Example URLs:
+ \verbatim
+ http://127.0.0.1:8000?h -- Print the Help menu
+ http://127.0.0.1:8000?d1234@target.com -- Make an outgoing call
+ \endverbatim
+ */
+
+
+static struct http_sock *httpsock;
+
+
+static int html_print_head(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+
+ return re_hprintf(pf,
+ "<html>\n"
+ "<head>\n"
+ "<title>Baresip v" BARESIP_VERSION "</title>\n"
+ "</head>\n");
+}
+
+
+static int html_print_cmd(struct re_printf *pf, const struct pl *prm)
+{
+ struct pl params;
+
+ if (!pf || !prm)
+ return EINVAL;
+
+ if (pl_isset(prm)) {
+ params.p = prm->p + 1;
+ params.l = prm->l - 1;
+ }
+ else {
+ params.p = "h";
+ params.l = 1;
+ }
+
+ return re_hprintf(pf,
+ "%H"
+ "<body>\n"
+ "<pre>\n"
+ "%H"
+ "</pre>\n"
+ "</body>\n"
+ "</html>\n",
+ html_print_head, NULL,
+ ui_input_pl, &params);
+}
+
+
+static int html_print_raw(struct re_printf *pf, const struct pl *prm)
+{
+ struct pl params;
+
+ if (!pf || !prm)
+ return EINVAL;
+
+ if (pl_isset(prm)) {
+ params.p = prm->p + 1;
+ params.l = prm->l - 1;
+ }
+ else {
+ params.p = "h";
+ params.l = 1;
+ }
+
+ return re_hprintf(pf,
+ "%H",
+ ui_input_pl, &params);
+}
+
+static void http_req_handler(struct http_conn *conn,
+ const struct http_msg *msg, void *arg)
+{
+ int err;
+ char *buf = NULL;
+ struct pl nprm;
+ (void)arg;
+
+ err = re_sdprintf(&buf, "%H", uri_header_unescape, &msg->prm);
+ if (err)
+ goto error;
+
+ pl_set_str(&nprm, buf);
+
+ if (0 == pl_strcasecmp(&msg->path, "/")) {
+
+ http_creply(conn, 200, "OK",
+ "text/html;charset=UTF-8",
+ "%H", html_print_cmd, &nprm);
+ }
+ else if (0 == pl_strcasecmp(&msg->path, "/raw/")) {
+
+ http_creply(conn, 200, "OK",
+ "text/plain;charset=UTF-8",
+ "%H", html_print_raw, &nprm);
+ }
+ else {
+ goto error;
+ }
+ mem_deref(buf);
+
+ return;
+
+ error:
+ mem_deref(buf);
+ http_ereply(conn, 404, "Not Found");
+}
+
+
+static int output_handler(const char *str)
+{
+ (void)str;
+
+ /* XXX: print 'str' to all active HTTP connections */
+
+ return 0;
+}
+
+
+static struct ui ui_http = {
+ .name = "http",
+ .outputh = output_handler
+};
+
+
+static int module_init(void)
+{
+ struct sa laddr;
+ int err;
+
+ if (conf_get_sa(conf_cur(), "http_listen", &laddr)) {
+ sa_set_str(&laddr, "0.0.0.0", 8000);
+ }
+
+ err = http_listen(&httpsock, &laddr, http_req_handler, NULL);
+ if (err)
+ return err;
+
+ ui_register(baresip_uis(), &ui_http);
+
+ info("httpd: listening on %J\n", &laddr);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ ui_unregister(&ui_http);
+
+ httpsock = mem_deref(httpsock);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(httpd) = {
+ "httpd",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/httpd/module.mk b/modules/httpd/module.mk
new file mode 100644
index 0000000..a29d2c5
--- /dev/null
+++ b/modules/httpd/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := httpd
+$(MOD)_SRCS += httpd.c
+
+include mk/mod.mk
diff --git a/modules/ice/ice.c b/modules/ice/ice.c
new file mode 100644
index 0000000..64e316c
--- /dev/null
+++ b/modules/ice/ice.c
@@ -0,0 +1,989 @@
+/**
+ * @file ice.c ICE Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef __APPLE__
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SCNetworkReachability.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup ice ice
+ *
+ * Interactive Connectivity Establishment (ICE) for media NAT traversal
+ *
+ * This module enables ICE for NAT traversal. You can enable ICE
+ * in your accounts file with the parameter ;medianat=ice. The following
+ * options can be configured:
+ *
+ \verbatim
+ ice_turn {yes,no} # Enable TURN candidates
+ ice_debug {yes,no} # Enable ICE debugging/tracing
+ ice_nomination {regular,aggressive} # Regular or aggressive nomination
+ ice_mode {full,lite} # Full ICE-mode or ICE-lite
+ \endverbatim
+ */
+
+
+enum {
+ ICE_LAYER = 0
+};
+
+
+struct mnat_sess {
+ struct list medial;
+ struct sa srv;
+ struct stun_dns *dnsq;
+ struct sdp_session *sdp;
+ char lufrag[8];
+ char lpwd[32];
+ uint64_t tiebrk;
+ bool offerer;
+ char *user;
+ char *pass;
+ int mediac;
+ bool started;
+ bool send_reinvite;
+ mnat_estab_h *estabh;
+ void *arg;
+};
+
+struct mnat_media {
+ struct comp {
+ struct mnat_media *m; /* pointer to parent */
+ struct stun_ctrans *ct_gath;
+ struct sa laddr;
+ unsigned id;
+ void *sock;
+ } compv[2];
+ struct le le;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct icem *icem;
+ bool complete;
+ bool terminated;
+ int nstun; /**< Number of pending STUN candidates */
+};
+
+
+static struct mnat *mnat;
+static struct {
+ enum ice_mode mode;
+ enum ice_nomination nom;
+ bool turn;
+ bool debug;
+} ice = {
+ ICE_MODE_FULL,
+ ICE_NOMINATION_REGULAR,
+ true,
+ false
+};
+
+
+static void gather_handler(int err, uint16_t scode, const char *reason,
+ void *arg);
+
+
+static void call_gather_handler(int err, struct mnat_media *m, uint16_t scode,
+ const char *reason)
+{
+
+ /* No more pending requests? */
+ if (m->nstun != 0)
+ return;
+
+ debug("ice: all components gathered.\n");
+
+ if (err)
+ goto out;
+
+ /* Eliminate redundant local candidates */
+ icem_cand_redund_elim(m->icem);
+
+ err = icem_comps_set_default_cand(m->icem);
+ if (err) {
+ warning("ice: set default cands failed (%m)\n", err);
+ goto out;
+ }
+
+ out:
+ gather_handler(err, scode, reason, m);
+}
+
+
+static void stun_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct comp *comp = arg;
+ struct mnat_media *m = comp->m;
+ struct stun_attr *attr;
+ struct ice_cand *lcand;
+
+ if (m->terminated)
+ return;
+
+ --m->nstun;
+
+ if (err || scode > 0) {
+ warning("ice: comp %u: STUN Request failed: %m\n",
+ comp->id, err);
+ goto out;
+ }
+
+ debug("ice: srflx gathering for comp %u complete.\n", comp->id);
+
+ /* base candidate */
+ lcand = icem_cand_find(icem_lcandl(m->icem), comp->id, NULL);
+ if (!lcand)
+ goto out;
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr)
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+ if (!attr) {
+ warning("ice: no Mapped Address in Response\n");
+ err = EPROTO;
+ goto out;
+ }
+
+ err = icem_lcand_add(m->icem, icem_lcand_base(lcand),
+ ICE_CAND_TYPE_SRFLX,
+ &attr->v.sa);
+
+ out:
+ call_gather_handler(err, m, scode, reason);
+}
+
+
+/** Gather Server Reflexive address */
+static int send_binding_request(struct mnat_media *m, struct comp *comp)
+{
+ int err;
+
+ if (comp->ct_gath)
+ return EALREADY;
+
+ debug("ice: gathering srflx for comp %u ..\n", comp->id);
+
+ err = stun_request(&comp->ct_gath, icem_stun(m->icem), IPPROTO_UDP,
+ comp->sock, &m->sess->srv, 0,
+ STUN_METHOD_BINDING,
+ NULL, false, 0,
+ stun_resp_handler, comp, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (err)
+ return err;
+
+ ++m->nstun;
+
+ return 0;
+}
+
+
+static void turnc_handler(int err, uint16_t scode, const char *reason,
+ const struct sa *relay, const struct sa *mapped,
+ const struct stun_msg *msg, void *arg)
+{
+ struct comp *comp = arg;
+ struct mnat_media *m = comp->m;
+ struct ice_cand *lcand;
+ (void)msg;
+
+ --m->nstun;
+
+ /* TURN failed, so we destroy the client */
+ if (err || scode) {
+ icem_set_turn_client(m->icem, comp->id, NULL);
+ }
+
+ if (err) {
+ warning("{%u} TURN Client error: %m\n",
+ comp->id, err);
+ goto out;
+ }
+
+ if (scode) {
+ warning("{%u} TURN Client error: %u %s\n",
+ comp->id, scode, reason);
+ err = send_binding_request(m, comp);
+ if (err)
+ goto out;
+ return;
+ }
+
+ debug("ice: relay gathered for comp %u (%u %s)\n",
+ comp->id, scode, reason);
+
+ lcand = icem_cand_find(icem_lcandl(m->icem), comp->id, NULL);
+ if (!lcand)
+ goto out;
+
+ if (!sa_cmp(relay, icem_lcand_addr(icem_lcand_base(lcand)), SA_ALL)) {
+ err = icem_lcand_add(m->icem, icem_lcand_base(lcand),
+ ICE_CAND_TYPE_RELAY, relay);
+ }
+
+ if (mapped) {
+ err |= icem_lcand_add(m->icem, icem_lcand_base(lcand),
+ ICE_CAND_TYPE_SRFLX, mapped);
+ }
+ else {
+ err |= send_binding_request(m, comp);
+ }
+
+ out:
+ call_gather_handler(err, m, scode, reason);
+}
+
+
+static int cand_gather_relayed(struct mnat_media *m, struct comp *comp,
+ const char *username, const char *password)
+{
+ struct turnc *turnc = NULL;
+ const int layer = ICE_LAYER - 10; /* below ICE stack */
+ int err;
+
+ err = turnc_alloc(&turnc, stun_conf(icem_stun(m->icem)),
+ IPPROTO_UDP, comp->sock, layer, &m->sess->srv,
+ username, password,
+ 60, turnc_handler, comp);
+ if (err)
+ return err;
+
+ err = icem_set_turn_client(m->icem, comp->id, turnc);
+ if (err)
+ goto out;
+
+ ++m->nstun;
+
+ out:
+ mem_deref(turnc);
+
+ return err;
+}
+
+
+static int start_gathering(struct mnat_media *m,
+ const char *username, const char *password)
+{
+ unsigned i;
+ int err = 0;
+
+ if (ice.mode != ICE_MODE_FULL)
+ return EINVAL;
+
+ /* for each component */
+ for (i=0; i<2; i++) {
+ struct comp *comp = &m->compv[i];
+
+ if (!comp->sock)
+ continue;
+
+ if (username && password) {
+ err |= cand_gather_relayed(m, comp,
+ username, password);
+ }
+ else
+ err |= send_binding_request(m, comp);
+ }
+
+ return err;
+}
+
+
+static int icem_gather_srflx(struct mnat_media *m)
+{
+ if (!m)
+ return EINVAL;
+
+ return start_gathering(m, NULL, NULL);
+}
+
+
+static int icem_gather_relay(struct mnat_media *m,
+ const char *username, const char *password)
+{
+ if (!m || !username || !password)
+ return EINVAL;
+
+ return start_gathering(m, username, password);
+}
+
+
+static bool is_cellular(const struct sa *laddr)
+{
+#if TARGET_OS_IPHONE
+ SCNetworkReachabilityRef r;
+ SCNetworkReachabilityFlags flags = 0;
+ bool cell = false;
+
+ r = SCNetworkReachabilityCreateWithAddressPair(NULL,
+ &laddr->u.sa, NULL);
+ if (!r)
+ return false;
+
+ if (SCNetworkReachabilityGetFlags(r, &flags)) {
+
+ if (flags & kSCNetworkReachabilityFlagsIsWWAN)
+ cell = true;
+ }
+
+ CFRelease(r);
+
+ return cell;
+#else
+ (void)laddr;
+ return false;
+#endif
+}
+
+
+static void ice_printf(struct mnat_media *m, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ debug("%s: %v", m ? sdp_media_name(m->sdpm) : "ICE", fmt, &ap);
+ va_end(ap);
+}
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+ mem_deref(sess->dnsq);
+ mem_deref(sess->user);
+ mem_deref(sess->pass);
+ mem_deref(sess->sdp);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+ unsigned i;
+
+ m->terminated = true;
+
+ list_unlink(&m->le);
+ mem_deref(m->sdpm);
+ mem_deref(m->icem);
+ for (i=0; i<2; i++) {
+ mem_deref(m->compv[i].ct_gath);
+ mem_deref(m->compv[i].sock);
+ }
+}
+
+
+static bool candidate_handler(struct le *le, void *arg)
+{
+ return 0 != sdp_media_set_lattr(arg, false, ice_attr_cand, "%H",
+ ice_cand_encode, le->data);
+}
+
+
+/**
+ * Update the local SDP attributes, this can be called multiple times
+ * when the state of the ICE machinery changes
+ */
+static int set_media_attributes(struct mnat_media *m)
+{
+ int err = 0;
+
+ if (icem_mismatch(m->icem)) {
+ err = sdp_media_set_lattr(m->sdpm, true,
+ ice_attr_mismatch, NULL);
+ return err;
+ }
+ else {
+ sdp_media_del_lattr(m->sdpm, ice_attr_mismatch);
+ }
+
+ /* Encode all my candidates */
+ sdp_media_del_lattr(m->sdpm, ice_attr_cand);
+ if (list_apply(icem_lcandl(m->icem), true, candidate_handler, m->sdpm))
+ return ENOMEM;
+
+ if (ice_remotecands_avail(m->icem)) {
+ err |= sdp_media_set_lattr(m->sdpm, true,
+ ice_attr_remote_cand, "%H",
+ ice_remotecands_encode, m->icem);
+ }
+
+ return err;
+}
+
+
+static bool if_handler(const char *ifname, const struct sa *sa, void *arg)
+{
+ struct mnat_media *m = arg;
+ uint16_t lprio;
+ unsigned i;
+ int err = 0;
+
+ /* Skip loopback and link-local addresses */
+ if (sa_is_loopback(sa) || sa_is_linklocal(sa))
+ return false;
+
+ lprio = is_cellular(sa) ? 0 : 10;
+
+ ice_printf(m, "added interface: %s:%j (local prio %u)\n",
+ ifname, sa, lprio);
+
+ for (i=0; i<2; i++) {
+ if (m->compv[i].sock)
+ err |= icem_cand_add(m->icem, i+1, lprio, ifname, sa);
+ }
+
+ if (err) {
+ warning("ice: %s:%j: icem_cand_add: %m\n", ifname, sa, err);
+ }
+
+ return false;
+}
+
+
+static int media_start(struct mnat_sess *sess, struct mnat_media *m)
+{
+ int err = 0;
+
+ net_if_apply(if_handler, m);
+
+ switch (ice.mode) {
+
+ default:
+ case ICE_MODE_FULL:
+ if (ice.turn) {
+ err = icem_gather_relay(m,
+ sess->user, sess->pass);
+ }
+ else {
+ err = icem_gather_srflx(m);
+ }
+ break;
+
+ case ICE_MODE_LITE:
+ err = icem_lite_set_default_candidates(m->icem);
+ if (err) {
+ warning("ice: could not set"
+ " default candidates (%m)\n", err);
+ return err;
+ }
+
+ gather_handler(0, 0, NULL, m);
+ break;
+ }
+
+ return err;
+}
+
+
+static void dns_handler(int err, const struct sa *srv, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ if (err)
+ goto out;
+
+ debug("ice: resolved %s-server to address %J\n",
+ ice.turn ? "TURN" : "STUN", srv);
+
+ sess->srv = *srv;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+
+ err = media_start(sess, m);
+ if (err)
+ goto out;
+ }
+
+ return;
+
+ out:
+ sess->estabh(err, 0, NULL, sess->arg);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ const char *usage;
+ int err;
+
+ if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh)
+ return EINVAL;
+
+ info("ice: new session with %s-server at %s (username=%s)\n",
+ ice.turn ? "TURN" : "STUN",
+ srv, user);
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->sdp = mem_ref(ss);
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ err = str_dup(&sess->user, user);
+ err |= str_dup(&sess->pass, pass);
+ if (err)
+ goto out;
+
+ rand_str(sess->lufrag, sizeof(sess->lufrag));
+ rand_str(sess->lpwd, sizeof(sess->lpwd));
+ sess->tiebrk = rand_u64();
+ sess->offerer = offerer;
+
+ if (ICE_MODE_LITE == ice.mode) {
+ err |= sdp_session_set_lattr(ss, true,
+ ice_attr_lite, NULL);
+ }
+
+ err |= sdp_session_set_lattr(ss, true,
+ ice_attr_ufrag, sess->lufrag);
+ err |= sdp_session_set_lattr(ss, true,
+ ice_attr_pwd, sess->lpwd);
+ if (err)
+ goto out;
+
+ usage = ice.turn ? stun_usage_relay : stun_usage_binding;
+
+ err = stun_server_discover(&sess->dnsq, dnsc, usage, stun_proto_udp,
+ af, srv, port, dns_handler, sess);
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static bool verify_peer_ice(struct mnat_sess *ms)
+{
+ struct le *le;
+
+ for (le = ms->medial.head; le; le = le->next) {
+ struct mnat_media *m = le->data;
+ struct sa raddr[2];
+ unsigned i;
+
+ if (!sdp_media_has_media(m->sdpm)) {
+ info("ice: stream '%s' is disabled -- ignore\n",
+ sdp_media_name(m->sdpm));
+ continue;
+ }
+
+ raddr[0] = *sdp_media_raddr(m->sdpm);
+ sdp_media_raddr_rtcp(m->sdpm, &raddr[1]);
+
+ for (i=0; i<2; i++) {
+ if (m->compv[i].sock &&
+ !icem_verify_support(m->icem, i+1, &raddr[i])) {
+ warning("ice: %s.%u: no remote candidates"
+ " found (address = %J)\n",
+ sdp_media_name(m->sdpm),
+ i+1, &raddr[i]);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+static bool refresh_comp_laddr(struct mnat_media *m, unsigned id,
+ struct comp *comp, const struct sa *laddr)
+{
+ bool changed = false;
+
+ if (!m || !comp || !comp->sock || !laddr)
+ return false;
+
+ if (!sa_cmp(&comp->laddr, laddr, SA_ALL)) {
+ changed = true;
+
+ ice_printf(m, "comp%u setting local: %J\n", id, laddr);
+ }
+
+ sa_cpy(&comp->laddr, laddr);
+
+ if (id == 1)
+ sdp_media_set_laddr(m->sdpm, &comp->laddr);
+ else if (id == 2)
+ sdp_media_set_laddr_rtcp(m->sdpm, &comp->laddr);
+
+ return changed;
+}
+
+
+/*
+ * Update SDP Media with local addresses
+ */
+static bool refresh_laddr(struct mnat_media *m,
+ const struct sa *laddr1,
+ const struct sa *laddr2)
+{
+ bool changed = false;
+
+ changed |= refresh_comp_laddr(m, 1, &m->compv[0], laddr1);
+ changed |= refresh_comp_laddr(m, 2, &m->compv[1], laddr2);
+
+ return changed;
+}
+
+
+static void gather_handler(int err, uint16_t scode, const char *reason,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ mnat_estab_h *estabh = m->sess->estabh;
+
+ if (err || scode) {
+ warning("ice: gather error: %m (%u %s)\n",
+ err, scode, reason);
+ }
+ else {
+ refresh_laddr(m,
+ icem_cand_default(m->icem, 1),
+ icem_cand_default(m->icem, 2));
+
+ info("ice: %s: Default local candidates: %J / %J\n",
+ sdp_media_name(m->sdpm),
+ &m->compv[0].laddr, &m->compv[1].laddr);
+
+ (void)set_media_attributes(m);
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ if (err || scode)
+ m->sess->estabh = NULL;
+
+ if (estabh)
+ estabh(err, scode, reason, m->sess->arg);
+}
+
+
+static void conncheck_handler(int err, bool update, void *arg)
+{
+ struct mnat_media *m = arg;
+ struct mnat_sess *sess = m->sess;
+ struct le *le;
+
+ info("ice: %s: connectivity check is complete (update=%d)\n",
+ sdp_media_name(m->sdpm), update);
+
+ ice_printf(m, "Dumping media state: %H\n", icem_debug, m->icem);
+
+ if (err) {
+ warning("ice: connectivity check failed: %m\n", err);
+ }
+ else {
+ bool changed;
+
+ m->complete = true;
+
+ changed = refresh_laddr(m,
+ icem_selected_laddr(m->icem, 1),
+ icem_selected_laddr(m->icem, 2));
+ if (changed)
+ sess->send_reinvite = true;
+
+ (void)set_media_attributes(m);
+
+ /* Check all conncheck flags */
+ LIST_FOREACH(&sess->medial, le) {
+ struct mnat_media *mx = le->data;
+ if (!mx->complete)
+ return;
+ }
+ }
+
+ /* call estab-handler and send re-invite */
+ if (sess->send_reinvite && update) {
+
+ info("ice: %s: sending Re-INVITE with updated"
+ " default candidates\n",
+ sdp_media_name(m->sdpm));
+
+ sess->estabh(0, 0, NULL, sess->arg);
+ sess->send_reinvite = false;
+ }
+}
+
+
+static int ice_start(struct mnat_sess *sess)
+{
+ struct le *le;
+ int err = 0;
+
+ /* Update SDP media */
+ if (sess->started) {
+
+ LIST_FOREACH(&sess->medial, le) {
+ struct mnat_media *m = le->data;
+
+ ice_printf(NULL, "ICE Start: %H",
+ icem_debug, m->icem);
+
+ icem_update(m->icem);
+
+ refresh_laddr(m,
+ icem_selected_laddr(m->icem, 1),
+ icem_selected_laddr(m->icem, 2));
+
+ err |= set_media_attributes(m);
+ }
+
+ return err;
+ }
+
+ /* Clear all conncheck flags */
+ LIST_FOREACH(&sess->medial, le) {
+ struct mnat_media *m = le->data;
+
+ if (sdp_media_has_media(m->sdpm)) {
+ m->complete = false;
+
+ if (ice.mode == ICE_MODE_FULL) {
+ err = icem_conncheck_start(m->icem);
+ if (err)
+ return err;
+
+ /* set the pair states
+ -- first media stream only */
+ if (sess->medial.head == le) {
+ ice_candpair_set_states(m->icem);
+ }
+ }
+ }
+ else {
+ m->complete = true;
+ }
+ }
+
+ sess->started = true;
+
+ return 0;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ enum ice_role role;
+ unsigned i;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sdpm = mem_ref(sdpm);
+ m->sess = sess;
+ m->compv[0].sock = mem_ref(sock1);
+ m->compv[1].sock = mem_ref(sock2);
+
+ if (sess->offerer)
+ role = ICE_ROLE_CONTROLLING;
+ else
+ role = ICE_ROLE_CONTROLLED;
+
+ err = icem_alloc(&m->icem, ice.mode, role,
+ proto, ICE_LAYER,
+ sess->tiebrk, sess->lufrag, sess->lpwd,
+ conncheck_handler, m);
+ if (err)
+ goto out;
+
+ icem_conf(m->icem)->nom = ice.nom;
+ icem_conf(m->icem)->debug = ice.debug;
+ icem_conf(m->icem)->rc = 4;
+
+ icem_set_conf(m->icem, icem_conf(m->icem));
+
+ icem_set_name(m->icem, sdp_media_name(sdpm));
+
+ for (i=0; i<2; i++) {
+ m->compv[i].m = m;
+ m->compv[i].id = i+1;
+ if (m->compv[i].sock)
+ err |= icem_comp_add(m->icem, i+1, m->compv[i].sock);
+ }
+
+ if (sa_isset(&sess->srv, SA_ALL))
+ err |= media_start(sess, m);
+
+ out:
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ ++sess->mediac;
+ }
+
+ return err;
+}
+
+
+static bool sdp_attr_handler(const char *name, const char *value, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+ struct mnat_media *m = le->data;
+
+ (void)ice_sdp_decode(m->icem, name, value);
+ }
+
+ return false;
+}
+
+
+static bool media_attr_handler(const char *name, const char *value, void *arg)
+{
+ struct mnat_media *m = arg;
+ return 0 != icem_sdp_decode(m->icem, name, value);
+}
+
+
+static int enable_turn_channels(struct mnat_sess *sess)
+{
+ struct le *le;
+ int err = 0;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+ struct sa raddr[2];
+ unsigned i;
+
+ err |= set_media_attributes(m);
+
+ raddr[0] = *sdp_media_raddr(m->sdpm);
+ sdp_media_raddr_rtcp(m->sdpm, &raddr[1]);
+
+ for (i=0; i<2; i++) {
+ if (m->compv[i].sock && sa_isset(&raddr[i], SA_ALL))
+ err |= icem_add_chan(m->icem, i+1, &raddr[i]);
+ }
+ }
+
+ return err;
+}
+
+
+/** This can be called several times */
+static int update(struct mnat_sess *sess)
+{
+ struct le *le;
+ int err = 0;
+
+ /* SDP session */
+ (void)sdp_session_rattr_apply(sess->sdp, NULL, sdp_attr_handler, sess);
+
+ /* SDP medialines */
+ for (le = sess->medial.head; le; le = le->next) {
+ struct mnat_media *m = le->data;
+
+ sdp_media_rattr_apply(m->sdpm, NULL, media_attr_handler, m);
+ }
+
+ /* 5.1. Verifying ICE Support */
+ if (verify_peer_ice(sess)) {
+ err = ice_start(sess);
+ }
+ else if (ice.turn) {
+ info("ice: ICE not supported by peer, fallback to TURN\n");
+ err = enable_turn_channels(sess);
+ }
+ else {
+ info("ice: ICE not supported by peer\n");
+
+ LIST_FOREACH(&sess->medial, le) {
+ struct mnat_media *m = le->data;
+
+ err |= set_media_attributes(m);
+ }
+ }
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+#ifdef MODULE_CONF
+ struct pl pl;
+
+ conf_get_bool(conf_cur(), "ice_turn", &ice.turn);
+ conf_get_bool(conf_cur(), "ice_debug", &ice.debug);
+
+ if (!conf_get(conf_cur(), "ice_nomination", &pl)) {
+ if (0 == pl_strcasecmp(&pl, "regular"))
+ ice.nom = ICE_NOMINATION_REGULAR;
+ else if (0 == pl_strcasecmp(&pl, "aggressive"))
+ ice.nom = ICE_NOMINATION_AGGRESSIVE;
+ else {
+ warning("ice: unknown nomination: %r\n", &pl);
+ return EINVAL;
+ }
+ }
+ if (!conf_get(conf_cur(), "ice_mode", &pl)) {
+ if (!pl_strcasecmp(&pl, "full"))
+ ice.mode = ICE_MODE_FULL;
+ else if (!pl_strcasecmp(&pl, "lite"))
+ ice.mode = ICE_MODE_LITE;
+ else {
+ warning("ice: unknown mode: %r\n", &pl);
+ return EINVAL;
+ }
+ }
+#endif
+
+ return mnat_register(&mnat, baresip_mnatl(),
+ "ice", "+sip.ice",
+ session_alloc, media_alloc, update);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(ice) = {
+ "ice",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/ice/module.mk b/modules/ice/module.mk
new file mode 100644
index 0000000..9a8254f
--- /dev/null
+++ b/modules/ice/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := ice
+$(MOD)_SRCS += ice.c
+
+include mk/mod.mk
diff --git a/modules/ilbc/ilbc.c b/modules/ilbc/ilbc.c
new file mode 100644
index 0000000..3628e11
--- /dev/null
+++ b/modules/ilbc/ilbc.c
@@ -0,0 +1,357 @@
+/**
+ * @file ilbc.c Internet Low Bit Rate Codec (iLBC) audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <iLBC_define.h>
+#include <iLBC_decode.h>
+#include <iLBC_encode.h>
+
+
+/**
+ * @defgroup ilbc ilbc
+ *
+ * iLBC audio codec
+ *
+ * This module implements the iLBC audio codec as defined in:
+ *
+ * RFC 3951 Internet Low Bit Rate Codec (iLBC)
+ * RFC 3952 RTP Payload Format for iLBC Speech
+ *
+ * The iLBC source code is not included here, but can be downloaded from
+ * http://ilbcfreeware.org/
+ *
+ * You can also use the source distributed by the Freeswitch project,
+ * see www.freeswitch.org, and then freeswitch/libs/codec/ilbc.
+ * Or you can look in the asterisk source code ...
+ *
+ * mode=20 15.20 kbit/s 160samp 38bytes
+ * mode=30 13.33 kbit/s 240samp 50bytes
+ */
+
+enum {
+ DEFAULT_MODE = 20, /* 20ms or 30ms */
+ USE_ENHANCER = 1
+};
+
+struct auenc_state {
+ iLBC_Enc_Inst_t enc;
+ int mode;
+ uint32_t enc_bytes;
+};
+
+struct audec_state {
+ iLBC_Dec_Inst_t dec;
+ int mode;
+ uint32_t nsamp;
+ size_t dec_bytes;
+};
+
+
+static char ilbc_fmtp[32];
+
+
+static void set_encoder_mode(struct auenc_state *st, int mode)
+{
+ if (st->mode == mode)
+ return;
+
+ info("ilbc: set iLBC encoder mode %dms\n", mode);
+
+ st->mode = mode;
+
+ switch (mode) {
+
+ case 20:
+ st->enc_bytes = NO_OF_BYTES_20MS;
+ break;
+
+ case 30:
+ st->enc_bytes = NO_OF_BYTES_30MS;
+ break;
+
+ default:
+ warning("ilbc: unknown encoder mode %d\n", mode);
+ return;
+ }
+
+ st->enc_bytes = initEncode(&st->enc, mode);
+}
+
+
+static void set_decoder_mode(struct audec_state *st, int mode)
+{
+ if (st->mode == mode)
+ return;
+
+ info("ilbc: set iLBC decoder mode %dms\n", mode);
+
+ st->mode = mode;
+
+ switch (mode) {
+
+ case 20:
+ st->nsamp = BLOCKL_20MS;
+ break;
+
+ case 30:
+ st->nsamp = BLOCKL_30MS;
+ break;
+
+ default:
+ warning("ilbc: unknown decoder mode %d\n", mode);
+ return;
+ }
+
+ st->nsamp = initDecode(&st->dec, mode, USE_ENHANCER);
+}
+
+
+static void encoder_fmtp_decode(struct auenc_state *st, const char *fmtp)
+{
+ struct pl mode;
+
+ if (!fmtp)
+ return;
+
+ if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode))
+ return;
+
+ set_encoder_mode(st, pl_u32(&mode));
+}
+
+
+static void decoder_fmtp_decode(struct audec_state *st, const char *fmtp)
+{
+ struct pl mode;
+
+ if (!fmtp)
+ return;
+
+ if (re_regex(fmtp, strlen(fmtp), "mode=[0-9]+", &mode))
+ return;
+
+ set_decoder_mode(st, pl_u32(&mode));
+}
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+ (void)st;
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+ (void)st;
+}
+
+
+static int check_ptime(const struct auenc_param *prm)
+{
+ if (!prm)
+ return 0;
+
+ switch (prm->ptime) {
+
+ case 20:
+ case 30:
+ return 0;
+
+ default:
+ warning("ilbc: invalid ptime %u ms\n", prm->ptime);
+ return EINVAL;
+ }
+}
+
+
+static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+
+ if (!aesp || !ac || !prm)
+ return EINVAL;
+ if (check_ptime(prm))
+ return EINVAL;
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ set_encoder_mode(st, DEFAULT_MODE);
+
+ if (str_isset(fmtp))
+ encoder_fmtp_decode(st, fmtp);
+
+ /* update parameters after SDP was decoded */
+ if (prm) {
+ prm->ptime = st->mode;
+ }
+
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ set_decoder_mode(st, DEFAULT_MODE);
+
+ if (str_isset(fmtp))
+ decoder_fmtp_decode(st, fmtp);
+
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ float float_buf[sampc];
+ uint32_t i;
+
+ /* Make sure there is enough space */
+ if (*len < st->enc_bytes) {
+ warning("ilbc: encode: buffer is too small (%u bytes)\n",
+ *len);
+ return ENOMEM;
+ }
+
+ /* Convert from 16-bit samples to float */
+ for (i=0; i<sampc; i++) {
+ const int16_t v = sampv[i];
+ float_buf[i] = (float)v;
+ }
+
+ iLBC_encode(buf, /* (o) encoded data bits iLBC */
+ float_buf, /* (o) speech vector to encode */
+ &st->enc); /* (i/o) the general encoder state */
+
+ *len = st->enc_bytes;
+
+ return 0;
+}
+
+
+static int do_dec(struct audec_state *st, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ float float_buf[st->nsamp];
+ const int mode = len ? 1 : 0;
+ uint32_t i;
+
+ /* Make sure there is enough space in the buffer */
+ if (*sampc < st->nsamp)
+ return ENOMEM;
+
+ iLBC_decode(float_buf, /* (o) decoded signal block */
+ (uint8_t *)buf, /* (i) encoded signal bits */
+ &st->dec, /* (i/o) the decoder state structure */
+ mode); /* (i) 0: bad packet, PLC, 1: normal */
+
+ /* Convert from float to 16-bit samples */
+ for (i=0; i<st->nsamp; i++) {
+ sampv[i] = (int16_t)float_buf[i];
+ }
+
+ *sampc = st->nsamp;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ /* Try to detect mode */
+ if (st->dec_bytes != len) {
+
+ st->dec_bytes = len;
+
+ switch (st->dec_bytes) {
+
+ case NO_OF_BYTES_20MS:
+ set_decoder_mode(st, 20);
+ break;
+
+ case NO_OF_BYTES_30MS:
+ set_decoder_mode(st, 30);
+ break;
+
+ default:
+ warning("ilbc: decode: expect %u, got %u\n",
+ st->dec_bytes, len);
+ return EINVAL;
+ }
+ }
+
+ return do_dec(st, sampv, sampc, buf, len);
+}
+
+
+static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ return do_dec(st, sampv, sampc, NULL, 0);
+}
+
+
+static struct aucodec ilbc = {
+ LE_INIT, 0, "iLBC", 8000, 8000, 1, ilbc_fmtp,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0
+};
+
+
+static int module_init(void)
+{
+ (void)re_snprintf(ilbc_fmtp, sizeof(ilbc_fmtp),
+ "mode=%d", DEFAULT_MODE);
+
+ aucodec_register(baresip_aucodecl(), &ilbc);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&ilbc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(ilbc) = {
+ "ilbc",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/ilbc/module.mk b/modules/ilbc/module.mk
new file mode 100644
index 0000000..f549a67
--- /dev/null
+++ b/modules/ilbc/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := ilbc
+$(MOD)_SRCS += ilbc.c
+$(MOD)_LFLAGS += -lilbc -lm
+
+include mk/mod.mk
diff --git a/modules/isac/isac.c b/modules/isac/isac.c
new file mode 100644
index 0000000..eb4bbdd
--- /dev/null
+++ b/modules/isac/isac.c
@@ -0,0 +1,226 @@
+/**
+ * @file isac.c iSAC audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "isac.h"
+
+
+/**
+ * @defgroup isac isac
+ *
+ * iSAC audio codec
+ *
+ * draft-ietf-avt-rtp-isac-04
+ */
+
+
+struct auenc_state {
+ ISACStruct *inst;
+};
+
+struct audec_state {
+ ISACStruct *inst;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ if (st->inst)
+ WebRtcIsac_Free(st->inst);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ if (st->inst)
+ WebRtcIsac_Free(st->inst);
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int err = 0;
+ (void)prm;
+ (void)fmtp;
+
+ if (!aesp || !ac)
+ return EINVAL;
+
+ if (*aesp)
+ return 0;
+
+ st = mem_alloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (WebRtcIsac_Create(&st->inst) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ WebRtcIsac_EncoderInit(st->inst, 0);
+
+ if (ac->srate == 32000)
+ WebRtcIsac_SetEncSampRate(st->inst, kIsacSuperWideband);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+
+ if (*adsp)
+ return 0;
+
+ st = mem_alloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (WebRtcIsac_Create(&st->inst) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ WebRtcIsac_DecoderInit(st->inst);
+
+ if (ac->srate == 32000)
+ WebRtcIsac_SetDecSampRate(st->inst, kIsacSuperWideband);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ WebRtc_Word16 len1, len2;
+ size_t l;
+
+ if (!st || !buf || !len || !sampv || !sampc)
+ return EINVAL;
+
+ /* 10 ms audio blocks */
+ len1 = WebRtcIsac_Encode(st->inst, sampv, (void *)buf);
+ len2 = WebRtcIsac_Encode(st->inst, &sampv[sampc/2], (void *)buf);
+
+ l = len1 ? len1 : len2;
+
+ if (l > *len)
+ return ENOMEM;
+
+ *len = l;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ WebRtc_Word16 speechType;
+ int n;
+
+ if (!st || !sampv || !sampc || !buf || !len)
+ return EINVAL;
+
+ n = WebRtcIsac_Decode(st->inst, (void *)buf, len,
+ (void *)sampv, &speechType);
+ if (n < 0)
+ return EPROTO;
+
+ if ((size_t)n > *sampc)
+ return ENOMEM;
+
+ *sampc = n;
+
+ return 0;
+}
+
+
+static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ int n;
+
+ if (!st || !sampv || !sampc)
+ return EINVAL;
+
+ n = WebRtcIsac_DecodePlc(st->inst, (void *)sampv, 1);
+ if (n < 0)
+ return EPROTO;
+
+ *sampc = n;
+
+ return 0;
+}
+
+
+static struct aucodec isacv[] = {
+ {
+ LE_INIT, 0, "isac", 32000, 32000, 1, NULL,
+ encode_update, encode, decode_update, decode, plc, NULL, NULL
+ },
+ {
+ LE_INIT, 0, "isac", 16000, 16000, 1, NULL,
+ encode_update, encode, decode_update, decode, plc, NULL, NULL
+ }
+};
+
+
+static int module_init(void)
+{
+ unsigned i;
+
+ for (i=0; i<ARRAY_SIZE(isacv); i++)
+ aucodec_register(baresip_aucodecl(), &isacv[i]);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ int i = ARRAY_SIZE(isacv);
+
+ while (i--)
+ aucodec_unregister(&isacv[i]);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(isac) = {
+ "isac",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/isac/module.mk b/modules/isac/module.mk
new file mode 100644
index 0000000..64cafde
--- /dev/null
+++ b/modules/isac/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := isac
+$(MOD)_SRCS += isac.c
+$(MOD)_LFLAGS += -lisac
+
+include mk/mod.mk
diff --git a/modules/jack/jack.c b/modules/jack/jack.c
new file mode 100644
index 0000000..5bf9a10
--- /dev/null
+++ b/modules/jack/jack.c
@@ -0,0 +1,43 @@
+/**
+ * @file jack.c JACK audio driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "mod_jack.h"
+
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+static int module_init(void)
+{
+ int err = 0;
+
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "jack", jack_play_alloc);
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "jack", jack_src_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ auplay = mem_deref(auplay);
+ ausrc = mem_deref(ausrc);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(jack) = {
+ "jack",
+ "sound",
+ module_init,
+ module_close
+};
diff --git a/modules/jack/jack_play.c b/modules/jack/jack_play.c
new file mode 100644
index 0000000..f890ddd
--- /dev/null
+++ b/modules/jack/jack_play.c
@@ -0,0 +1,239 @@
+/**
+ * @file jack.c JACK audio driver -- player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <jack/jack.h>
+#include "mod_jack.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* pointer to base-class (inheritance) */
+
+ struct auplay_prm prm;
+ int16_t *sampv;
+ size_t sampc; /* includes number of channels */
+ auplay_write_h *wh;
+ void *arg;
+
+ jack_client_t *client;
+ jack_port_t *portv[2];
+ jack_nframes_t nframes; /* num frames per port (channel) */
+};
+
+
+static inline float ausamp_short2float(int16_t in)
+{
+ float out;
+
+ out = (float) (in / (1.0 * 0x8000));
+
+ return out;
+}
+
+
+/**
+ * The process callback for this JACK application is called in a
+ * special realtime thread once for each audio cycle.
+ *
+ * This client does nothing more than copy data from its input
+ * port to its output port. It will exit when stopped by
+ * the user (e.g. using Ctrl-C on a unix-ish operating system)
+ *
+ * XXX avoid memory allocations in this function
+ */
+static int process_handler(jack_nframes_t nframes, void *arg)
+{
+ struct auplay_st *st = arg;
+ size_t sampc = nframes * st->prm.ch;
+ size_t ch, j;
+
+ /* 1. read data from app (signed 16-bit) interleaved */
+ st->wh(st->sampv, sampc, st->arg);
+
+ /* 2. convert from 16-bit to float and copy to Jack */
+
+ /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */
+ for (ch = 0; ch < st->prm.ch; ch++) {
+
+ jack_default_audio_sample_t *buffer;
+
+ buffer = jack_port_get_buffer(st->portv[ch], st->nframes);
+
+ for (j = 0; j < nframes; j++) {
+ int16_t samp = st->sampv[j*st->prm.ch + ch];
+ buffer[j] = ausamp_short2float(samp);
+ }
+ }
+
+ return 0;
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ info("jack: destroy\n");
+
+ if (st->client)
+ jack_client_close(st->client);
+
+ mem_deref(st->sampv);
+}
+
+
+static int start_jack(struct auplay_st *st)
+{
+ const char **ports;
+ const char *client_name = "baresip";
+ const char *server_name = NULL;
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ unsigned ch;
+ jack_nframes_t engine_srate;
+
+ /* open a client connection to the JACK server */
+
+ st->client = jack_client_open(client_name, options,
+ &status, server_name);
+ if (st->client == NULL) {
+ warning("jack: jack_client_open() failed, "
+ "status = 0x%2.0x\n", status);
+
+ if (status & JackServerFailed) {
+ warning("jack: Unable to connect to JACK server\n");
+ }
+ return ENODEV;
+ }
+ if (status & JackServerStarted) {
+ info("jack: JACK server started\n");
+ }
+ if (status & JackNameNotUnique) {
+ client_name = jack_get_client_name(st->client);
+ info("jack: unique name `%s' assigned\n", client_name);
+ }
+
+ jack_set_process_callback(st->client, process_handler, st);
+
+ engine_srate = jack_get_sample_rate(st->client);
+ st->nframes = jack_get_buffer_size(st->client);
+
+ info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n",
+ engine_srate, st->nframes);
+
+ /* currently the application must use the same sample-rate
+ as the jack server backend */
+ if (engine_srate != st->prm.srate) {
+ warning("jack: samplerate %uHz expected\n", engine_srate);
+ return EINVAL;
+ }
+
+ /* create one port per channel */
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ char buf[32];
+ re_snprintf(buf, sizeof(buf), "output_%u", ch+1);
+
+ st->portv[ch] = jack_port_register (st->client, buf,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if ( st->portv[ch] == NULL) {
+ warning("jack: no more JACK ports available\n");
+ return ENODEV;
+ }
+ }
+
+ /* Tell the JACK server that we are ready to roll. Our
+ * process() callback will start running now. */
+
+ if (jack_activate (st->client)) {
+ warning("jack: cannot activate client");
+ return ENODEV;
+ }
+
+ /* Connect the ports. You can't do this before the client is
+ * activated, because we can't make connections to clients
+ * that aren't running. Note the confusing (but necessary)
+ * orientation of the driver backend ports: playback ports are
+ * "input" to the backend, and capture ports are "output" from
+ * it.
+ */
+
+ ports = jack_get_ports (st->client, NULL, NULL,
+ JackPortIsInput);
+ if (ports == NULL) {
+ warning("jack: no physical playback ports\n");
+ return ENODEV;
+ }
+
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ if (jack_connect (st->client, jack_port_name (st->portv[ch]),
+ ports[ch])) {
+ warning("jack: cannot connect output ports\n");
+ }
+ }
+
+ jack_free(ports);
+
+ return 0;
+}
+
+
+int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err = 0;
+
+ (void)device;
+
+ if (!stp || !ap || !prm || !wh)
+ return EINVAL;
+
+ info("jack: play %uHz,%uch\n", prm->srate, prm->ch);
+
+ if (prm->ch > ARRAY_SIZE(st->portv))
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("jack: playback: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->prm = *prm;
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = start_jack(st);
+ if (err)
+ goto out;
+
+ st->sampc = st->nframes * prm->ch;
+ st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ info("jack: sampc=%zu\n", st->sampc);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/jack/jack_src.c b/modules/jack/jack_src.c
new file mode 100644
index 0000000..0ef1e42
--- /dev/null
+++ b/modules/jack/jack_src.c
@@ -0,0 +1,234 @@
+/**
+ * @file jack_src.c JACK audio driver -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <math.h>
+#include <jack/jack.h>
+#include "mod_jack.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* pointer to base-class (inheritance) */
+
+ struct ausrc_prm prm;
+ int16_t *sampv;
+ size_t sampc; /* includes number of channels */
+ ausrc_read_h *rh;
+ void *arg;
+
+ jack_client_t *client;
+ jack_port_t *portv[2];
+ jack_nframes_t nframes; /* num frames per port (channel) */
+};
+
+
+static inline int16_t ausamp_float2short(float in)
+{
+ double scaled_value;
+ int16_t out;
+
+ scaled_value = in * (8.0 * 0x10000000);
+
+ if (scaled_value >= (1.0 * 0x7fffffff)) {
+ out = 32767;
+ }
+ else if (scaled_value <= (-8.0 * 0x10000000)) {
+ out = -32768;
+ }
+ else
+ out = (short) (lrint (scaled_value) >> 16);
+
+ return out;
+}
+
+
+static int process_handler(jack_nframes_t nframes, void *arg)
+{
+ struct ausrc_st *st = arg;
+ size_t sampc = nframes * st->prm.ch;
+ size_t ch, j;
+
+ /* 2. convert from 16-bit to float and copy to Jack */
+
+ /* 3. de-interleave [LRLRLRLR] -> [LLLLL]+[RRRRR] */
+ for (ch = 0; ch < st->prm.ch; ch++) {
+
+ const jack_default_audio_sample_t *buffer;
+
+ buffer = jack_port_get_buffer(st->portv[ch], st->nframes);
+
+ for (j = 0; j < nframes; j++) {
+ int16_t samp;
+ samp = ausamp_float2short(buffer[j]);
+ st->sampv[j*st->prm.ch + ch] = samp;
+ }
+ }
+
+ /* 1. read data from app (signed 16-bit) interleaved */
+ st->rh(st->sampv, sampc, st->arg);
+
+ return 0;
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ info("jack: source destroy\n");
+
+ if (st->client)
+ jack_client_close(st->client);
+
+ mem_deref(st->sampv);
+}
+
+
+static int start_jack(struct ausrc_st *st)
+{
+ const char **ports;
+ const char *client_name = "baresip";
+ const char *server_name = NULL;
+ jack_options_t options = JackNullOption;
+ jack_status_t status;
+ unsigned ch;
+ jack_nframes_t engine_srate;
+
+ /* open a client connection to the JACK server */
+
+ st->client = jack_client_open(client_name, options,
+ &status, server_name);
+ if (st->client == NULL) {
+ warning("jack: jack_client_open() failed, "
+ "status = 0x%2.0x\n", status);
+
+ if (status & JackServerFailed) {
+ warning("jack: Unable to connect to JACK server\n");
+ }
+ return ENODEV;
+ }
+ if (status & JackServerStarted) {
+ info("jack: JACK server started\n");
+ }
+ if (status & JackNameNotUnique) {
+ client_name = jack_get_client_name(st->client);
+ info("jack: unique name `%s' assigned\n", client_name);
+ }
+
+ jack_set_process_callback(st->client, process_handler, st);
+
+ engine_srate = jack_get_sample_rate(st->client);
+ st->nframes = jack_get_buffer_size(st->client);
+
+ info("jack: engine sample rate: %" PRIu32 " max_frames=%u\n",
+ engine_srate, st->nframes);
+
+ /* currently the application must use the same sample-rate
+ as the jack server backend */
+ if (engine_srate != st->prm.srate) {
+ warning("jack: samplerate %uHz expected\n", engine_srate);
+ return EINVAL;
+ }
+
+ /* create one port per channel */
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ char buf[32];
+ re_snprintf(buf, sizeof(buf), "input_%u", ch+1);
+
+ st->portv[ch] = jack_port_register(st->client, buf,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput, 0);
+ if ( st->portv[ch] == NULL) {
+ warning("jack: no more JACK ports available\n");
+ return ENODEV;
+ }
+ }
+
+ /* Tell the JACK server that we are ready to roll. Our
+ * process() callback will start running now. */
+
+ if (jack_activate (st->client)) {
+ warning("jack: cannot activate client");
+ return ENODEV;
+ }
+
+ ports = jack_get_ports (st->client, NULL, NULL,
+ JackPortIsOutput);
+ if (ports == NULL) {
+ warning("jack: no physical playback ports\n");
+ return ENODEV;
+ }
+
+ for (ch=0; ch<st->prm.ch; ch++) {
+
+ if (jack_connect(st->client, ports[ch],
+ jack_port_name(st->portv[ch]))) {
+ warning("jack: cannot connect output ports\n");
+ }
+ }
+
+ jack_free(ports);
+
+ return 0;
+}
+
+
+int jack_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err = 0;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ if (prm->ch > ARRAY_SIZE(st->portv))
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("jack: source: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->prm = *prm;
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ err = start_jack(st);
+ if (err)
+ goto out;
+
+ st->sampc = st->nframes * prm->ch;
+ st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ info("jack: source sampc=%zu\n", st->sampc);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/jack/mod_jack.h b/modules/jack/mod_jack.h
new file mode 100644
index 0000000..b154ebf
--- /dev/null
+++ b/modules/jack/mod_jack.h
@@ -0,0 +1,14 @@
+/**
+ * @file mod_jack.h JACK audio driver -- internal api
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+int jack_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int jack_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/jack/module.mk b/modules/jack/module.mk
new file mode 100644
index 0000000..486ffd6
--- /dev/null
+++ b/modules/jack/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := jack
+$(MOD)_SRCS += jack.c jack_play.c jack_src.c
+$(MOD)_CFLAGS += $(shell pkg-config --cflags jack)
+$(MOD)_LFLAGS += $(shell pkg-config --libs jack)
+
+include mk/mod.mk
diff --git a/modules/l16/l16.c b/modules/l16/l16.c
new file mode 100644
index 0000000..204a8f5
--- /dev/null
+++ b/modules/l16/l16.c
@@ -0,0 +1,104 @@
+/**
+ * @file l16.c 16-bit linear codec
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup l16 l16
+ *
+ * Linear 16-bit audio codec
+ */
+
+
+enum {NR_CODECS = 8};
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int16_t *p = (void *)buf;
+ (void)st;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < sampc*2)
+ return ENOMEM;
+
+ *len = sampc*2;
+
+ while (sampc--)
+ *p++ = htons(*sampv++);
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int16_t *p = (void *)buf;
+ (void)st;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*sampc < len/2)
+ return ENOMEM;
+
+ *sampc = len/2;
+
+ len /= 2;
+ while (len--)
+ *sampv++ = ntohs(*p++);
+
+ return 0;
+}
+
+
+/* See RFC 3551 */
+static struct aucodec l16v[NR_CODECS] = {
+{LE_INIT, "10", "L16", 44100, 44100, 2, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 32000, 32000, 2, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 16000, 16000, 2, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 8000, 8000, 2, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, "11", "L16", 44100, 44100, 1, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 32000, 32000, 1, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 16000, 16000, 1, 0, 0, encode, 0, decode, 0, 0, 0},
+{LE_INIT, 0, "L16", 8000, 8000, 1, 0, 0, encode, 0, decode, 0, 0, 0},
+};
+
+
+static int module_init(void)
+{
+ struct list *aucodecl = baresip_aucodecl();
+ size_t i;
+
+ for (i=0; i<NR_CODECS; i++)
+ aucodec_register(aucodecl, &l16v[i]);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ size_t i;
+
+ for (i=0; i<NR_CODECS; i++)
+ aucodec_unregister(&l16v[i]);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(l16) = {
+ "l16",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/l16/module.mk b/modules/l16/module.mk
new file mode 100644
index 0000000..b870d18
--- /dev/null
+++ b/modules/l16/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := l16
+$(MOD)_SRCS += l16.c
+
+include mk/mod.mk
diff --git a/modules/libsrtp/module.mk b/modules/libsrtp/module.mk
new file mode 100644
index 0000000..d24ad60
--- /dev/null
+++ b/modules/libsrtp/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := libsrtp
+$(MOD)_SRCS += srtp.c sdes.c
+$(MOD)_LFLAGS += -lsrtp
+
+include mk/mod.mk
diff --git a/modules/libsrtp/sdes.c b/modules/libsrtp/sdes.c
new file mode 100644
index 0000000..1a90f3f
--- /dev/null
+++ b/modules/libsrtp/sdes.c
@@ -0,0 +1,46 @@
+/**
+ * @file libsrtp/sdes.c SDP Security Descriptions (RFC 4568)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+static const char sdp_attr_crypto[] = "crypto";
+
+
+int libsrtp_sdes_encode_crypto(struct sdp_media *m, uint32_t tag,
+ const char *suite,
+ const char *key, size_t key_len)
+{
+ return sdp_media_set_lattr(m, true, sdp_attr_crypto, "%u %s inline:%b",
+ tag, suite, key, key_len);
+}
+
+
+/* http://tools.ietf.org/html/rfc4568
+ * a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+ */
+int libsrtp_sdes_decode_crypto(struct crypto *c, const char *val)
+{
+ struct pl tag, key_prms;
+ int err;
+
+ err = re_regex(val, str_len(val), "[0-9]+ [^ ]+ [^ ]+[]*[^]*",
+ &tag, &c->suite, &key_prms, NULL, &c->sess_prms);
+ if (err)
+ return err;
+
+ c->tag = pl_u32(&tag);
+
+ c->lifetime = c->mki = pl_null;
+ err = re_regex(key_prms.p, key_prms.l, "[^:]+:[^|]+[|]*[^|]*[|]*[^|]*",
+ &c->key_method, &c->key_info,
+ NULL, &c->lifetime, NULL, &c->mki);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/modules/libsrtp/sdes.h b/modules/libsrtp/sdes.h
new file mode 100644
index 0000000..8c2c1f9
--- /dev/null
+++ b/modules/libsrtp/sdes.h
@@ -0,0 +1,22 @@
+/**
+ * @file libsrtp/sdes.h SDP Security Descriptions (RFC 4568) API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct crypto {
+ uint32_t tag;
+ struct pl suite;
+ struct pl key_method;
+ struct pl key_info;
+ struct pl lifetime; /* optional */
+ struct pl mki; /* optional */
+ struct pl sess_prms; /* optional */
+};
+
+
+int libsrtp_sdes_encode_crypto(struct sdp_media *m, uint32_t tag,
+ const char *suite,
+ const char *key, size_t key_len);
+int libsrtp_sdes_decode_crypto(struct crypto *c, const char *val);
diff --git a/modules/libsrtp/srtp.c b/modules/libsrtp/srtp.c
new file mode 100644
index 0000000..0534a60
--- /dev/null
+++ b/modules/libsrtp/srtp.c
@@ -0,0 +1,474 @@
+/**
+ * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#if defined (__GNUC__) && !defined (asm)
+#define asm __asm__ /* workaround */
+#endif
+#include <srtp/srtp.h>
+#include <srtp/crypto_kernel.h>
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+/*
+ * NOTE: this module is deprecated, please use the 'srtp' module instead.
+ */
+
+
+struct menc_st {
+ /* one SRTP session per media line */
+ uint8_t key_tx[32]; /* 32 for alignment, only 30 used */
+ uint8_t key_rx[32];
+ srtp_t srtp_tx, srtp_rx;
+ srtp_policy_t policy_tx, policy_rx;
+ bool use_srtp;
+ char *crypto_suite;
+
+ void *rtpsock;
+ void *rtcpsock;
+ struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */
+ struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */
+ struct sdp_media *sdpm;
+};
+
+
+static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80";
+
+
+static void destructor(void *arg)
+{
+ struct menc_st *st = arg;
+
+ mem_deref(st->sdpm);
+ mem_deref(st->crypto_suite);
+
+ /* note: must be done before freeing socket */
+ mem_deref(st->uh_rtp);
+ mem_deref(st->uh_rtcp);
+ mem_deref(st->rtpsock);
+ mem_deref(st->rtcpsock);
+
+ if (st->srtp_tx)
+ srtp_dealloc(st->srtp_tx);
+ if (st->srtp_rx)
+ srtp_dealloc(st->srtp_rx);
+}
+
+
+static bool cryptosuite_issupported(const struct pl *suite)
+{
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true;
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true;
+
+ return false;
+}
+
+
+static int errstatus_print(struct re_printf *pf, err_status_t e)
+{
+ const char *s;
+
+ switch (e) {
+
+ case err_status_ok: s = "ok"; break;
+ case err_status_fail: s = "fail"; break;
+ case err_status_auth_fail: s = "auth_fail"; break;
+ case err_status_cipher_fail: s = "cipher_fail"; break;
+ case err_status_replay_fail: s = "replay_fail"; break;
+
+ default:
+ return re_hprintf(pf, "err=%d", e);
+ }
+
+ return re_hprintf(pf, "%s", s);
+}
+
+
+/*
+ * See RFC 5764 figure 3:
+ *
+ * +----------------+
+ * | 127 < B < 192 -+--> forward to RTP
+ * | |
+ * packet --> | 19 < B < 64 -+--> forward to DTLS
+ * | |
+ * | B < 2 -+--> forward to STUN
+ * +----------------+
+ *
+ */
+static bool is_rtp_or_rtcp(const struct mbuf *mb)
+{
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mbuf_buf(mb)[0];
+
+ return 127 < b && b < 192;
+}
+
+
+static bool is_rtcp_packet(const struct mbuf *mb)
+{
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return false;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ return 64 <= pt && pt <= 95;
+}
+
+
+static int start_srtp(struct menc_st *st, const char *suite)
+{
+ crypto_policy_t policy;
+ err_status_t e;
+
+ if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy);
+ }
+ else if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy);
+ }
+ else {
+ warning("srtp: unknown SRTP crypto suite (%s)\n", suite);
+ return ENOENT;
+ }
+
+ /* transmit policy */
+ st->policy_tx.rtp = policy;
+ st->policy_tx.rtcp = policy;
+ st->policy_tx.ssrc.type = ssrc_any_outbound;
+ st->policy_tx.key = st->key_tx;
+ st->policy_tx.next = NULL;
+
+ /* receive policy */
+ st->policy_rx.rtp = policy;
+ st->policy_rx.rtcp = policy;
+ st->policy_rx.ssrc.type = ssrc_any_inbound;
+ st->policy_rx.key = st->key_rx;
+ st->policy_rx.next = NULL;
+
+ /* allocate and initialize the SRTP session */
+ e = srtp_create(&st->srtp_tx, &st->policy_tx);
+ if (e != err_status_ok) {
+ warning("srtp: srtp_create TX failed (%H)\n",
+ errstatus_print, e);
+ return EPROTO;
+ }
+
+ e = srtp_create(&st->srtp_rx, &st->policy_rx);
+ if (err_status_ok != e) {
+ warning("srtp: srtp_create RX failed (%H)\n",
+ errstatus_print, e);
+ return EPROTO;
+ }
+
+ /* use SRTP for this stream/session */
+ st->use_srtp = true;
+
+ return 0;
+}
+
+
+static int setup_srtp(struct menc_st *st)
+{
+ err_status_t e;
+
+ /* init SRTP */
+ e = crypto_get_random(st->key_tx, SRTP_MASTER_KEY_LEN);
+ if (err_status_ok != e) {
+ warning("srtp: crypto_get_random() failed (%H)\n",
+ errstatus_print, e);
+ return ENOSYS;
+ }
+
+ return 0;
+}
+
+
+static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ err_status_t e;
+ int len;
+ (void)dst;
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ len = (int)mbuf_get_left(mb);
+
+ if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) {
+ mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN);
+ }
+
+ if (is_rtcp_packet(mb)) {
+ e = srtp_protect_rtcp(st->srtp_tx, mbuf_buf(mb), &len);
+ }
+ else {
+ e = srtp_protect(st->srtp_tx, mbuf_buf(mb), &len);
+ }
+
+ if (err_status_ok != e) {
+ warning("srtp: send: failed to protect %s-packet"
+ " with %d bytes (%H)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP",
+ len, errstatus_print, e);
+ *err = EPROTO;
+ return false;
+ }
+
+ mbuf_set_end(mb, mb->pos + len);
+
+ return false; /* continue processing */
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ err_status_t e;
+ int len;
+ (void)src;
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ len = (int)mbuf_get_left(mb);
+
+ if (is_rtcp_packet(mb)) {
+ e = srtp_unprotect_rtcp(st->srtp_rx, mbuf_buf(mb), &len);
+ }
+ else {
+ e = srtp_unprotect(st->srtp_rx, mbuf_buf(mb), &len);
+ }
+
+ if (e != err_status_ok) {
+ warning("srtp: recv: failed to unprotect %s-packet"
+ " with %d bytes (%H)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP",
+ len, errstatus_print, e);
+ return true; /* error - drop packet */
+ }
+
+ mbuf_set_end(mb, mb->pos + len);
+
+ return false; /* continue processing */
+}
+
+
+/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */
+static int sdp_enc(struct menc_st *st, struct sdp_media *m,
+ uint32_t tag, const char *suite)
+{
+ char key[128] = "";
+ size_t olen;
+ int err;
+
+ olen = sizeof(key);
+ err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen);
+ if (err)
+ return err;
+
+ return libsrtp_sdes_encode_crypto(m, tag, suite, key, olen);
+}
+
+
+static int start_crypto(struct menc_st *st, const struct pl *key_info)
+{
+ size_t olen;
+ int err;
+
+ /* key-info is BASE64 encoded */
+
+ olen = sizeof(st->key_rx);
+ err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen);
+ if (err)
+ return err;
+
+ if (SRTP_MASTER_KEY_LEN != olen) {
+ warning("srtp: srtp keylen is %u (should be 30)\n", olen);
+ }
+
+ err = start_srtp(st, st->crypto_suite);
+ if (err)
+ return err;
+
+ info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n",
+ sdp_media_name(st->sdpm), st->crypto_suite);
+
+ return 0;
+}
+
+
+static bool sdp_attr_handler(const char *name, const char *value, void *arg)
+{
+ struct menc_st *st = arg;
+ struct crypto c;
+ (void)name;
+
+ if (libsrtp_sdes_decode_crypto(&c, value))
+ return false;
+
+ if (0 != pl_strcmp(&c.key_method, "inline"))
+ return false;
+
+ if (!cryptosuite_issupported(&c.suite))
+ return false;
+
+ st->crypto_suite = mem_deref(st->crypto_suite);
+ pl_strdup(&st->crypto_suite, &c.suite);
+
+ if (start_crypto(st, &c.key_info))
+ return false;
+
+ sdp_enc(st, st->sdpm, c.tag, st->crypto_suite);
+
+ return true;
+}
+
+
+static int alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_st *st;
+ const char *rattr = NULL;
+ int layer = 10; /* above zero */
+ int err = 0;
+ bool mux = (rtpsock == rtcpsock);
+ (void)sess;
+ (void)rtp;
+
+ if (!stp || !sdpm)
+ return EINVAL;
+ if (proto != IPPROTO_UDP)
+ return EPROTONOSUPPORT;
+
+ st = (struct menc_st *)*stp;
+ if (!st) {
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sdpm = mem_ref(sdpm);
+
+ err = sdp_media_set_alt_protos(st->sdpm, 4,
+ "RTP/AVP",
+ "RTP/AVPF",
+ "RTP/SAVP",
+ "RTP/SAVPF");
+ if (err)
+ goto out;
+
+ if (rtpsock) {
+ st->rtpsock = mem_ref(rtpsock);
+ err |= udp_register_helper(&st->uh_rtp, rtpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (rtcpsock && !mux) {
+ st->rtcpsock = mem_ref(rtcpsock);
+ err |= udp_register_helper(&st->uh_rtcp, rtcpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (err)
+ goto out;
+
+ /* set our preferred crypto-suite */
+ err |= str_dup(&st->crypto_suite, aes_cm_128_hmac_sha1_80);
+ if (err)
+ goto out;
+
+ err = setup_srtp(st);
+ if (err)
+ goto out;
+ }
+
+ /* SDP handling */
+
+ if (sdp_media_rattr(st->sdpm, "crypto")) {
+
+ rattr = sdp_media_rattr_apply(st->sdpm, "crypto",
+ sdp_attr_handler, st);
+ if (!rattr) {
+ warning("srtp: no valid a=crypto attribute from"
+ " remote peer\n");
+ }
+ }
+
+ if (!rattr)
+ err = sdp_enc(st, sdpm, 0, st->crypto_suite);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct menc_media *)st;
+
+ return err;
+}
+
+
+static struct menc menc_srtp_opt = {
+ LE_INIT, "srtp", "RTP/AVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mand = {
+ LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mandf = {
+ LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc
+};
+
+
+static int mod_srtp_init(void)
+{
+ struct list *mencl = baresip_mencl();
+ err_status_t err;
+
+ err = srtp_init();
+ if (err_status_ok != err) {
+ warning("srtp: srtp_init() failed (%H)\n",
+ errstatus_print, err);
+ return ENOSYS;
+ }
+
+ menc_register(mencl, &menc_srtp_opt);
+ menc_register(mencl, &menc_srtp_mand);
+ menc_register(mencl, &menc_srtp_mandf);
+
+ return 0;
+}
+
+
+static int mod_srtp_close(void)
+{
+ menc_unregister(&menc_srtp_mandf);
+ menc_unregister(&menc_srtp_mand);
+ menc_unregister(&menc_srtp_opt);
+
+ crypto_kernel_shutdown();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(libsrtp) = {
+ "libsrtp",
+ "menc",
+ mod_srtp_init,
+ mod_srtp_close
+};
diff --git a/modules/menu/menu.c b/modules/menu/menu.c
new file mode 100644
index 0000000..c7f1646
--- /dev/null
+++ b/modules/menu/menu.c
@@ -0,0 +1,1162 @@
+/**
+ * @file menu.c Interactive menu
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup menu menu
+ *
+ * Interactive menu
+ *
+ * This module must be loaded if you want to use the interactive menu
+ * to control the Baresip application.
+ */
+
+
+/** Defines the status modes */
+enum statmode {
+ STATMODE_CALL = 0,
+ STATMODE_OFF,
+};
+
+
+static uint64_t start_ticks; /**< Ticks when app started */
+static struct tmr tmr_alert; /**< Incoming call alert timer */
+static struct tmr tmr_stat; /**< Call status timer */
+static enum statmode statmode; /**< Status mode */
+static struct mbuf *dialbuf; /**< Buffer for dialled number */
+static struct le *le_cur; /**< Current User-Agent (struct ua) */
+
+static struct {
+ struct play *play;
+ struct message_lsnr *message;
+ bool bell;
+ bool ringback_disabled; /**< no ringback on sip 180 respons */
+ struct tmr tmr_redial; /**< Timer for auto-reconnect */
+ uint32_t redial_delay; /**< Redial delay in [seconds] */
+ uint32_t redial_attempts; /**< Number of re-dial attempts */
+ uint32_t current_attempts; /**< Current number of re-dials */
+} menu;
+
+
+static int menu_set_incall(bool incall);
+static void update_callstatus(void);
+static void alert_stop(void);
+static int switch_audio_source(struct re_printf *pf, void *arg);
+static int switch_audio_player(struct re_printf *pf, void *arg);
+
+
+static void redial_reset(void)
+{
+ tmr_cancel(&menu.tmr_redial);
+ menu.current_attempts = 0;
+}
+
+
+static const char *translate_errorcode(uint16_t scode)
+{
+ switch (scode) {
+
+ case 404: return "notfound.wav";
+ case 486: return "busy.wav";
+ case 487: return NULL; /* ignore */
+ default: return "error.wav";
+ }
+}
+
+
+static void check_registrations(void)
+{
+ static bool ual_ready = false;
+ struct le *le;
+ uint32_t n;
+
+ if (ual_ready)
+ return;
+
+ for (le = list_head(uag_list()); le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (!ua_isregistered(ua))
+ return;
+ }
+
+ n = list_count(uag_list());
+
+ /* We are ready */
+ ui_output(baresip_uis(),
+ "\x1b[32mAll %u useragent%s registered successfully!"
+ " (%u ms)\x1b[;m\n",
+ n, n==1 ? "" : "s",
+ (uint32_t)(tmr_jiffies() - start_ticks));
+
+ ual_ready = true;
+}
+
+
+/**
+ * Return the current User-Agent in focus
+ *
+ * @return Current User-Agent
+ */
+static struct ua *uag_cur(void)
+{
+ return uag_current();
+}
+
+
+/* Return TRUE if there are any active calls for any UAs */
+static bool have_active_calls(void)
+{
+ struct le *le;
+
+ for (le = list_head(uag_list()); le; le = le->next) {
+
+ struct ua *ua = le->data;
+
+ if (ua_call(ua))
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Print the SIP Registration for all User-Agents
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int ua_print_reg_status(struct re_printf *pf, void *unused)
+{
+ struct le *le;
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "\n--- Useragents: %u ---\n",
+ list_count(uag_list()));
+
+ for (le = list_head(uag_list()); le && !err; le = le->next) {
+ const struct ua *ua = le->data;
+
+ err = re_hprintf(pf, "%s ", ua == uag_cur() ? ">" : " ");
+ err |= ua_print_status(pf, ua);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Print the current SIP Call status for the current User-Agent
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int ua_print_call_status(struct re_printf *pf, void *unused)
+{
+ struct call *call;
+ int err;
+
+ (void)unused;
+
+ call = ua_call(uag_cur());
+ if (call) {
+ err = re_hprintf(pf, "\n%H\n", call_debug, call);
+ }
+ else {
+ err = re_hprintf(pf, "\n(no active calls)\n");
+ }
+
+ return err;
+}
+
+
+static int dial_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ int err = 0;
+
+ (void)pf;
+
+ if (str_isset(carg->prm)) {
+
+ mbuf_rewind(dialbuf);
+ (void)mbuf_write_str(dialbuf, carg->prm);
+
+ err = ua_connect(uag_cur(), NULL, NULL,
+ carg->prm, NULL, VIDMODE_ON);
+ }
+ else if (dialbuf->end > 0) {
+
+ char *uri;
+
+ dialbuf->pos = 0;
+ err = mbuf_strdup(dialbuf, &uri, dialbuf->end);
+ if (err)
+ return err;
+
+ err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON);
+
+ mem_deref(uri);
+ }
+
+ if (err) {
+ warning("menu: ua_connect failed: %m\n", err);
+ }
+
+ return err;
+}
+
+
+static void options_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ (void)arg;
+
+ if (err) {
+ warning("options reply error: %m\n", err);
+ return;
+ }
+
+ if (msg->scode < 200)
+ return;
+
+ if (msg->scode < 300) {
+
+ mbuf_set_pos(msg->mb, 0);
+ info("----- OPTIONS of %r -----\n%b",
+ &(msg->to.auri), mbuf_buf(msg->mb),
+ mbuf_get_left(msg->mb));
+ return;
+ }
+
+ info("%r: OPTIONS failed: %u %r\n", &(msg->to.auri),
+ msg->scode, &msg->reason);
+}
+
+
+static int options_command(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ int err = 0;
+
+ (void)pf;
+
+ if (str_isset(carg->prm)) {
+
+ mbuf_rewind(dialbuf);
+ (void)mbuf_write_str(dialbuf, carg->prm);
+
+ err = ua_options_send(uag_cur(), carg->prm,
+ options_resp_handler, NULL);
+ }
+ else if (dialbuf->end > 0) {
+
+ char *uri;
+
+ dialbuf->pos = 0;
+ err = mbuf_strdup(dialbuf, &uri, dialbuf->end);
+ if (err)
+ return err;
+
+ err = ua_options_send(uag_cur(), uri,
+ options_resp_handler, NULL);
+
+ mem_deref(uri);
+ }
+
+ if (err) {
+ warning("menu: ua_options failed: %m\n", err);
+ }
+
+ return err;
+}
+
+
+static int cmd_answer(struct re_printf *pf, void *unused)
+{
+ struct ua *ua = uag_cur();
+ int err;
+ (void)unused;
+
+ err = re_hprintf(pf, "%s: Answering incoming call\n", ua_aor(ua));
+
+ /* Stop any ongoing ring-tones */
+ menu.play = mem_deref(menu.play);
+
+ ua_hold_answer(ua, NULL);
+
+ return err;
+}
+
+
+static int cmd_hangup(struct re_printf *pf, void *unused)
+{
+ (void)pf;
+ (void)unused;
+
+ /* Stop any ongoing ring-tones */
+ menu.play = mem_deref(menu.play);
+ alert_stop();
+
+ ua_hangup(uag_cur(), NULL, 0, NULL);
+
+ /* note: must be called after ua_hangup() */
+ menu_set_incall(have_active_calls());
+
+ return 0;
+}
+
+
+static int create_ua(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct le *le;
+ int err = 0;
+
+ (void)pf;
+
+ if (str_isset(carg->prm)) {
+
+ mbuf_rewind(dialbuf);
+ (void)mbuf_write_str(dialbuf, carg->prm);
+
+ (void)re_hprintf(pf, "Creating UA for %s ...\n", carg->prm);
+ err = ua_alloc(NULL, carg->prm);
+
+
+ }
+ else if (dialbuf->end > 0) {
+
+ char *uri;
+
+ dialbuf->pos = 0;
+ err = mbuf_strdup(dialbuf, &uri, dialbuf->end);
+ if (err)
+ return err;
+
+ (void)re_hprintf(pf, "Creating UA for %s ...\n", uri);
+ err |= ua_alloc(NULL, uri);
+
+ mem_deref(uri);
+ }
+
+ for (le = list_head(uag_list()); le && !err; le = le->next) {
+ const struct ua *ua = le->data;
+
+ err = re_hprintf(pf, "%s ", ua == uag_cur() ? ">" : " ");
+ err |= ua_print_status(pf, ua);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+
+ if (err) {
+ (void)re_hprintf(pf, "menu: create_ua failed: %m\n", err);
+ }
+
+
+ return err;
+}
+
+
+static int cmd_ua_next(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)pf;
+ (void)unused;
+
+ if (!le_cur)
+ le_cur = list_head(uag_list());
+ if (!le_cur)
+ return 0;
+
+ le_cur = le_cur->next ? le_cur->next : list_head(uag_list());
+
+ err = re_hprintf(pf, "ua: %s\n", ua_aor(list_ledata(le_cur)));
+
+ uag_current_set(list_ledata(le_cur));
+
+ update_callstatus();
+
+ return err;
+}
+
+
+static int print_commands(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return cmd_print(pf, baresip_commands());
+}
+
+
+static int cmd_print_calls(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return ua_print_calls(pf, uag_cur());
+}
+
+
+static const char about_fmt[] =
+ ".------------------------------------------------------------.\n"
+ "| "
+ "\x1b[34;1m" "bare"
+ "\x1b[31;1m" "sip"
+ "\x1b[;m"
+ " %-10s |\n"
+ "| |\n"
+ "| Baresip is a portable and modular SIP User-Agent |\n"
+ "| with audio and video support |\n"
+ "| |\n"
+ "| License: BSD |\n"
+ "| Homepage: https://github.com/alfredh/baresip |\n"
+ "| |\n"
+ "'------------------------------------------------------------'\n"
+ ;
+
+
+static int about_box(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+
+ return re_hprintf(pf, about_fmt, BARESIP_VERSION);
+}
+
+
+static const struct cmd cmdv[] = {
+
+{"accept", 'a', 0, "Accept incoming call", cmd_answer },
+{"hangup", 'b', 0, "Hangup call", cmd_hangup },
+{"callstat", 'c', 0, "Call status", ua_print_call_status },
+{"dial", 'd', CMD_PRM, "Dial", dial_handler },
+{"help", 'h', 0, "Help menu", print_commands },
+{"listcalls", 'l', 0, "List active calls", cmd_print_calls },
+{"options", 'o', CMD_PRM, "Options", options_command },
+{"reginfo", 'r', 0, "Registration info", ua_print_reg_status },
+{NULL, KEYCODE_ESC,0, "Hangup call", cmd_hangup },
+{"uanext", 'T', 0, "Toggle UAs", cmd_ua_next },
+{"uanew", 0, CMD_PRM, "Create User-Agent", create_ua },
+{"ausrc", 0, CMD_IPRM, "Switch audio source", switch_audio_source },
+{"auplay", 0, CMD_IPRM, "Switch audio player", switch_audio_player },
+{"about", 0, 0, "About box", about_box },
+
+};
+
+static const struct cmd dialcmdv[] = {
+/* Numeric keypad inputs: */
+{NULL, '#', CMD_PRM, NULL, dial_handler },
+{NULL, '*', CMD_PRM, NULL, dial_handler },
+{NULL, '0', CMD_PRM, NULL, dial_handler },
+{NULL, '1', CMD_PRM, NULL, dial_handler },
+{NULL, '2', CMD_PRM, NULL, dial_handler },
+{NULL, '3', CMD_PRM, NULL, dial_handler },
+{NULL, '4', CMD_PRM, NULL, dial_handler },
+{NULL, '5', CMD_PRM, NULL, dial_handler },
+{NULL, '6', CMD_PRM, NULL, dial_handler },
+{NULL, '7', CMD_PRM, NULL, dial_handler },
+{NULL, '8', CMD_PRM, NULL, dial_handler },
+{NULL, '9', CMD_PRM, NULL, dial_handler },
+};
+
+
+static int call_audio_debug(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return audio_debug(pf, call_audio(ua_call(uag_cur())));
+}
+
+
+static int call_audioenc_cycle(struct re_printf *pf, void *unused)
+{
+ (void)pf;
+ (void)unused;
+ audio_encoder_cycle(call_audio(ua_call(uag_cur())));
+ return 0;
+}
+
+
+static int call_reinvite(struct re_printf *pf, void *unused)
+{
+ (void)pf;
+ (void)unused;
+ return call_modify(ua_call(uag_cur()));
+}
+
+
+static int call_mute(struct re_printf *pf, void *unused)
+{
+ struct audio *audio = call_audio(ua_call(uag_cur()));
+ bool muted = !audio_ismuted(audio);
+ (void)unused;
+
+ (void)re_hprintf(pf, "\ncall %smuted\n", muted ? "" : "un-");
+ audio_mute(audio, muted);
+
+ return 0;
+}
+
+
+static int call_xfer(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ static bool xfer_inprogress;
+
+ if (!xfer_inprogress && !carg->complete) {
+ statmode = STATMODE_OFF;
+ re_hprintf(pf, "\rPlease enter transfer target SIP uri:\n");
+ }
+
+ xfer_inprogress = true;
+
+ if (carg->complete) {
+ statmode = STATMODE_CALL;
+ xfer_inprogress = false;
+ return call_transfer(ua_call(uag_cur()), carg->prm);
+ }
+
+ return 0;
+}
+
+
+static int cmd_call_hold(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ return call_hold(ua_call(uag_cur()), true);
+}
+
+
+static int cmd_call_resume(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ return call_hold(ua_call(uag_cur()), false);
+}
+
+
+static int hold_prev_call(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ (void)pf;
+
+ return call_hold(ua_prev_call(uag_cur()), 'H' == carg->key);
+}
+
+
+static int switch_audio_player(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct pl pl_driver, pl_device;
+ struct config_audio *aucfg;
+ struct config *cfg;
+ struct audio *a;
+ struct le *le;
+ char driver[16], device[128] = "";
+ int err = 0;
+
+ static bool switch_aud_inprogress;
+
+ if (!switch_aud_inprogress && !carg->complete) {
+ re_hprintf(pf,
+ "\rPlease enter audio device (driver,device)\n");
+ }
+
+ switch_aud_inprogress = true;
+
+ if (carg->complete) {
+
+ switch_aud_inprogress = false;
+
+ if (re_regex(carg->prm, str_len(carg->prm), "[^,]+,[~]*",
+ &pl_driver, &pl_device)) {
+
+ return re_hprintf(pf, "\rFormat should be:"
+ " driver,device\n");
+ }
+
+ pl_strcpy(&pl_driver, driver, sizeof(driver));
+ pl_strcpy(&pl_device, device, sizeof(device));
+
+ if (!auplay_find(baresip_auplayl(), driver)) {
+ re_hprintf(pf, "no such audio-player: %s\n", driver);
+ return 0;
+ }
+
+ re_hprintf(pf, "switch audio player: %s,%s\n",
+ driver, device);
+
+ cfg = conf_config();
+ if (!cfg) {
+ return re_hprintf(pf, "no config object\n");
+ }
+
+ aucfg = &cfg->audio;
+
+ str_ncpy(aucfg->play_mod, driver, sizeof(aucfg->play_mod));
+ str_ncpy(aucfg->play_dev, device, sizeof(aucfg->play_dev));
+
+ str_ncpy(aucfg->alert_mod, driver, sizeof(aucfg->alert_mod));
+ str_ncpy(aucfg->alert_dev, device, sizeof(aucfg->alert_dev));
+
+ for (le = list_tail(ua_calls(uag_cur())); le; le = le->prev) {
+
+ struct call *call = le->data;
+
+ a = call_audio(call);
+
+ err = audio_set_player(a, driver, device);
+ if (err) {
+ re_hprintf(pf, "failed to set audio-player"
+ " (%m)\n", err);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int switch_audio_source(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct pl pl_driver, pl_device;
+ struct config_audio *aucfg;
+ struct config *cfg;
+ struct audio *a;
+ struct le *le;
+ char driver[16], device[128] = "";
+ int err = 0;
+
+ static bool switch_aud_inprogress;
+
+ if (!switch_aud_inprogress && !carg->complete) {
+ re_hprintf(pf,
+ "\rPlease enter audio device (driver,device)\n");
+ }
+
+ switch_aud_inprogress = true;
+
+ if (carg->complete) {
+
+ switch_aud_inprogress = false;
+
+ if (re_regex(carg->prm, str_len(carg->prm), "[^,]+,[~]*",
+ &pl_driver, &pl_device)) {
+
+ return re_hprintf(pf, "\rFormat should be:"
+ " driver,device\n");
+ }
+
+ pl_strcpy(&pl_driver, driver, sizeof(driver));
+ pl_strcpy(&pl_device, device, sizeof(device));
+
+ if (!ausrc_find(baresip_ausrcl(), driver)) {
+ re_hprintf(pf, "no such audio-source: %s\n", driver);
+ return 0;
+ }
+
+ re_hprintf(pf, "switch audio device: %s,%s\n",
+ driver, device);
+
+ cfg = conf_config();
+ if (!cfg) {
+ return re_hprintf(pf, "no config object\n");
+ }
+
+ aucfg = &cfg->audio;
+
+ str_ncpy(aucfg->src_mod, driver, sizeof(aucfg->src_mod));
+ str_ncpy(aucfg->src_dev, device, sizeof(aucfg->src_dev));
+
+ for (le = list_tail(ua_calls(uag_cur())); le; le = le->prev) {
+
+ struct call *call = le->data;
+
+ a = call_audio(call);
+
+ err = audio_set_source(a, driver, device);
+ if (err) {
+ re_hprintf(pf, "failed to set audio-source"
+ " (%m)\n", err);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef USE_VIDEO
+static int call_videoenc_cycle(struct re_printf *pf, void *unused)
+{
+ (void)pf;
+ (void)unused;
+ video_encoder_cycle(call_video(ua_call(uag_cur())));
+ return 0;
+}
+
+
+static int call_video_debug(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return video_debug(pf, call_video(ua_call(uag_cur())));
+}
+#endif
+
+
+static int digit_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct call *call;
+ int err = 0;
+
+ (void)pf;
+
+ call = ua_call(uag_cur());
+ if (call)
+ err = call_send_digit(call, carg->key);
+
+ return err;
+}
+
+
+static int toggle_statmode(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ if (statmode == STATMODE_OFF)
+ statmode = STATMODE_CALL;
+ else
+ statmode = STATMODE_OFF;
+
+ return 0;
+}
+
+
+static int set_current_call(struct re_printf *pf, void *arg)
+{
+ struct cmd_arg *carg = arg;
+ struct call *call;
+ uint32_t linenum = atoi(carg->prm);
+ int err;
+
+ call = call_find_linenum(ua_calls(uag_cur()), linenum);
+ if (call) {
+ err = re_hprintf(pf, "setting current call: line %u\n",
+ linenum);
+ call_set_current(ua_calls(uag_cur()), call);
+ }
+ else {
+ err = re_hprintf(pf, "call not found\n");
+ }
+
+ return err;
+}
+
+
+static const struct cmd callcmdv[] = {
+{"reinvite", 'I', 0, "Send re-INVITE", call_reinvite },
+{"resume", 'X', 0, "Call resume", cmd_call_resume },
+{"audio_debug",'A', 0, "Audio stream", call_audio_debug },
+{"audio_cycle",'e', 0, "Cycle audio encoder", call_audioenc_cycle },
+{"mute", 'm', 0, "Call mute/un-mute", call_mute },
+{"transfer", 't', CMD_IPRM, "Transfer call", call_xfer },
+{"hold", 'x', 0, "Call hold", cmd_call_hold },
+{"", 'H', 0, "Hold previous call", hold_prev_call },
+{"", 'L', 0, "Resume previous call",hold_prev_call },
+
+#ifdef USE_VIDEO
+{"video_cycle", 'E', 0, "Cycle video encoder", call_videoenc_cycle },
+{"video_debug", 'V', 0, "Video stream", call_video_debug },
+#endif
+
+/* Numeric keypad for DTMF events: */
+{NULL, '#', 0, NULL, digit_handler },
+{NULL, '*', 0, NULL, digit_handler },
+{NULL, '0', 0, NULL, digit_handler },
+{NULL, '1', 0, NULL, digit_handler },
+{NULL, '2', 0, NULL, digit_handler },
+{NULL, '3', 0, NULL, digit_handler },
+{NULL, '4', 0, NULL, digit_handler },
+{NULL, '5', 0, NULL, digit_handler },
+{NULL, '6', 0, NULL, digit_handler },
+{NULL, '7', 0, NULL, digit_handler },
+{NULL, '8', 0, NULL, digit_handler },
+{NULL, '9', 0, NULL, digit_handler },
+{NULL, KEYCODE_REL, 0, NULL, digit_handler },
+
+{NULL, 'S', 0, "Statusmode toggle", toggle_statmode },
+{"line",'@', CMD_PRM, "Set current call <line>", set_current_call },
+};
+
+
+static int menu_set_incall(bool incall)
+{
+ struct commands *commands = baresip_commands();
+ int err = 0;
+
+ /* Dynamic menus */
+ if (incall) {
+ cmd_unregister(commands, dialcmdv);
+
+ if (!cmds_find(commands, callcmdv)) {
+ err = cmd_register(commands,
+ callcmdv, ARRAY_SIZE(callcmdv));
+ }
+ }
+ else {
+ cmd_unregister(commands, callcmdv);
+
+ if (!cmds_find(commands, dialcmdv)) {
+ err = cmd_register(baresip_commands(), dialcmdv,
+ ARRAY_SIZE(dialcmdv));
+ }
+ }
+ if (err) {
+ warning("menu: set_incall: cmd_register failed (%m)\n", err);
+ }
+
+ return err;
+}
+
+
+static void tmrstat_handler(void *arg)
+{
+ struct call *call;
+ (void)arg;
+
+ /* the UI will only show the current active call */
+ call = ua_call(uag_cur());
+ if (!call)
+ return;
+
+ tmr_start(&tmr_stat, 100, tmrstat_handler, 0);
+
+ if (ui_isediting(baresip_uis()))
+ return;
+
+ if (STATMODE_OFF != statmode) {
+ (void)re_fprintf(stderr, "%H\r", call_status, call);
+ }
+}
+
+
+static void update_callstatus(void)
+{
+ /* if there are any active calls, enable the call status view */
+ if (have_active_calls())
+ tmr_start(&tmr_stat, 100, tmrstat_handler, 0);
+ else
+ tmr_cancel(&tmr_stat);
+}
+
+
+static void alert_start(void *arg)
+{
+ (void)arg;
+
+ if (!menu.bell)
+ return;
+
+ ui_output(baresip_uis(), "\033[10;1000]\033[11;1000]\a");
+
+ tmr_start(&tmr_alert, 1000, alert_start, NULL);
+}
+
+
+static void alert_stop(void)
+{
+ if (!menu.bell)
+ return;
+
+ if (tmr_isrunning(&tmr_alert))
+ ui_output(baresip_uis(), "\r");
+
+ tmr_cancel(&tmr_alert);
+}
+
+
+static void redial_handler(void *arg)
+{
+ char *uri = NULL;
+ int err;
+ (void)arg;
+
+ info("now: redialing now. current_attempts=%u, max_attempts=%u\n",
+ menu.current_attempts,
+ menu.redial_attempts);
+
+ if (menu.current_attempts > menu.redial_attempts) {
+
+ info("menu: redial: too many attemptes -- giving up\n");
+ return;
+ }
+
+ if (dialbuf->end == 0) {
+ warning("menu: redial: dialbuf is empty\n");
+ return;
+ }
+
+ dialbuf->pos = 0;
+ err = mbuf_strdup(dialbuf, &uri, dialbuf->end);
+ if (err)
+ return;
+
+ err = ua_connect(uag_cur(), NULL, NULL, uri, NULL, VIDMODE_ON);
+ if (err) {
+ warning("menu: redial: ua_connect failed (%m)\n", err);
+ }
+
+ mem_deref(uri);
+
+}
+
+
+static void ua_event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ struct player *player = baresip_player();
+
+ (void)call;
+ (void)prm;
+ (void)arg;
+
+ switch (ev) {
+
+ case UA_EVENT_CALL_INCOMING:
+
+ /* set the current User-Agent to the one with the call */
+ uag_current_set(ua);
+
+ info("%s: Incoming call from: %s %s -"
+ " (press 'a' to accept)\n",
+ ua_aor(ua), call_peername(call), call_peeruri(call));
+
+ /* stop any ringtones */
+ menu.play = mem_deref(menu.play);
+
+ /* Only play the ringtones if answermode is "Manual".
+ * If the answermode is "auto" then be silent.
+ */
+ if (ANSWERMODE_MANUAL == account_answermode(ua_account(ua))) {
+
+ if (list_count(ua_calls(ua)) > 1) {
+ (void)play_file(&menu.play, player,
+ "callwaiting.wav", 3);
+ }
+ else {
+ /* Alert user */
+ (void)play_file(&menu.play, player,
+ "ring.wav", -1);
+ }
+
+ if (menu.bell)
+ alert_start(0);
+ }
+ break;
+
+ case UA_EVENT_CALL_RINGING:
+ /* stop any ringtones */
+ menu.play = mem_deref(menu.play);
+
+ if (menu.ringback_disabled) {
+ info("\nRingback disabled\n");
+ }
+ else {
+ (void)play_file(&menu.play, player,
+ "ringback.wav",-1);
+ }
+ break;
+
+ case UA_EVENT_CALL_ESTABLISHED:
+ /* stop any ringtones */
+ menu.play = mem_deref(menu.play);
+
+ alert_stop();
+
+ /* We must stop the re-dialing if the call was
+ established */
+ redial_reset();
+ break;
+
+ case UA_EVENT_CALL_CLOSED:
+ /* stop any ringtones */
+ menu.play = mem_deref(menu.play);
+
+ if (call_scode(call)) {
+ const char *tone;
+ tone = translate_errorcode(call_scode(call));
+ if (tone) {
+ (void)play_file(&menu.play, player,
+ tone, 1);
+ }
+ }
+
+ alert_stop();
+
+ /* Activate the re-dialing if:
+ *
+ * - redial_attempts must be enabled in config
+ * - the closed call must be of outgoing direction
+ * - the closed call must fail with special code 701
+ */
+ if (menu.redial_attempts) {
+
+ if (menu.current_attempts
+ ||
+ (call_is_outgoing(call) &&
+ call_scode(call) == 701)) {
+
+ info("menu: call closed"
+ " -- redialing in %u seconds\n",
+ menu.redial_delay);
+
+ ++menu.current_attempts;
+
+ tmr_start(&menu.tmr_redial,
+ menu.redial_delay*1000,
+ redial_handler, NULL);
+ }
+ else {
+ info("menu: call closed -- not redialing\n");
+ }
+ }
+
+ break;
+
+ case UA_EVENT_REGISTER_OK:
+ check_registrations();
+ break;
+
+ case UA_EVENT_UNREGISTERING:
+ return;
+
+ default:
+ break;
+ }
+
+ menu_set_incall(have_active_calls());
+ update_callstatus();
+}
+
+
+static void message_handler(const struct pl *peer, const struct pl *ctype,
+ struct mbuf *body, void *arg)
+{
+ (void)ctype;
+ (void)arg;
+
+ ui_output(baresip_uis(), "\r%r: \"%b\"\n",
+ peer, mbuf_buf(body), mbuf_get_left(body));
+
+ (void)play_file(NULL, baresip_player(), "message.wav", 0);
+}
+
+
+static int module_init(void)
+{
+ struct pl val;
+ int err;
+
+ /*
+ * Read the config values
+ */
+ conf_get_bool(conf_cur(), "menu_bell", &menu.bell);
+ conf_get_bool(conf_cur(), "ringback_disabled",
+ &menu.ringback_disabled);
+
+ if (0 == conf_get(conf_cur(), "redial_attempts", &val) &&
+ 0 == pl_strcasecmp(&val, "inf")) {
+ menu.redial_attempts = (uint32_t)-1;
+ }
+ else {
+ conf_get_u32(conf_cur(), "redial_attempts",
+ &menu.redial_attempts);
+ }
+ conf_get_u32(conf_cur(), "redial_delay", &menu.redial_delay);
+
+ if (menu.redial_attempts) {
+ info("menu: redial enabled with %u attempts and"
+ " %u seconds delay\n",
+ menu.redial_attempts,
+ menu.redial_delay);
+ }
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ start_ticks = tmr_jiffies();
+ tmr_init(&tmr_alert);
+ statmode = STATMODE_CALL;
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ err |= cmd_register(baresip_commands(), dialcmdv,
+ ARRAY_SIZE(dialcmdv));
+ if (err)
+ return err;
+
+ err = uag_event_register(ua_event_handler, NULL);
+ if (err)
+ return err;
+
+ err = message_listen(&menu.message, baresip_message(),
+ message_handler, NULL);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ debug("menu: close (redial current_attempts=%d)\n",
+ menu.current_attempts);
+
+ menu.message = mem_deref(menu.message);
+ uag_event_unregister(ua_event_handler);
+ cmd_unregister(baresip_commands(), cmdv);
+ cmd_unregister(baresip_commands(), dialcmdv);
+ cmd_unregister(baresip_commands(), callcmdv);
+
+ tmr_cancel(&tmr_alert);
+ tmr_cancel(&tmr_stat);
+ dialbuf = mem_deref(dialbuf);
+
+ le_cur = NULL;
+
+ menu.play = mem_deref(menu.play);
+
+ tmr_cancel(&menu.tmr_redial);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(menu) = {
+ "menu",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/menu/module.mk b/modules/menu/module.mk
new file mode 100644
index 0000000..d727f4b
--- /dev/null
+++ b/modules/menu/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := menu
+$(MOD)_SRCS += menu.c
+
+include mk/mod.mk
diff --git a/modules/mpa/decode.c b/modules/mpa/decode.c
new file mode 100644
index 0000000..4e2a720
--- /dev/null
+++ b/modules/mpa/decode.c
@@ -0,0 +1,217 @@
+/**
+ * @file mpa/decode.c mpa Decode
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <mpg123.h>
+#include <speex/speex_resampler.h>
+#include <string.h>
+#include "mpa.h"
+
+struct audec_state {
+ mpg123_handle *dec;
+ SpeexResamplerState *resampler;
+ int channels;
+ int16_t intermediate_buffer[MPA_FRAMESIZE*2];
+ int start;
+};
+
+
+static void destructor(void *arg)
+{
+ struct audec_state *ads = arg;
+
+ if (ads->resampler)
+ speex_resampler_destroy(ads->resampler);
+
+ mpg123_close(ads->dec);
+ mpg123_delete(ads->dec);
+#ifdef DEBUG
+ debug("MPA dec destroyed\n");
+#endif
+}
+
+
+int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp)
+{
+ struct audec_state *ads;
+ int result, err=0;
+ (void)fmtp;
+
+ if (!adsp || !ac || !ac->ch)
+ return EINVAL;
+
+ ads = *adsp;
+
+#ifdef DEBUG
+ debug("MPA dec created %s\n",fmtp);
+#endif
+
+ if (!ads) {
+ ads = mem_zalloc(sizeof(*ads), destructor);
+ if (!ads)
+ return ENOMEM;
+ }
+ else {
+ memset(ads,0,sizeof(*ads));
+ }
+ ads->channels = 0;
+ ads->resampler = NULL;
+ ads->start = 0;
+
+ ads->dec = mpg123_new(NULL,&result);
+ if (!ads->dec) {
+ warning("MPA dec create: %s\n",
+ mpg123_plain_strerror(result));
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef DEBUG
+ result = mpg123_param(ads->dec, MPG123_VERBOSE, 4, 4.);
+#else
+ result = mpg123_param(ads->dec, MPG123_VERBOSE, 0, 0.);
+#endif
+ if (result != MPG123_OK) {
+ warning("MPA dec param error %s\n",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+
+ result = mpg123_format_all(ads->dec);
+ if (result != MPG123_OK) {
+ warning("MPA dec format error %s\n",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+ result = mpg123_open_feed(ads->dec);
+ if (result != MPG123_OK) {
+ warning("MPA dec open feed error %s\n",
+ mpg123_plain_strerror(result));
+ err = EINVAL;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(ads);
+ else
+ *adsp = ads;
+
+ return err;
+}
+
+
+int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int result, channels, encoding, i;
+ long samplerate;
+ size_t n;
+ spx_uint32_t intermediate_len;
+ spx_uint32_t out_len;
+
+#ifdef DEBUG
+ debug("MPA dec start %d %ld\n",len, *sampc);
+#endif
+
+ if (!ads || !sampv || !sampc || !buf || len<=4)
+ return EINVAL;
+
+ if (*(uint32_t*)(void *)buf != 0) {
+ warning("MPA dec header is not zero %08X, not supported yet\n",
+ *(uint32_t*)(void *)buf);
+ return EPROTO;
+ }
+
+ n = 0;
+ result = mpg123_decode(ads->dec, buf+4, len-4,
+ (unsigned char*)ads->intermediate_buffer,
+ sizeof(ads->intermediate_buffer), &n);
+ /* n counts bytes */
+#ifdef DEBUG
+ debug("MPA dec %d %d %d %d\n",result, len-4, n, ads->channels);
+#endif
+
+ if (result == MPG123_NEW_FORMAT) {
+ mpg123_getformat(ads->dec, &samplerate, &channels, &encoding);
+ info("MPA dec format change %d %d %04X\n",samplerate
+ ,channels,encoding);
+
+ ads->channels = channels;
+ ads->start = 0;
+ if (ads->resampler)
+ speex_resampler_destroy(ads->resampler);
+ if (samplerate != MPA_IORATE) {
+ ads->resampler = speex_resampler_init(channels,
+ (uint32_t)samplerate, MPA_IORATE,
+ 3, &result);
+ if (result!=RESAMPLER_ERR_SUCCESS
+ || ads->resampler==NULL) {
+ warning("MPA dec upsampler failed %d\n",
+ result);
+ return EINVAL;
+ }
+ }
+ else
+ ads->resampler = NULL;
+ }
+ else if (result == MPG123_NEED_MORE)
+ ; /* workaround: do nothing */
+ else if (result != MPG123_OK) {
+ warning("MPA dec feed error %d %s\n", result,
+ mpg123_plain_strerror(result));
+ return EPROTO;
+ }
+
+ if (ads->resampler) {
+ intermediate_len = (uint32_t)(n / 2 / ads->channels);
+ /* intermediate_len counts samples per channel */
+ out_len = (uint32_t)(*sampc / 2);
+
+ result=speex_resampler_process_interleaved_int(
+ ads->resampler, ads->intermediate_buffer,
+ &intermediate_len, sampv, &out_len);
+ if (result!=RESAMPLER_ERR_SUCCESS) {
+ warning("MPA dec upsample error: %s %d %d\n",
+ strerror(result), out_len, *sampc/2);
+ return EPROTO;
+ }
+ if (ads->channels==1) {
+ for (i=out_len-1;i>=0;i--)
+ sampv[i+i+1]=sampv[i+i]=sampv[i];
+ *sampc = out_len * 2;
+ }
+ else
+ *sampc = out_len * ads->channels;
+ }
+ else {
+ n /= 2;
+ if (ads->channels!=1) {
+ for (i=0;(unsigned)i<n;i++)
+ sampv[i]=ads->intermediate_buffer[i];
+ *sampc = n;
+ }
+ else {
+ for (i=0;(unsigned)i<n;i++)
+ sampv[i*2]=sampv[i*2+1]=
+ ads->intermediate_buffer[i];
+ *sampc = n * 2;
+ }
+
+#ifdef DEBUG
+ debug("MPA dec done %d\n",*sampc);
+#endif
+ }
+
+ return 0;
+}
+
diff --git a/modules/mpa/encode.c b/modules/mpa/encode.c
new file mode 100644
index 0000000..d13bc0a
--- /dev/null
+++ b/modules/mpa/encode.c
@@ -0,0 +1,196 @@
+/**
+ * @file mpa/encode.c mpa Encode
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <twolame.h>
+#include <string.h>
+#include <speex/speex_resampler.h>
+#include "mpa.h"
+
+
+struct auenc_state {
+ twolame_options *enc;
+ int channels, samplerate;
+ SpeexResamplerState *resampler;
+ int16_t intermediate_buffer[MPA_FRAMESIZE*6];
+ uint32_t timestamp;
+};
+
+
+static void destructor(void *arg)
+{
+ struct auenc_state *aes = arg;
+
+ if (aes->resampler) {
+ speex_resampler_destroy(aes->resampler);
+ aes->resampler = NULL;
+ }
+
+ if (aes->enc)
+ twolame_close(&aes->enc);
+#ifdef DEBUG
+ debug("MPA enc destroyed\n");
+#endif
+}
+
+
+int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *param, const char *fmtp)
+{
+ struct auenc_state *aes;
+ struct mpa_param prm;
+ int result,err=0;
+
+ (void)param;
+
+ if (!aesp || !ac || !ac->ch)
+ return EINVAL;
+
+ aes = *aesp;
+ if (!aes) {
+ aes = mem_zalloc(sizeof(*aes), destructor);
+ if (!aes)
+ return ENOMEM;
+
+ }
+ else
+ memset(aes,0,sizeof(*aes));
+
+ aes->enc = twolame_init();
+ if (!aes->enc) {
+ warning("MPA enc create failed\n");
+ mem_deref(aes);
+ return ENOMEM;
+ }
+#ifdef DEBUG
+ debug("MPA enc created %s\n",fmtp);
+#endif
+ aes->channels = ac->ch;
+ aes->timestamp = rand_u32();
+
+ prm.samplerate = 48000;
+ prm.bitrate = 128000;
+ prm.layer = 2;
+ prm.mode = SINGLE_CHANNEL;
+ mpa_decode_fmtp(&prm, fmtp);
+ aes->samplerate = prm.samplerate;
+
+ result = 0;
+#ifdef DEBUG
+ result |= twolame_set_verbosity(aes->enc, 5);
+#else
+ result |= twolame_set_verbosity(aes->enc, 0);
+#endif
+
+ result |= twolame_set_mode(aes->enc,
+ prm.mode == SINGLE_CHANNEL ? TWOLAME_MONO :
+ prm.mode == DUAL_CHANNEL ? TWOLAME_DUAL_CHANNEL :
+ prm.mode == JOINT_STEREO ? TWOLAME_JOINT_STEREO :
+ prm.mode == STEREO ? TWOLAME_STEREO : TWOLAME_AUTO_MODE);
+ result |= twolame_set_version(aes->enc,
+ prm.samplerate < 32000 ? TWOLAME_MPEG2 : TWOLAME_MPEG1);
+ result |= twolame_set_bitrate(aes->enc, prm.bitrate/1000);
+ result |= twolame_set_in_samplerate(aes->enc, prm.samplerate);
+ result |= twolame_set_out_samplerate(aes->enc, prm.samplerate);
+ result |= twolame_set_num_channels(aes->enc, 2);
+ if (result!=0) {
+ warning("MPA enc set failed\n");
+ err=EINVAL;
+ goto out;
+ }
+
+ result = twolame_init_params(aes->enc);
+ if (result!=0) {
+ warning("MPA enc init params failed\n");
+ err=EINVAL;
+ goto out;
+ }
+#ifdef DEBUG
+ twolame_print_config(aes->enc);
+#endif
+ if (prm.samplerate != MPA_IORATE) {
+ aes->resampler = speex_resampler_init(2, MPA_IORATE,
+ prm.samplerate, 3, &result);
+ if (result!=RESAMPLER_ERR_SUCCESS) {
+ warning("MPA enc resampler init failed %d\n",result);
+ err=EINVAL;
+ goto out;
+ }
+
+ }
+ else
+ aes->resampler = NULL;
+
+out:
+ if (err)
+ mem_deref(aes);
+ else
+ *aesp = aes;
+
+ return err;
+}
+
+
+int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int n;
+ spx_uint32_t intermediate_len,in_len;
+
+ if (!aes || !buf || !len || !sampv)
+ return EINVAL;
+
+ if (aes->resampler) {
+ in_len = (uint32_t)sampc/2;
+ intermediate_len = sizeof(aes->intermediate_buffer)
+ / sizeof(aes->intermediate_buffer[0]);
+ n=speex_resampler_process_interleaved_int(aes->resampler,
+ sampv, &in_len, aes->intermediate_buffer,
+ &intermediate_len);
+ if (n!=RESAMPLER_ERR_SUCCESS || in_len != sampc/2) {
+ warning("MPA enc downsample error: %s %d %d\n",
+ strerror(n), in_len, sampc/2);
+ return EPROTO;
+ }
+ n = twolame_encode_buffer_interleaved(aes->enc,
+ aes->intermediate_buffer, intermediate_len,
+ buf+4, (int)(*len)-4);
+#ifdef DEBUG
+ debug("MPA enc %d %d %d %d %d %p\n",intermediate_len,sampc,
+ aes->channels,*len,n,aes->enc);
+#endif
+ }
+ else {
+ n = twolame_encode_buffer_interleaved(aes->enc,
+ sampv, (int)(sampc/2),
+ buf+4, (int)(*len)-4);
+#ifdef DEBUG
+ debug("MPA enc %d %d %d %d\n",sampc,
+ aes->channels,*len,n);
+#endif
+ }
+ if (n < 0) {
+ warning("MPA enc error %s\n", strerror((int)n));
+ return EPROTO;
+ }
+
+ if (n > 0) {
+ *(uint32_t*)(void *)buf = 0;
+ *len = n+4;
+ }
+ else
+ *len = 0;
+
+#ifdef DEBUG
+ debug("MPA enc done %d %d %d %d %p\n",sampc,aes->channels,
+ *len,n,aes->enc);
+#endif
+ aes->timestamp += ((MPA_FRAMESIZE*MPA_RTPRATE)<<4) / aes->samplerate;
+
+ return 0x00010000 | ((aes->timestamp>>4) & 0x0000ffff);
+}
+
diff --git a/modules/mpa/module.mk b/modules/mpa/module.mk
new file mode 100644
index 0000000..b060f12
--- /dev/null
+++ b/modules/mpa/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2016 Symonics GmbH
+#
+
+MOD := mpa
+$(MOD)_SRCS += mpa.c
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_SRCS += encode.c
+$(MOD)_LFLAGS += -ltwolame -lmpg123 -lspeexdsp -lm
+
+include mk/mod.mk
diff --git a/modules/mpa/mpa.c b/modules/mpa/mpa.c
new file mode 100644
index 0000000..d36e07d
--- /dev/null
+++ b/modules/mpa/mpa.c
@@ -0,0 +1,205 @@
+/**
+ * @file mpa.c mpa Audio Codec
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <ctype.h>
+#include <string.h>
+#include "mpa.h"
+#include <mpg123.h>
+
+/**
+ * @defgroup mpa mpa
+ *
+ * The mpa audio codec
+ *
+ * Supported version:
+ * libmpg123 1.16.0 or later
+ * libtwolame 0.3.13 or later
+ *
+ * References:
+ *
+ * RFC 2250 RTP Payload Format for the mpa Speech and Audio Codec
+ *
+ */
+
+/*
+4.1.17. Registration of MIME media type audio/MPA
+
+ MIME media type name: audio
+
+ MIME subtype name: MPA (MPEG audio)
+
+ Required parameters: None
+
+ Optional parameters:
+ layer: which layer of MPEG audio encoding; permissible values
+ are 1, 2, 3.
+
+ samplerate: the rate at which audio is sampled. MPEG-1 audio
+ supports sampling rates of 32, 44.1, and 48 kHz; MPEG-2
+ supports sampling rates of 16, 22.05 and 24 kHz. This parameter
+ is separate from the RTP timestamp clock rate which is always
+ 90000 Hz for MPA.
+
+ mode: permissible values are "stereo", "joint_stereo",
+ "single_channel", "dual_channel". The "channels" parameter
+ does not apply to MPA. It is undefined to put a number of
+ channels in the SDP rtpmap attribute for MPA.
+
+ bitrate: the data rate for the audio bit stream.
+
+ ptime: RECOMMENDED duration of each packet in milliseconds.
+
+ maxptime: maximum duration of each packet in milliseconds.
+
+ Parameters which are omitted are left to the encoder to choose
+ based on the session bandwidth, configuration information, or
+ other constraints. The selected layer as well as the sampling
+ rate and mode are indicated in the payload so receivers can
+ process the data without these parameters being specified
+ externally.
+
+ Encoding considerations:
+ This type is only defined for transfer via RTP [RFC 3550].
+
+ Security considerations: See Section 5 of RFC 3555
+
+ Interoperability considerations: none
+
+ Published specification: RFC 3551
+
+ Applications which use this media type:
+ Audio and video streaming and conferencing tools.
+
+*/
+
+
+static struct aucodec mpa = {
+ .pt = "14",
+ .name = "MPA",
+ .srate = MPA_IORATE,
+ .crate = MPA_RTPRATE,
+ .ch = 1,
+/* MPA does not expect channels count, even those it is stereo */
+ .fmtp = "layer=2",
+ .encupdh = mpa_encode_update,
+ .ench = mpa_encode_frm,
+ .decupdh = mpa_decode_update,
+ .dech = mpa_decode_frm,
+};
+
+
+static int module_init(void)
+{
+ struct conf *conf = conf_cur();
+ uint32_t value;
+ static char fmtp[256];
+ static char mode[30];
+ int res;
+
+ /** generate fmtp string based on config file */
+
+ strcpy(mode,mpa.fmtp);
+
+ if (0 == conf_get_u32(conf, "mpa_bitrate", &value)) {
+ if (value<8000 || value>384000) {
+ warning("MPA bitrate between 8000 and "
+ "384000 are allowed.\n");
+ return -1;
+ }
+
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; bitrate=%d", value);
+ }
+ if (0 == conf_get_u32(conf, "mpa_layer", &value)) {
+ if (value<1 || value>4) {
+ warning("MPA layer 1, 2 or 3 are allowed.");
+ return -1;
+ }
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; layer=%d", value);
+ }
+ if (0 == conf_get_u32(conf, "mpa_samplerate", &value)) {
+ switch (value) {
+ case 32000:
+ case 44100:
+ case 48000:
+ case 16000:
+ case 22050:
+ case 24000:
+ break;
+ default:
+ warning("MPA samplerates of 16, 22.05, 24, 32, "
+ "44.1, and 48 kHz are allowed.\n");
+ return -1;
+ }
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; samplerate=%d", value);
+ }
+ if (0 == conf_get_str(conf, "mpa_mode", mode, sizeof(mode))) {
+ char *p = mode;
+ while (*p) {
+ *p = tolower(*p);
+ ++p;
+ }
+
+ if (strcmp(mode,"stereo")
+ && strcmp(mode,"joint_stereo")
+ && strcmp(mode,"single_channel")
+ && strcmp(mode,"dual_channel")) {
+ warning("MPA mode: Permissible values are stereo, "
+ "joint_stereo, single_channel, dual_channel.\n");
+ return -1;
+ }
+
+ (void)re_snprintf(fmtp+strlen(fmtp),
+ sizeof(fmtp)-strlen(fmtp),
+ "; mode=%s", mode);
+ }
+
+ if (fmtp[0]==';' && fmtp[1]==' ')
+ mpa.fmtp = fmtp+2;
+ else
+ mpa.fmtp = fmtp;
+
+ /* init decoder library */
+ res = mpg123_init();
+ if (res != MPG123_OK) {
+ warning("MPA libmpg123 init error %s\n",
+ mpg123_plain_strerror(res));
+ return -1;
+ }
+
+ aucodec_register(baresip_aucodecl(), &mpa);
+
+#ifdef DEBUG
+ info("MPA init with %s\n",mpa.fmtp);
+#endif
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&mpa);
+
+ mpg123_exit();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(mpa) = {
+ "MPA",
+ "audio codec",
+ module_init,
+ module_close,
+};
+
diff --git a/modules/mpa/mpa.h b/modules/mpa/mpa.h
new file mode 100644
index 0000000..0db2528
--- /dev/null
+++ b/modules/mpa/mpa.h
@@ -0,0 +1,37 @@
+/**
+ * @file mpa.h Private mpa Interface
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#define MPA_FRAMESIZE 1152
+#define MPA_IORATE 48000
+#define MPA_RTPRATE 90000
+#define BARESIP_FRAMESIZE (MPA_IORATE/50*2)
+
+#undef DEBUG
+
+struct mpa_param {
+ unsigned samplerate;
+ unsigned bitrate;
+ unsigned layer;
+ enum { AUTO=0, STEREO, JOINT_STEREO, SINGLE_CHANNEL, DUAL_CHANNEL }
+ mode;
+};
+
+
+/* Encode */
+int mpa_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+int mpa_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc);
+
+
+/* Decode */
+int mpa_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp);
+int mpa_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len);
+
+/* SDP */
+void mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp);
diff --git a/modules/mpa/sdp.c b/modules/mpa/sdp.c
new file mode 100644
index 0000000..a4e432c
--- /dev/null
+++ b/modules/mpa/sdp.c
@@ -0,0 +1,55 @@
+/**
+ * @file mpa/sdp.c mpa SDP Functions
+ *
+ * Copyright (C) 2016 Symonics GmbH
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <string.h>
+#include "mpa.h"
+
+
+static void assign_if (uint32_t *v, const struct pl *pl,
+ uint32_t min, uint32_t max)
+{
+ const uint32_t val = pl_u32(pl);
+
+ if (val < min || val > max)
+ return;
+
+ *v = val;
+}
+
+
+void mpa_decode_fmtp(struct mpa_param *prm, const char *fmtp)
+{
+ struct pl pl, val;
+
+ if (!prm || !fmtp)
+ return;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "bitrate", &val))
+ assign_if (&prm->bitrate, &val, 8000, 384000);
+
+ if (fmt_param_get(&pl, "samplerate", &val))
+ assign_if (&prm->samplerate, &val, 16000, 48000);
+
+ if (fmt_param_get(&pl, "layer", &val))
+ assign_if (&prm->layer, &val, 1, 3);
+
+ if (fmt_param_get(&pl, "mode", &val)) {
+
+ if (!strncmp("stereo",val.p,val.l))
+ prm->mode = STEREO;
+ else if (!strncmp("joint_stereo",val.p,val.l))
+ prm->mode = JOINT_STEREO;
+ else if (!strncmp("single_channel",val.p,val.l))
+ prm->mode = SINGLE_CHANNEL;
+ else if (!strncmp("dual_channel",val.p,val.l))
+ prm->mode = DUAL_CHANNEL;
+ }
+}
+
diff --git a/modules/mqtt/README.md b/modules/mqtt/README.md
new file mode 100644
index 0000000..6a86b84
--- /dev/null
+++ b/modules/mqtt/README.md
@@ -0,0 +1,59 @@
+README
+------
+
+
+This module implements an MQTT (Message Queue Telemetry Transport) client
+for publishing and subscribing to topics.
+
+
+The module is using libmosquitto
+
+
+Starting the MQTT broker:
+
+```
+$ /usr/local/sbin/mosquitto -v
+```
+
+
+Subscribing to all topics:
+
+```
+$ mosquitto_sub -t /baresip/+
+```
+
+
+Publishing to the topic:
+
+```
+$ mosquitto_pub -t /baresip/xxx -m foo=42
+```
+
+
+## Topic patterns
+
+(Outgoing direction is from baresip mqtt module to broker,
+ incoming direction is from broker to baresip mqtt module)
+
+* /baresip/event Outgoing events from ua_event
+* /baresip/command Incoming long command request
+* /baresip/command_resp Outgoing long command response
+
+
+## Examples
+
+```
+/baresip/event sip:aeh@iptel.org,REGISTERING
+/baresip/event sip:aeh@iptel.org,REGISTER_OK
+/baresip/event sip:aeh@iptel.org,SHUTDOWN
+```
+
+```
+mosquitto_pub -t /baresip/command -m "/dial music"
+
+/baresip/command /dial music
+/baresip/command_resp (null)
+/baresip/event sip:aeh@iptel.org,CALL_ESTABLISHED
+/baresip/event sip:aeh@iptel.org,CALL_CLOSED
+```
+
diff --git a/modules/mqtt/module.mk b/modules/mqtt/module.mk
new file mode 100644
index 0000000..cb4c379
--- /dev/null
+++ b/modules/mqtt/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := mqtt
+$(MOD)_SRCS += mqtt.c
+$(MOD)_SRCS += publish.c
+$(MOD)_SRCS += subscribe.c
+$(MOD)_LFLAGS += -lmosquitto
+$(MOD)_CFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/mqtt/mqtt.c b/modules/mqtt/mqtt.c
new file mode 100644
index 0000000..a927959
--- /dev/null
+++ b/modules/mqtt/mqtt.c
@@ -0,0 +1,157 @@
+/**
+ * @file mqtt.c Message Queue Telemetry Transport (MQTT) client
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <mosquitto.h>
+#include <re.h>
+#include <baresip.h>
+#include "mqtt.h"
+
+
+static char broker_host[256] = "127.0.0.1";
+static uint32_t broker_port = 1883;
+
+static struct mqtt s_mqtt;
+
+
+static void fd_handler(int flags, void *arg)
+{
+ struct mqtt *mqtt = arg;
+
+ mosquitto_loop_read(mqtt->mosq, 1);
+
+ mosquitto_loop_write(mqtt->mosq, 1);
+}
+
+
+/* XXX: use mosquitto_socket and fd_listen instead? */
+static void tmr_handler(void *data)
+{
+ struct mqtt *mqtt = data;
+ int ret;
+
+ tmr_start(&mqtt->tmr, 500, tmr_handler, mqtt);
+
+ ret = mosquitto_loop_misc(mqtt->mosq);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ warning("mqtt: error in loop (%s)\n", mosquitto_strerror(ret));
+ }
+}
+
+
+/*
+ * This is called when the broker sends a CONNACK message
+ * in response to a connection.
+ */
+static void connect_callback(struct mosquitto *mosq, void *obj, int result)
+{
+ struct mqtt *mqtt = obj;
+ int err;
+ (void)mqtt;
+
+ if (result != MOSQ_ERR_SUCCESS) {
+ warning("mqtt: could not connect to broker (%s)\n",
+ mosquitto_strerror(result));
+ return;
+ }
+
+ info("mqtt: connected to broker at %s:%d\n",
+ broker_host, broker_port);
+
+ err = mqtt_subscribe_start(mqtt);
+ if (err) {
+ warning("mqtt: subscribe_init failed (%m)\n", err);
+ }
+}
+
+
+static int module_init(void)
+{
+ const int keepalive = 60;
+ int ret;
+ int err = 0;
+
+ tmr_init(&s_mqtt.tmr);
+
+ mosquitto_lib_init();
+
+ conf_get_str(conf_cur(), "mqtt_broker_host",
+ broker_host, sizeof(broker_host));
+ conf_get_u32(conf_cur(), "mqtt_broker_port", &broker_port);
+
+ s_mqtt.mosq = mosquitto_new("baresip", true, &s_mqtt);
+ if (!s_mqtt.mosq) {
+ warning("mqtt: failed to create client instance\n");
+ return ENOMEM;
+ }
+
+ err = mqtt_subscribe_init(&s_mqtt);
+ if (err)
+ return err;
+
+ mosquitto_connect_callback_set(s_mqtt.mosq, connect_callback);
+
+ ret = mosquitto_connect(s_mqtt.mosq, broker_host, broker_port,
+ keepalive);
+ if (ret != MOSQ_ERR_SUCCESS) {
+
+ err = ret == MOSQ_ERR_ERRNO ? errno : EIO;
+
+ warning("mqtt: failed to connect to %s:%d (%s)\n",
+ broker_host, broker_port,
+ mosquitto_strerror(ret));
+ return err;
+ }
+
+ tmr_start(&s_mqtt.tmr, 1, tmr_handler, &s_mqtt);
+
+ err = mqtt_publish_init(&s_mqtt);
+ if (err)
+ return err;
+
+ s_mqtt.fd = mosquitto_socket(s_mqtt.mosq);
+
+ err = fd_listen(s_mqtt.fd, FD_READ, fd_handler, &s_mqtt);
+ if (err)
+ return err;
+
+ info("mqtt: module loaded\n");
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ fd_close(s_mqtt.fd);
+
+ mqtt_publish_close();
+
+ mqtt_subscribe_close();
+
+ tmr_cancel(&s_mqtt.tmr);
+
+ if (s_mqtt.mosq) {
+
+ mosquitto_disconnect(s_mqtt.mosq);
+
+ mosquitto_destroy(s_mqtt.mosq);
+ s_mqtt.mosq = NULL;
+ }
+
+ mosquitto_lib_cleanup();
+
+ info("mqtt: module unloaded\n");
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(mqtt) = {
+ "mqtt",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/mqtt/mqtt.h b/modules/mqtt/mqtt.h
new file mode 100644
index 0000000..e79fb6e
--- /dev/null
+++ b/modules/mqtt/mqtt.h
@@ -0,0 +1,26 @@
+
+
+struct mqtt {
+ struct mosquitto *mosq;
+ struct tmr tmr;
+ int fd;
+};
+
+
+/*
+ * Subscribe direction (incoming)
+ */
+
+int mqtt_subscribe_init(struct mqtt *mqtt);
+int mqtt_subscribe_start(struct mqtt *mqtt);
+void mqtt_subscribe_close(void);
+
+
+/*
+ * Publish direction (outgoing)
+ */
+
+int mqtt_publish_init(struct mqtt *mqtt);
+void mqtt_publish_close(void);
+int mqtt_publish_message(struct mqtt *mqtt, const char *topic,
+ const char *fmt, ...);
diff --git a/modules/mqtt/publish.c b/modules/mqtt/publish.c
new file mode 100644
index 0000000..524b0a2
--- /dev/null
+++ b/modules/mqtt/publish.c
@@ -0,0 +1,104 @@
+/**
+ * @file publish.c MQTT client -- publish
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <mosquitto.h>
+#include <re.h>
+#include <baresip.h>
+#include "mqtt.h"
+
+
+/*
+ * This file contains functions for sending outgoing messages
+ * from baresip to broker (publish)
+ */
+
+
+/*
+ * Relay UA events as publish messages to the Broker
+ *
+ * XXX: move JSON encoding to baresip core
+ */
+static void ua_event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ struct mqtt *mqtt = arg;
+ struct odict *od = NULL;
+ int err;
+
+ err = odict_alloc(&od, 8);
+ if (err)
+ return;
+
+ err = event_encode_dict(od, ua, ev, call, prm);
+ if (err)
+ goto out;
+
+ err = mqtt_publish_message(mqtt, "/baresip/event", "%H",
+ json_encode_odict, od);
+ if (err) {
+ warning("mqtt: failed to publish message (%m)\n", err);
+ goto out;
+ }
+
+ out:
+ mem_deref(od);
+}
+
+
+int mqtt_publish_message(struct mqtt *mqtt, const char *topic,
+ const char *fmt, ...)
+{
+ char *message;
+ va_list ap;
+ int ret;
+ int err = 0;
+
+ if (!mqtt || !topic || !fmt)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = re_vsdprintf(&message, fmt, ap);
+ va_end(ap);
+
+ if (err)
+ return err;
+
+ ret = mosquitto_publish(mqtt->mosq,
+ NULL,
+ topic,
+ (int)str_len(message),
+ message,
+ 0,
+ false);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ warning("mqtt: failed to publish (%s)\n",
+ mosquitto_strerror(ret));
+ err = EINVAL;
+ goto out;
+ }
+
+ out:
+ mem_deref(message);
+ return err;
+}
+
+
+int mqtt_publish_init(struct mqtt *mqtt)
+{
+ int err;
+
+ err = uag_event_register(ua_event_handler, mqtt);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+void mqtt_publish_close(void)
+{
+ uag_event_unregister(&ua_event_handler);
+}
diff --git a/modules/mqtt/subscribe.c b/modules/mqtt/subscribe.c
new file mode 100644
index 0000000..d36ece2
--- /dev/null
+++ b/modules/mqtt/subscribe.c
@@ -0,0 +1,146 @@
+/**
+ * @file subscribe.c MQTT client -- subscribe
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <mosquitto.h>
+#include <re.h>
+#include <baresip.h>
+#include "mqtt.h"
+
+
+static const char *subscription_pattern = "/baresip/+";
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ struct mbuf *mb = arg;
+
+ return mbuf_write_mem(mb, (void *)p, size);
+}
+
+
+static void handle_command(struct mqtt *mqtt, const struct pl *msg)
+{
+ struct mbuf *resp = mbuf_alloc(1024);
+ struct re_printf pf = {print_handler, resp};
+ struct odict *od = NULL;
+ const struct odict_entry *oe_cmd, *oe_prm, *oe_tok;
+ char buf[256], resp_topic[256];
+ int err;
+
+ /* XXX: add transaction ID ? */
+
+ err = json_decode_odict(&od, 32, msg->p, msg->l, 16);
+ if (err) {
+ warning("mqtt: failed to decode JSON with %zu bytes (%m)\n",
+ msg->l, err);
+ return;
+ }
+
+ oe_cmd = odict_lookup(od, "command");
+ oe_prm = odict_lookup(od, "params");
+ oe_tok = odict_lookup(od, "token");
+ if (!oe_cmd) {
+ warning("mqtt: missing json entries\n");
+ goto out;
+ }
+
+ debug("mqtt: handle_command: cmd='%s', token='%s'\n",
+ oe_cmd ? oe_cmd->u.str : "",
+ oe_tok ? oe_tok->u.str : "");
+
+ re_snprintf(buf, sizeof(buf), "%s%s%s",
+ oe_cmd->u.str,
+ oe_prm ? " " : "",
+ oe_prm ? oe_prm->u.str : "");
+
+ /* Relay message to long commands */
+ err = cmd_process_long(baresip_commands(),
+ buf,
+ str_len(buf),
+ &pf, NULL);
+ if (err) {
+ warning("mqtt: error processing command (%m)\n", err);
+ }
+
+ /* NOTE: the command will now write the response
+ to the resp mbuf, send it back to broker */
+
+ re_snprintf(resp_topic, sizeof(resp_topic),
+ "/baresip/command_resp/%s",
+ oe_tok ? oe_tok->u.str : "nil");
+
+ err = mqtt_publish_message(mqtt, resp_topic,
+ "%b",
+ resp->buf, resp->end);
+ if (err) {
+ warning("mqtt: failed to publish message (%m)\n", err);
+ goto out;
+ }
+
+ out:
+ mem_deref(resp);
+ mem_deref(od);
+}
+
+
+/*
+ * This is called when a message is received from the broker.
+ */
+static void message_callback(struct mosquitto *mosq, void *obj,
+ const struct mosquitto_message *message)
+{
+ struct mqtt *mqtt = obj;
+ struct pl msg;
+ bool match = false;
+
+ info("mqtt: got message '%b' for topic '%s'\n",
+ (char*) message->payload, (size_t)message->payloadlen,
+ message->topic);
+
+ msg.p = message->payload;
+ msg.l = message->payloadlen;
+
+ mosquitto_topic_matches_sub("/baresip/command", message->topic,
+ &match);
+ if (match) {
+ info("mqtt: got message for '%s' topic\n", message->topic);
+
+ handle_command(mqtt, &msg);
+ }
+}
+
+
+int mqtt_subscribe_init(struct mqtt *mqtt)
+{
+ if (!mqtt)
+ return EINVAL;
+
+ mosquitto_message_callback_set(mqtt->mosq, message_callback);
+
+ return 0;
+}
+
+
+int mqtt_subscribe_start(struct mqtt *mqtt)
+{
+ int ret;
+
+ ret = mosquitto_subscribe(mqtt->mosq, NULL, subscription_pattern, 0);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ warning("mqtt: failed to subscribe (%s)\n",
+ mosquitto_strerror(ret));
+ return EPROTO;
+ }
+
+ info("mqtt: subscribed to pattern '%s'\n", subscription_pattern);
+
+ return 0;
+}
+
+
+void mqtt_subscribe_close(void)
+{
+}
diff --git a/modules/mwi/module.mk b/modules/mwi/module.mk
new file mode 100644
index 0000000..f6d1bde
--- /dev/null
+++ b/modules/mwi/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := mwi
+$(MOD)_SRCS += mwi.c
+
+include mk/mod.mk
diff --git a/modules/mwi/mwi.c b/modules/mwi/mwi.c
new file mode 100644
index 0000000..4bae5f5
--- /dev/null
+++ b/modules/mwi/mwi.c
@@ -0,0 +1,225 @@
+/**
+ * @file mwi.c Message Waiting Indication (RFC 3842)
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup mwi mwi
+ *
+ * Message Waiting Indication
+ *
+ */
+
+
+struct mwi {
+ struct le le;
+ struct sipsub *sub;
+ struct ua *ua;
+ struct tmr tmr;
+ bool shutdown;
+};
+
+static struct tmr tmr;
+static struct list mwil;
+
+
+static void destructor(void *arg)
+{
+ struct mwi *mwi = arg;
+
+ tmr_cancel(&mwi->tmr);
+ list_unlink(&mwi->le);
+ mem_deref(mwi->sub);
+ mem_deref(mwi->ua);
+}
+
+
+static void deref_handler(void *arg)
+{
+ struct mwi *mwi = arg;
+ mem_deref(mwi);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static void notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct mwi *mwi = arg;
+
+ if (mbuf_get_left(msg->mb)) {
+ struct ui_sub *uis = baresip_uis();
+ ui_output(uis, "----- MWI for %s -----\n", ua_aor(mwi->ua));
+ ui_output(uis, "%b\n", mbuf_buf(msg->mb),
+ mbuf_get_left(msg->mb));
+ }
+
+ (void)sip_treply(NULL, sip, msg, 200, "OK");
+
+ if (mwi->shutdown)
+ mem_deref(mwi);
+}
+
+
+static void close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg)
+{
+ struct mwi *mwi = arg;
+ (void)substate;
+
+ info("mwi: subscription for %s closed: %s (%u %r)\n",
+ ua_aor(mwi->ua),
+ err ? strerror(err) : "",
+ err ? 0 : msg->scode,
+ err ? 0 : &msg->reason);
+
+ mem_deref(mwi);
+}
+
+
+static int mwi_subscribe(struct ua *ua)
+{
+ const char *routev[1];
+ struct mwi *mwi;
+ int err;
+
+ mwi = mem_zalloc(sizeof(*mwi), destructor);
+ if (!mwi)
+ return ENOMEM;
+
+ list_append(&mwil, &mwi->le, mwi);
+ mwi->ua = mem_ref(ua);
+
+ routev[0] = ua_outbound(ua);
+
+ info("mwi: subscribing to messages for %s\n", ua_aor(ua));
+
+ err = sipevent_subscribe(&mwi->sub, uag_sipevent_sock(), ua_aor(ua),
+ NULL, ua_aor(ua), "message-summary", NULL,
+ 600, ua_cuser(ua),
+ routev, routev[0] ? 1 : 0,
+ auth_handler, ua_account(ua), true, NULL,
+ notify_handler, close_handler, mwi,
+ "Accept:"
+ " application/simple-message-summary\r\n");
+ if (err) {
+ warning("mwi: subscribe ERROR: %m\n", err);
+ }
+
+ if (err)
+ mem_deref(mwi);
+
+ return err;
+}
+
+
+static struct mwi *mwi_find(const struct ua *ua)
+{
+ struct le *le;
+
+ for (le = mwil.head; le; le = le->next) {
+
+ struct mwi *mwi = le->data;
+
+ if (mwi->ua == ua)
+ return mwi;
+ }
+
+ return NULL;
+}
+
+
+static void ua_event_handler(struct ua *ua,
+ enum ua_event ev,
+ struct call *call,
+ const char *prm,
+ void *arg )
+{
+ (void)call;
+ (void)prm;
+ (void)arg;
+
+ if (ev == UA_EVENT_REGISTER_OK) {
+
+ if (!mwi_find(ua))
+ mwi_subscribe(ua);
+ }
+ else if (ev == UA_EVENT_SHUTDOWN) {
+
+ struct le *le;
+
+ info("mwi: shutdown\n");
+
+ le = list_head(&mwil);
+ while (le) {
+ struct mwi *mwi = le->data;
+ le = le->next;
+
+ mwi->shutdown = true;
+
+ if (mwi->sub) {
+ mwi->sub = mem_deref(mwi->sub);
+ tmr_start(&mwi->tmr, 500, deref_handler, mwi);
+ }
+ else
+ mem_deref(mwi);
+ }
+ }
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct le *le;
+
+ (void)arg;
+
+ for (le = list_head(uag_list()); le; le = le->next) {
+ struct ua *ua = le->data;
+ struct account *acc = ua_account(ua);
+
+ if (account_regint(acc) == 0) {
+ mwi_subscribe(ua);
+ }
+ }
+}
+
+
+static int module_init(void)
+{
+ list_init(&mwil);
+ tmr_start(&tmr, 1, tmr_handler, 0);
+
+ return uag_event_register(ua_event_handler, NULL);
+}
+
+
+static int module_close(void)
+{
+ uag_event_unregister(ua_event_handler);
+ tmr_cancel(&tmr);
+ list_flush(&mwil);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(mwi) = {
+ "mwi",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/natbd/module.mk b/modules/natbd/module.mk
new file mode 100644
index 0000000..d6da3f9
--- /dev/null
+++ b/modules/natbd/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := natbd
+$(MOD)_SRCS += natbd.c
+
+include mk/mod.mk
diff --git a/modules/natbd/natbd.c b/modules/natbd/natbd.c
new file mode 100644
index 0000000..2b2efff
--- /dev/null
+++ b/modules/natbd/natbd.c
@@ -0,0 +1,509 @@
+/**
+ * @file natbd.c NAT Behavior Discovery Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup natbd natbd
+ *
+ * NAT Behavior Discovery Using STUN (RFC 5780)
+ *
+ * This module is only for diagnostics purposes and does not affect
+ * the main SIP client. It uses the NATBD api in libre to detect the
+ * NAT Behaviour, by sending STUN packets to a STUN server. Both
+ * protocols UDP and TCP are supported.
+ *
+ * The STUN server used must be compliant with RFC 5780
+ */
+
+
+struct natbd {
+ struct nat_hairpinning *nh;
+ struct nat_filtering *nf;
+ struct nat_lifetime *nl;
+ struct nat_mapping *nm;
+ struct nat_genalg *ga;
+ struct stun_dns *dns;
+ struct sa stun_srv;
+ struct tmr tmr;
+ char host[256];
+ uint16_t port;
+ uint32_t interval;
+ bool terminated;
+ int proto;
+ int res_hp;
+ enum nat_type res_nm;
+ enum nat_type res_nf;
+ struct nat_lifetime_interval res_nl;
+ uint32_t n_nl;
+ int status_ga;
+};
+
+static struct natbd *natbdv[2];
+
+
+static const char *hairpinning_str(int res_hp)
+{
+ switch (res_hp) {
+
+ case -1: return "Unknown";
+ case 0: return "Not Supported";
+ default: return "Supported";
+ }
+}
+
+
+static const char *genalg_str(int status)
+{
+ switch (status) {
+
+ case -1: return "Not Detected";
+ case 0: return "Unknown";
+ case 1: return "Detected";
+ default: return "???";
+ }
+}
+
+
+static int natbd_status(struct re_printf *pf, void *arg)
+{
+ const struct natbd *natbd = arg;
+ int err;
+
+ if (!pf || !natbd)
+ return 0;
+
+ err = re_hprintf(pf, "NAT Binding Discovery (using %s:%J)\n",
+ net_proto2name(natbd->proto),
+ &natbd->stun_srv);
+ err |= re_hprintf(pf, " Hairpinning: %s\n",
+ hairpinning_str(natbd->res_hp));
+ err |= re_hprintf(pf, " Mapping: %s\n",
+ nat_type_str(natbd->res_nm));
+ if (natbd->proto == IPPROTO_UDP) {
+ err |= re_hprintf(pf, " Filtering: %s\n",
+ nat_type_str(natbd->res_nf));
+ err |= re_hprintf(pf, " Lifetime: min=%u cur=%u max=%u"
+ " (%u probes)\n", natbd->res_nl.min,
+ natbd->res_nl.cur, natbd->res_nl.max,
+ natbd->n_nl);
+ }
+ err |= re_hprintf(pf, " Generic ALG: %s\n",
+ genalg_str(natbd->status_ga));
+
+ return err;
+}
+
+
+static void nat_hairpinning_handler(int err, bool supported, void *arg)
+{
+ struct natbd *natbd = arg;
+ const int res_hp = (0 == err) ? supported : -1;
+
+ if (natbd->terminated)
+ return;
+
+ if (res_hp != natbd->res_hp) {
+ info("NAT Hairpinning %s changed from (%s) to (%s)\n",
+ net_proto2name(natbd->proto),
+ hairpinning_str(natbd->res_hp),
+ hairpinning_str(res_hp));
+ }
+
+ natbd->res_hp = res_hp;
+
+ natbd->nh = mem_deref(natbd->nh);
+}
+
+
+static void nat_mapping_handler(int err, enum nat_type type, void *arg)
+{
+ struct natbd *natbd = arg;
+
+ if (natbd->terminated)
+ return;
+
+ if (err) {
+ warning("natbd: NAT mapping failed (%m)\n", err);
+ goto out;
+ }
+
+ if (type != natbd->res_nm) {
+ info("NAT Mapping %s changed from (%s) to (%s)\n",
+ net_proto2name(natbd->proto),
+ nat_type_str(natbd->res_nm),
+ nat_type_str(type));
+ }
+
+ natbd->res_nm = type;
+
+ out:
+ natbd->nm = mem_deref(natbd->nm);
+}
+
+
+static void nat_filtering_handler(int err, enum nat_type type, void *arg)
+{
+ struct natbd *natbd = arg;
+
+ if (natbd->terminated)
+ return;
+
+ if (err) {
+ warning("natbd: NAT filtering failed (%m)\n", err);
+ goto out;
+ }
+
+ if (type != natbd->res_nf) {
+ info("NAT Filtering %s changed from (%s) to (%s)\n",
+ net_proto2name(natbd->proto),
+ nat_type_str(natbd->res_nf),
+ nat_type_str(type));
+ }
+
+ natbd->res_nf = type;
+
+ out:
+ natbd->nf = mem_deref(natbd->nf);
+}
+
+
+static void nat_lifetime_handler(int err,
+ const struct nat_lifetime_interval *interval,
+ void *arg)
+{
+ struct natbd *natbd = arg;
+
+ ++natbd->n_nl;
+
+ if (err) {
+ warning("natbd: nat_lifetime_handler: (%m)\n", err);
+ return;
+ }
+
+ natbd->res_nl = *interval;
+
+ info("NAT Binding lifetime for %s: min=%u cur=%u max=%u\n",
+ net_proto2name(natbd->proto),
+ interval->min, interval->cur, interval->max);
+}
+
+
+static void nat_genalg_handler(int err, uint16_t scode, const char *reason,
+ int status, const struct sa *map,
+ void *arg)
+{
+ struct natbd *natbd = arg;
+
+ (void)map;
+
+ if (natbd->terminated)
+ return;
+
+ if (err) {
+ warning("natbd: Generic ALG detection failed: %m\n", err);
+ goto out;
+ }
+ else if (scode) {
+ warning("natbd: Generic ALG detection failed: %u %s\n",
+ scode, reason);
+ goto out;
+ }
+
+ if (status != natbd->status_ga) {
+ info("Generic ALG for %s changed from (%s) to (%s)\n",
+ net_proto2name(natbd->proto),
+ genalg_str(natbd->status_ga),
+ genalg_str(status));
+ }
+
+ natbd->status_ga = status;
+
+ out:
+ natbd->ga = mem_deref(natbd->ga);
+}
+
+
+static void destructor(void *arg)
+{
+ struct natbd *natbd = arg;
+
+ natbd->terminated = true;
+
+ tmr_cancel(&natbd->tmr);
+ mem_deref(natbd->dns);
+ mem_deref(natbd->nh);
+ mem_deref(natbd->nm);
+ mem_deref(natbd->nf);
+ mem_deref(natbd->nl);
+ mem_deref(natbd->ga);
+}
+
+
+static int natbd_start(struct natbd *natbd)
+{
+ struct network *net = baresip_network();
+ int err = 0;
+
+ if (!natbd->nh) {
+ err |= nat_hairpinning_alloc(&natbd->nh, &natbd->stun_srv,
+ natbd->proto, NULL,
+ nat_hairpinning_handler, natbd);
+ err |= nat_hairpinning_start(natbd->nh);
+ if (err) {
+ warning("natbd: nat_hairpinning_start() failed (%m)\n",
+ err);
+ }
+ }
+
+ if (!natbd->nm) {
+ err |= nat_mapping_alloc(&natbd->nm,
+ net_laddr_af(net, net_af(net)),
+ &natbd->stun_srv, natbd->proto, NULL,
+ nat_mapping_handler, natbd);
+ err |= nat_mapping_start(natbd->nm);
+ if (err) {
+ warning("natbd: nat_mapping_start() failed (%m)\n",
+ err);
+ }
+ }
+
+ if (natbd->proto == IPPROTO_UDP) {
+
+ if (!natbd->nf) {
+ err |= nat_filtering_alloc(&natbd->nf,
+ &natbd->stun_srv, NULL,
+ nat_filtering_handler,
+ natbd);
+ err |= nat_filtering_start(natbd->nf);
+ if (err) {
+ warning("natbd: nat_filtering_start() (%m)\n",
+ err);
+ }
+ }
+ }
+
+ if (!natbd->ga) {
+ err |= nat_genalg_alloc(&natbd->ga, &natbd->stun_srv,
+ natbd->proto, NULL,
+ nat_genalg_handler, natbd);
+
+ if (err) {
+ warning("natbd: natbd_init: %m\n", err);
+ }
+ err |= nat_genalg_start(natbd->ga);
+ if (err) {
+ warning("natbd: nat_genalg_start() failed (%m)\n",
+ err);
+ }
+ }
+
+ return err;
+}
+
+
+static void timeout(void *arg)
+{
+ struct natbd *natbd = arg;
+
+ info("%H\n", natbd_status, natbd);
+
+ natbd_start(natbd);
+
+ tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd);
+}
+
+
+static void dns_handler(int err, const struct sa *addr, void *arg)
+{
+ struct natbd *natbd = arg;
+
+ if (err) {
+ warning("natbd: failed to resolve '%s' (%m)\n",
+ natbd->host, err);
+ goto out;
+ }
+
+ info("natbd: resolved STUN-server for %s -- %J\n",
+ net_proto2name(natbd->proto), addr);
+
+ sa_cpy(&natbd->stun_srv, addr);
+
+ natbd_start(natbd);
+
+ /* Lifetime discovery is a special test */
+ if (natbd->proto == IPPROTO_UDP) {
+
+ err = nat_lifetime_alloc(&natbd->nl, &natbd->stun_srv, 3,
+ NULL, nat_lifetime_handler, natbd);
+ err |= nat_lifetime_start(natbd->nl);
+ if (err) {
+ warning("natbd: nat_lifetime_start() failed (%m)\n",
+ err);
+ }
+ }
+
+ tmr_start(&natbd->tmr, natbd->interval * 1000, timeout, natbd);
+
+ out:
+ natbd->dns = mem_deref(natbd->dns);
+}
+
+
+static void timeout_init(void *arg)
+{
+ struct natbd *natbd = arg;
+ const char *proto_str;
+ int err = 0;
+
+ if (sa_isset(&natbd->stun_srv, SA_ALL)) {
+ dns_handler(0, &natbd->stun_srv, natbd);
+ return;
+ }
+
+ if (natbd->proto == IPPROTO_UDP)
+ proto_str = stun_proto_udp;
+ else if (natbd->proto == IPPROTO_TCP)
+ proto_str = stun_proto_tcp;
+ else {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ err = stun_server_discover(&natbd->dns, net_dnsc(baresip_network()),
+ stun_usage_binding,
+ proto_str, net_af(baresip_network()),
+ natbd->host, natbd->port,
+ dns_handler, natbd);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ warning("natbd: timeout_init: %m\n", err);
+ }
+}
+
+
+static int natbd_alloc(struct natbd **natbdp, uint32_t interval,
+ int proto, const char *server)
+{
+ struct pl host, port;
+ struct natbd *natbd;
+ int err = 0;
+
+ if (!natbdp || !interval || !proto || !server)
+ return EINVAL;
+
+ natbd = mem_zalloc(sizeof(*natbd), destructor);
+ if (!natbd)
+ return ENOMEM;
+
+ natbd->interval = interval;
+ natbd->proto = proto;
+ natbd->res_hp = -1;
+
+ if (0 == sa_decode(&natbd->stun_srv, server, str_len(server))) {
+ ;
+ }
+ else if (0 == re_regex(server, str_len(server), "[^:]+[:]*[^]*",
+ &host, NULL, &port)) {
+
+ pl_strcpy(&host, natbd->host, sizeof(natbd->host));
+ natbd->port = pl_u32(&port);
+ }
+ else {
+ warning("natbd: failed to decode natbd_server (%s)\n",
+ server);
+ err = EINVAL;
+ goto out;
+ }
+
+ tmr_start(&natbd->tmr, 1, timeout_init, natbd);
+
+ out:
+ if (err)
+ mem_deref(natbd);
+ else
+ *natbdp = natbd;
+
+ return err;
+}
+
+
+static int status(struct re_printf *pf, void *unused)
+{
+ size_t i;
+ int err = 0;
+
+ (void)unused;
+
+ for (i=0; i<ARRAY_SIZE(natbdv); i++) {
+
+ if (natbdv[i])
+ err |= natbd_status(pf, natbdv[i]);
+ }
+
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+ {"natbd", 'z', 0, "NAT status", status}
+};
+
+
+static int module_init(void)
+{
+ char server[256] = "";
+ uint32_t interval = 3600;
+ int err;
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ return err;
+
+ (void)conf_get_u32(conf_cur(), "natbd_interval", &interval);
+ (void)conf_get_str(conf_cur(), "natbd_server", server, sizeof(server));
+
+ if (!server[0]) {
+ warning("natbd: missing config 'natbd_server'\n");
+ return EINVAL;
+ }
+
+ info("natbd: Enable NAT Behavior Discovery using STUN server %s\n",
+ server);
+
+ err |= natbd_alloc(&natbdv[0], interval, IPPROTO_UDP, server);
+ err |= natbd_alloc(&natbdv[1], interval, IPPROTO_TCP, server);
+ if (err) {
+ warning("natbd: failed to allocate natbd state: %m\n", err);
+ }
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(natbdv); i++)
+ natbdv[i] = mem_deref(natbdv[i]);
+
+ cmd_unregister(baresip_commands(), cmdv);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(natbd) = {
+ "natbd",
+ "natbd",
+ module_init,
+ module_close,
+};
diff --git a/modules/natpmp/libnatpmp.c b/modules/natpmp/libnatpmp.c
new file mode 100644
index 0000000..7969007
--- /dev/null
+++ b/modules/natpmp/libnatpmp.c
@@ -0,0 +1,235 @@
+/**
+ * @file libnatpmp.c NAT-PMP Client library
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "libnatpmp.h"
+
+
+enum {
+ NATPMP_DELAY = 250,
+ NATPMP_MAXTX = 9,
+};
+
+struct natpmp_req {
+ struct natpmp_req **npp;
+ struct udp_sock *us;
+ struct tmr tmr;
+ struct mbuf *mb;
+ struct sa srv;
+ unsigned n;
+ natpmp_resp_h *resph;
+ void *arg;
+};
+
+
+static void completed(struct natpmp_req *np, int err,
+ const struct natpmp_resp *resp)
+{
+ natpmp_resp_h *resph = np->resph;
+ void *arg = np->arg;
+
+ tmr_cancel(&np->tmr);
+
+ if (np->npp) {
+ *np->npp = NULL;
+ np->npp = NULL;
+ }
+
+ np->resph = NULL;
+
+ /* must be destroyed before calling handler */
+ mem_deref(np);
+
+ if (resph)
+ resph(err, resp, arg);
+}
+
+
+static void destructor(void *arg)
+{
+ struct natpmp_req *np = arg;
+
+ tmr_cancel(&np->tmr);
+ mem_deref(np->us);
+ mem_deref(np->mb);
+}
+
+
+static void timeout(void *arg)
+{
+ struct natpmp_req *np = arg;
+ int err;
+
+ if (np->n > NATPMP_MAXTX) {
+ completed(np, ETIMEDOUT, NULL);
+ return;
+ }
+
+ tmr_start(&np->tmr, NATPMP_DELAY<<np->n, timeout, arg);
+
+#if 1
+ debug("natpmp: {n=%u} tx %u bytes\n", np->n, np->mb->end);
+#endif
+
+ np->n++;
+
+ np->mb->pos = 0;
+ err = udp_send(np->us, &np->srv, np->mb);
+ if (err) {
+ completed(np, err, NULL);
+ }
+}
+
+
+static int resp_decode(struct natpmp_resp *resp, struct mbuf *mb)
+{
+ resp->vers = mbuf_read_u8(mb);
+ resp->op = mbuf_read_u8(mb);
+ resp->result = ntohs(mbuf_read_u16(mb));
+ resp->epoch = ntohl(mbuf_read_u32(mb));
+
+ if (!(resp->op & 0x80))
+ return EPROTO;
+ resp->op &= ~0x80;
+
+ switch (resp->op) {
+
+ case NATPMP_OP_EXTERNAL:
+ resp->u.ext_addr = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case NATPMP_OP_MAPPING_UDP:
+ case NATPMP_OP_MAPPING_TCP:
+ resp->u.map.int_port = ntohs(mbuf_read_u16(mb));
+ resp->u.map.ext_port = ntohs(mbuf_read_u16(mb));
+ resp->u.map.lifetime = ntohl(mbuf_read_u32(mb));
+ break;
+
+ default:
+ warning("natmap: unknown opcode %d\n", resp->op);
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct natpmp_req *np = arg;
+ struct natpmp_resp resp;
+
+ if (!sa_cmp(src, &np->srv, SA_ALL))
+ return;
+
+ if (resp_decode(&resp, mb))
+ return;
+
+ completed(np, 0, &resp);
+}
+
+
+static int natpmp_init(struct natpmp_req *np, const struct sa *srv,
+ uint8_t opcode, natpmp_resp_h *resph, void *arg)
+{
+ int err;
+
+ if (!np || !srv)
+ return EINVAL;
+
+ /* a new UDP socket for each NAT-PMP request */
+ err = udp_listen(&np->us, NULL, udp_recv, np);
+ if (err)
+ return err;
+
+ np->srv = *srv;
+ np->resph = resph;
+ np->arg = arg;
+
+ udp_connect(np->us, srv);
+
+ np->mb = mbuf_alloc(512);
+ if (!np->mb)
+ return ENOMEM;
+
+ err |= mbuf_write_u8(np->mb, NATPMP_VERSION);
+ err |= mbuf_write_u8(np->mb, opcode);
+
+ return err;
+}
+
+
+int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv,
+ natpmp_resp_h *resph, void *arg)
+{
+ struct natpmp_req *np;
+ int err;
+
+ np = mem_zalloc(sizeof(*np), destructor);
+ if (!np)
+ return ENOMEM;
+
+ err = natpmp_init(np, srv, NATPMP_OP_EXTERNAL, resph, arg);
+ if (err)
+ goto out;
+
+ timeout(np);
+
+ out:
+ if (err)
+ mem_deref(np);
+ else if (npp) {
+ np->npp = npp;
+ *npp = np;
+ }
+ else {
+ /* Destroy the transaction now */
+ mem_deref(np);
+ }
+
+ return err;
+}
+
+
+int natpmp_mapping_request(struct natpmp_req **npp, const struct sa *srv,
+ uint16_t int_port, uint16_t ext_port,
+ uint32_t lifetime, natpmp_resp_h *resph, void *arg)
+{
+ struct natpmp_req *np;
+ int err;
+
+ np = mem_zalloc(sizeof(*np), destructor);
+ if (!np)
+ return ENOMEM;
+
+ err = natpmp_init(np, srv, NATPMP_OP_MAPPING_UDP, resph, arg);
+ if (err)
+ goto out;
+
+ err |= mbuf_write_u16(np->mb, 0x0000);
+ err |= mbuf_write_u16(np->mb, htons(int_port));
+ err |= mbuf_write_u16(np->mb, htons(ext_port));
+ err |= mbuf_write_u32(np->mb, htonl(lifetime));
+ if (err)
+ goto out;
+
+ timeout(np);
+
+ out:
+ if (err)
+ mem_deref(np);
+ else if (npp) {
+ np->npp = npp;
+ *npp = np;
+ }
+ else {
+ /* Destroy the transaction now */
+ mem_deref(np);
+ }
+
+ return err;
+}
diff --git a/modules/natpmp/libnatpmp.h b/modules/natpmp/libnatpmp.h
new file mode 100644
index 0000000..d1cc33c
--- /dev/null
+++ b/modules/natpmp/libnatpmp.h
@@ -0,0 +1,53 @@
+/**
+ * @file libnatpmp.h Interface to NAT-PMP Client library
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ NATPMP_VERSION = 0,
+ NATPMP_PORT = 5351,
+};
+
+enum natpmp_op {
+ NATPMP_OP_EXTERNAL = 0,
+ NATPMP_OP_MAPPING_UDP = 1,
+ NATPMP_OP_MAPPING_TCP = 2,
+};
+
+enum natpmp_result {
+ NATPMP_SUCCESS = 0,
+ NATPMP_UNSUP_VERSION = 1,
+ NATPMP_REFUSED = 2,
+ NATPMP_NETWORK_FAILURE = 3,
+ NATPMP_OUT_OF_RESOURCES = 4,
+ NATPMP_UNSUP_OPCODE = 5
+};
+
+struct natpmp_resp {
+ uint8_t vers;
+ uint8_t op;
+ uint16_t result;
+ uint32_t epoch;
+
+ union {
+ uint32_t ext_addr;
+ struct {
+ uint16_t int_port;
+ uint16_t ext_port;
+ uint32_t lifetime;
+ } map;
+ } u;
+};
+
+struct natpmp_req;
+
+typedef void (natpmp_resp_h)(int err, const struct natpmp_resp *resp,
+ void *arg);
+
+int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv,
+ natpmp_resp_h *h, void *arg);
+int natpmp_mapping_request(struct natpmp_req **natpmpp, const struct sa *srv,
+ uint16_t int_port, uint16_t ext_port,
+ uint32_t lifetime, natpmp_resp_h *resph, void *arg);
diff --git a/modules/natpmp/module.mk b/modules/natpmp/module.mk
new file mode 100644
index 0000000..3087c77
--- /dev/null
+++ b/modules/natpmp/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := natpmp
+$(MOD)_SRCS += natpmp.c libnatpmp.c
+
+include mk/mod.mk
diff --git a/modules/natpmp/natpmp.c b/modules/natpmp/natpmp.c
new file mode 100644
index 0000000..45c7809
--- /dev/null
+++ b/modules/natpmp/natpmp.c
@@ -0,0 +1,387 @@
+/**
+ * @file natpmp.c NAT-PMP Module for Media NAT-traversal
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "libnatpmp.h"
+
+
+/**
+ * @defgroup natpmp natpmp
+ *
+ * NAT Port Mapping Protocol (NAT-PMP)
+ *
+ * https://tools.ietf.org/html/rfc6886
+ */
+
+enum {
+ LIFETIME = 300 /* seconds */
+};
+
+struct mnat_sess {
+ struct list medial;
+ mnat_estab_h *estabh;
+ void *arg;
+};
+
+struct mnat_media {
+ struct comp {
+ struct natpmp_req *natpmp;
+ struct mnat_media *media; /* pointer to parent */
+ struct tmr tmr;
+ uint16_t int_port;
+ uint32_t lifetime;
+ unsigned id;
+ bool granted;
+ } compv[2];
+ unsigned compc;
+
+ struct le le;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+};
+
+
+static struct mnat *mnat;
+static struct sa natpmp_srv, natpmp_extaddr;
+static struct natpmp_req *natpmp_ext;
+
+
+static void natpmp_resp_handler(int err, const struct natpmp_resp *resp,
+ void *arg);
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+ unsigned i;
+
+ list_unlink(&m->le);
+
+ for (i=0; i<m->compc; i++) {
+ struct comp *comp = &m->compv[i];
+
+ /* Destroy the mapping */
+ if (comp->granted) {
+ (void)natpmp_mapping_request(NULL, &natpmp_srv,
+ comp->int_port, 0, 0,
+ NULL, NULL);
+ }
+
+ tmr_cancel(&comp->tmr);
+ mem_deref(comp->natpmp);
+ }
+
+ mem_deref(m->sdpm);
+}
+
+
+static void complete(struct mnat_sess *sess, int err)
+{
+ mnat_estab_h *estabh = sess->estabh;
+
+ if (sess->estabh) {
+
+ sess->estabh = NULL;
+
+ estabh(err, 0, "done", sess->arg);
+ }
+}
+
+
+static bool all_components_granted(const struct mnat_media *m)
+{
+ unsigned i;
+
+ if (!m || !m->compc)
+ return false;
+
+ for (i=0; i<m->compc; i++) {
+ const struct comp *comp = &m->compv[i];
+ if (!comp->granted)
+ return false;
+ }
+
+ return true;
+}
+
+static void is_complete(struct mnat_sess *sess)
+{
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+
+ if (!all_components_granted(m))
+ return;
+ }
+
+ complete(sess, 0);
+}
+
+
+static void refresh_timeout(void *arg)
+{
+ struct comp *comp = arg;
+
+ comp->natpmp = mem_deref(comp->natpmp);
+ (void)natpmp_mapping_request(&comp->natpmp, &natpmp_srv,
+ comp->int_port, 0, comp->lifetime,
+ natpmp_resp_handler, comp);
+}
+
+
+static void natpmp_resp_handler(int err, const struct natpmp_resp *resp,
+ void *arg)
+{
+ struct comp *comp = arg;
+ struct mnat_media *m = comp->media;
+ struct sa map_addr;
+
+ if (err) {
+ warning("natpmp: response error: %m\n", err);
+ complete(m->sess, err);
+ return;
+ }
+
+ if (resp->op != NATPMP_OP_MAPPING_UDP)
+ return;
+ if (resp->result != NATPMP_SUCCESS) {
+ warning("natpmp: request failed with result code: %d\n",
+ resp->result);
+ complete(m->sess, EPROTO);
+ return;
+ }
+
+ if (resp->u.map.int_port != comp->int_port) {
+ info("natpmp: ignoring response for internal_port=%u\n",
+ resp->u.map.int_port);
+ return;
+ }
+
+ info("natpmp: mapping granted for comp %u:"
+ " internal_port=%u, external_port=%u, lifetime=%u\n",
+ comp->id,
+ resp->u.map.int_port, resp->u.map.ext_port,
+ resp->u.map.lifetime);
+
+ map_addr = natpmp_extaddr;
+ sa_set_port(&map_addr, resp->u.map.ext_port);
+ comp->lifetime = resp->u.map.lifetime;
+
+ /* Update SDP media with external IP-address mapping */
+ if (comp->id == 1)
+ sdp_media_set_laddr(m->sdpm, &map_addr);
+ else
+ sdp_media_set_laddr_rtcp(m->sdpm, &map_addr);
+
+ comp->granted = true;
+
+ tmr_start(&comp->tmr, comp->lifetime * 1000 * 3/4,
+ refresh_timeout, comp);
+
+ is_complete(m->sess);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err = 0;
+ (void)af;
+ (void)port;
+ (void)user;
+ (void)pass;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int comp_alloc(struct comp *comp, void *sock)
+{
+ struct sa laddr;
+ int err;
+
+ err = udp_local_get(sock, &laddr);
+ if (err)
+ goto out;
+
+ comp->int_port = sa_port(&laddr);
+
+ info("natpmp: `%s' stream comp %u local UDP port is %u\n",
+ sdp_media_name(comp->media->sdpm), comp->id, comp->int_port);
+
+ err = natpmp_mapping_request(&comp->natpmp, &natpmp_srv,
+ comp->int_port, 0, comp->lifetime,
+ natpmp_resp_handler, comp);
+ if (err)
+ goto out;
+
+ out:
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ unsigned i;
+ int err = 0;
+ (void)sock2;
+
+ if (!mp || !sess || !sdpm || proto != IPPROTO_UDP)
+ return EINVAL;
+ if (!sock1)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ m->compc = sock2 ? 2 : 1;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sess = sess;
+ m->sdpm = mem_ref(sdpm);
+
+ for (i=0; i<m->compc; i++) {
+
+ struct comp *comp = &m->compv[i];
+
+ comp->id = i+1;
+ comp->media = m;
+ comp->lifetime = LIFETIME;
+
+ err = comp_alloc(comp, i==0 ? sock1 : sock2);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ }
+
+ return err;
+}
+
+
+static void extaddr_handler(int err, const struct natpmp_resp *resp, void *arg)
+{
+ (void)arg;
+
+ if (err) {
+ warning("natpmp: external address ERROR: %m\n", err);
+ return;
+ }
+
+ if (resp->result != NATPMP_SUCCESS) {
+ warning("natpmp: external address failed"
+ " with result code: %d\n", resp->result);
+ return;
+ }
+
+ if (resp->op != NATPMP_OP_EXTERNAL)
+ return;
+
+ sa_set_in(&natpmp_extaddr, resp->u.ext_addr, 0);
+
+ info("natpmp: discovered External address: %j\n", &natpmp_extaddr);
+}
+
+
+static bool net_rt_handler(const char *ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg)
+{
+ (void)dstlen;
+ (void)arg;
+
+ if (sa_af(dst) != AF_INET)
+ return false;
+
+ if (sa_in(dst) == 0) {
+ natpmp_srv = *gw;
+ sa_set_port(&natpmp_srv, NATPMP_PORT);
+ info("natpmp: found default gateway %j on interface '%s'\n",
+ gw, ifname);
+ return true;
+ }
+
+ return false;
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ sa_init(&natpmp_srv, AF_INET);
+ sa_set_port(&natpmp_srv, NATPMP_PORT);
+
+ net_rt_list(net_rt_handler, NULL);
+
+ conf_get_sa(conf_cur(), "natpmp_server", &natpmp_srv);
+
+ info("natpmp: using NAT-PMP server at %J\n", &natpmp_srv);
+
+ err = natpmp_external_request(&natpmp_ext, &natpmp_srv,
+ extaddr_handler, NULL);
+ if (err)
+ return err;
+
+ return mnat_register(&mnat, baresip_mnatl(), "natpmp", NULL,
+ session_alloc, media_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+ natpmp_ext = mem_deref(natpmp_ext);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(natpmp) = {
+ "natpmp",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/omx/README b/modules/omx/README
new file mode 100644
index 0000000..e24c274
--- /dev/null
+++ b/modules/omx/README
@@ -0,0 +1,17 @@
+README
+------
+
+This module implements support for the VideoCore4 of
+the Raspberry Pi A/B/2/3.
+Currently it only does video playback.
+
+EXAMPLE CONFIG
+--------------
+
+# Video
+video_display omx,nil
+
+# Video codec Modules (in order)
+module omx.so
+
+
diff --git a/modules/omx/module.c b/modules/omx/module.c
new file mode 100644
index 0000000..a5b6fa8
--- /dev/null
+++ b/modules/omx/module.c
@@ -0,0 +1,137 @@
+/**
+ * @file omx/module.c Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+
+#include "omx.h"
+
+#include <stdlib.h>
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd,
+ struct vidisp_prm *prm, const char *dev, vidisp_resize_h *resizeh,
+ void *arg);
+int omx_vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance */
+ struct vidsz size;
+ struct omx_state* omx;
+};
+
+static struct vidisp* vid;
+
+static struct omx_state omx;
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+ omx_display_disable(st->omx);
+}
+
+
+int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+
+ /* Not used by OMX */
+ (void) prm;
+ (void) dev;
+ (void) resizeh;
+ (void) arg;
+
+ info("omx: vidisp_alloc\n");
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+ *vp = st;
+
+ st->omx = &omx;
+
+ return 0;
+}
+
+
+int omx_vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ int err = 0;
+ void* buf;
+ uint32_t len;
+
+ struct vidframe omx_frame;
+
+ (void)title;
+
+ if (frame->fmt != VID_FMT_YUV420P) {
+ return EINVAL;
+ }
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ info("omx: new frame size: w=%d h=%d\n",
+ frame->size.w, frame->size.h);
+ info("omx: linesize[0]=%d\tlinesize[1]=%d\tlinesize[2]=%d\n",
+ frame->linesize[0], frame->linesize[1],
+ frame->linesize[2]);
+ err = omx_display_enable(st->omx,
+ frame->size.w, frame->size.h, frame->size.w);
+ if (err) {
+ warning("omx_display_enable failed");
+ return err;
+ }
+ st->size = frame->size;
+ }
+
+ /* Get Buffer Pointer */
+ omx_display_input_buffer(st->omx, &buf, &len);
+
+ vidframe_init_buf(&omx_frame, VID_FMT_YUV420P, &frame->size,
+ buf);
+
+ vidconv(&omx_frame, frame, 0);
+
+ omx_display_flush_buffer(st->omx);
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ if (omx_init(&omx) != 0) {
+ warning("Could not initialize OpenMAX");
+ return ENODEV;
+ }
+
+ return vidisp_register(&vid, baresip_vidispl(), "omx",
+ omx_vidisp_alloc, NULL, omx_vidisp_display, NULL);
+}
+
+
+static int module_close(void)
+{
+ /* HACK: not deinitializing OMX because of a hangup */
+ /* omx_deinit(&omx) */
+ vid = mem_deref(vid);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(omx) = {
+ "omx",
+ "vidisp",
+ module_init,
+ module_close
+};
diff --git a/modules/omx/module.mk b/modules/omx/module.mk
new file mode 100644
index 0000000..958a1a5
--- /dev/null
+++ b/modules/omx/module.mk
@@ -0,0 +1,23 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+MOD := omx
+$(MOD)_SRCS += omx.c module.c
+
+ifneq ($(USE_OMX_RPI),)
+$(MOD)_CFLAGS := -DRASPBERRY_PI -DOMX_SKIP64BIT \
+ -I/usr/local/include/interface/vmcs_host/linux/ \
+ -I /usr/local/include/interface/vcos/pthreads/ \
+ -I /opt/vc/include -I /opt/vc/include/interface/vmcs_host/linux \
+ -I /opt/vc/include/interface/vcos/pthreads
+$(MOD)_LFLAGS += -lvcos -lbcm_host -lopenmaxil -L /opt/vc/lib
+endif
+
+ifneq ($(USE_OMX_BELLAGIO),)
+$(MOD)_LFLAGS += -lomxil-bellagio
+endif
+
+include mk/mod.mk
diff --git a/modules/omx/omx.c b/modules/omx/omx.c
new file mode 100644
index 0000000..6b08d19
--- /dev/null
+++ b/modules/omx/omx.c
@@ -0,0 +1,349 @@
+/**
+ * @file omx.c Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+#include "omx.h"
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Avoids a VideoCore header warning about clock_gettime() */
+#include <time.h>
+#include <sys/time.h>
+
+/**
+ * @defgroup omx omx
+ *
+ * TODO:
+ * * Proper sync OMX events across threads, instead of busy waiting
+ */
+
+#ifdef RASPBERRY_PI
+static const int VIDEO_RENDER_PORT = 90;
+#else
+static const int VIDEO_RENDER_PORT = 0;
+#endif
+
+/*
+static void setHeader(OMX_PTR header, OMX_U32 size) {
+ OMX_VERSIONTYPE* ver = (OMX_VERSIONTYPE*)(header + sizeof(OMX_U32));
+ *((OMX_U32*)header) = size;
+
+ ver->s.nVersionMajor = VERSIONMAJOR;
+ ver->s.nVersionMinor = VERSIONMINOR;
+ ver->s.nRevision = VERSIONREVISION;
+ ver->s.nStep = VERSIONSTEP;
+}
+* */
+
+
+static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE hComponent, OMX_PTR pAppData,
+ OMX_EVENTTYPE eEvent, OMX_U32 nData1, OMX_U32 nData2,
+ OMX_PTR pEventData)
+{
+ (void) hComponent;
+
+ switch (eEvent) {
+
+ case OMX_EventCmdComplete:
+ debug("omx.EventHandler: Previous command completed\n"
+ "d1=%x\td2=%x\teventData=%p\tappdata=%p\n",
+ nData1, nData2, pEventData, pAppData);
+ /* TODO: Put these event into a multithreaded queue,
+ * properly wait for them in the issuing code */
+ break;
+
+ case OMX_EventError:
+ warning("omx.EventHandler: Error event type "
+ "data1=%x\tdata2=%x\n", nData1, nData2);
+ break;
+
+ default:
+ warning("omx.EventHandler: Unknown event type %d\t"
+ "data1=%x data2=%x\n", eEvent, nData1, nData2);
+ return -1;
+ break;
+ }
+
+ return 0;
+}
+
+
+static OMX_ERRORTYPE EmptyBufferDone(OMX_HANDLETYPE hComponent,
+ OMX_PTR pAppData,
+ OMX_BUFFERHEADERTYPE* pBuffer)
+{
+ (void) hComponent;
+ (void) pAppData;
+ (void) pBuffer;
+
+ /* TODO: Wrap every call that can generate an event,
+ * and panic if an unexpected event arrives */
+ return 0;
+}
+
+
+static OMX_ERRORTYPE FillBufferDone(OMX_HANDLETYPE hComponent,
+ OMX_PTR pAppData, OMX_BUFFERHEADERTYPE* pBuffer)
+{
+ (void) hComponent;
+ (void) pAppData;
+ (void) pBuffer;
+ debug("FillBufferDone\n");
+ return 0;
+}
+
+
+static struct OMX_CALLBACKTYPE callbacks = {
+ EventHandler,
+ EmptyBufferDone,
+ &FillBufferDone
+};
+
+
+int omx_init(struct omx_state* st)
+{
+ OMX_ERRORTYPE err;
+#ifdef RASPBERRY_PI
+ bcm_host_init();
+#endif
+
+ st->buffers = NULL;
+
+ err = OMX_Init();
+#ifdef RASPBERRY_PI
+ err |= OMX_GetHandle(&st->video_render,
+ "OMX.broadcom.video_render", 0, &callbacks);
+#else
+ err |= OMX_GetHandle(&st->video_render,
+ "OMX.st.video.xvideosink", 0, &callbacks);
+#endif
+
+ if (!st->video_render || err != 0) {
+ warning("Failed to create OMX video_render component\n");
+ return ENOENT;
+ }
+ else {
+ info("created video_render component\n");
+ return 0;
+ }
+}
+
+
+/* Some busy loops to verify we're running in order */
+static void block_until_state_changed(OMX_HANDLETYPE hComponent,
+ OMX_STATETYPE wanted_eState)
+{
+ OMX_STATETYPE eState;
+ unsigned int i = 0;
+ while (i++ == 0 || eState != wanted_eState) {
+ OMX_GetState(hComponent, &eState);
+ if (eState != wanted_eState) {
+ sys_usleep(10000);
+ }
+ }
+}
+
+
+void omx_deinit(struct omx_state* st)
+{
+ info("omx_deinit");
+ OMX_SendCommand(st->video_render,
+ OMX_CommandStateSet, OMX_StateIdle, NULL);
+ block_until_state_changed(st->video_render, OMX_StateIdle);
+ OMX_SendCommand(st->video_render,
+ OMX_CommandStateSet, OMX_StateLoaded, NULL);
+ block_until_state_changed(st->video_render, OMX_StateLoaded);
+ OMX_FreeHandle(st->video_render);
+ OMX_Deinit();
+}
+
+
+void omx_display_disable(struct omx_state* st)
+{
+ (void)st;
+
+ #ifdef RASPBERRY_PI
+ OMX_ERRORTYPE err;
+ OMX_CONFIG_DISPLAYREGIONTYPE config;
+ memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE));
+ config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE);
+ config.nVersion.nVersion = OMX_VERSION;
+ config.nPortIndex = VIDEO_RENDER_PORT;
+ config.fullscreen = 0;
+ config.set = OMX_DISPLAY_SET_FULLSCREEN;
+
+ err = OMX_SetParameter(st->video_render,
+ OMX_IndexConfigDisplayRegion, &config);
+
+ if (err != 0) {
+ warning("omx_display_disable command failed");
+ }
+
+ #endif
+}
+
+
+static void block_until_port_changed(OMX_HANDLETYPE hComponent,
+ OMX_U32 nPortIndex, OMX_BOOL bEnabled) {
+
+ OMX_ERRORTYPE r;
+ OMX_PARAM_PORTDEFINITIONTYPE portdef;
+ OMX_U32 i = 0;
+
+ memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ portdef.nVersion.nVersion = OMX_VERSION;
+ portdef.nPortIndex = nPortIndex;
+
+ while (i++ == 0 || portdef.bEnabled != bEnabled) {
+ r = OMX_GetParameter(hComponent,
+ OMX_IndexParamPortDefinition, &portdef);
+ if (r != OMX_ErrorNone) {
+ warning("block_until_port_changed: OMX_GetParameter "
+ " failed with Result=%d\n", r);
+ }
+ if (portdef.bEnabled != bEnabled) {
+ sys_usleep(10000);
+ }
+ }
+}
+
+
+int omx_display_enable(struct omx_state* st,
+ int width, int height, int stride)
+{
+ unsigned int i;
+ OMX_PARAM_PORTDEFINITIONTYPE portdef;
+#ifdef RASPBERRY_PI
+ OMX_CONFIG_DISPLAYREGIONTYPE config;
+#endif
+ OMX_ERRORTYPE err = 0;
+
+ info("omx_update_size %d %d\n", width, height);
+
+ #ifdef RASPBERRY_PI
+ memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE));
+ config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE);
+ config.nVersion.nVersion = OMX_VERSION;
+ config.nPortIndex = VIDEO_RENDER_PORT;
+ config.fullscreen = 1;
+ config.set = OMX_DISPLAY_SET_FULLSCREEN;
+
+ err |= OMX_SetParameter(st->video_render,
+ OMX_IndexConfigDisplayRegion, &config);
+
+ #endif
+
+ memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ portdef.nVersion.nVersion = OMX_VERSION;
+ portdef.nPortIndex = VIDEO_RENDER_PORT;
+
+ /* specify buffer requirements */
+ err |= OMX_GetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+ if (err != 0) {
+ warning("omx_display_enable: couldn't retrieve port def\n");
+ err = ENOMEM;
+ goto exit;
+ }
+
+ info("omx port definition: h=%d w=%d s=%d sh=%d\n",
+ portdef.format.video.nFrameWidth,
+ portdef.format.video.nFrameHeight,
+ portdef.format.video.nStride,
+ portdef.format.video.nSliceHeight);
+
+ portdef.format.video.nFrameWidth = width;
+ portdef.format.video.nFrameHeight = height;
+ portdef.format.video.nStride = stride;
+ portdef.format.video.nSliceHeight = height;
+ portdef.bEnabled = 1;
+
+ err |= OMX_SetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+
+ if (err) {
+ warning("omx_display_enable: could not set port definition\n");
+ }
+ block_until_port_changed(st->video_render, VIDEO_RENDER_PORT, true);
+
+ err |= OMX_GetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+
+ if (err != 0 || !portdef.bEnabled) {
+ warning("omx_display_enable: failed to set up video port\n");
+ err = ENOMEM;
+ goto exit;
+ }
+
+ /* HACK: This state-change sometimes hangs for unknown reasons,
+ * so we just send the state command and wait 50 ms */
+ /* block_until_state_changed(st->video_render, OMX_StateIdle); */
+
+ OMX_SendCommand(st->video_render, OMX_CommandStateSet,
+ OMX_StateIdle, NULL);
+ sys_usleep(50000);
+
+ if (!st->buffers) {
+ st->buffers =
+ malloc(portdef.nBufferCountActual * sizeof(void*));
+ st->num_buffers = portdef.nBufferCountActual;
+ st->current_buffer = 0;
+
+ for (i = 0; i < portdef.nBufferCountActual; i++) {
+ err = OMX_AllocateBuffer(st->video_render,
+ &st->buffers[i], VIDEO_RENDER_PORT,
+ st, portdef.nBufferSize);
+ if (err) {
+ warning("OMX_AllocateBuffer failed: %d\n",
+ err);
+ err = ENOMEM;
+ goto exit;
+ }
+ }
+ }
+
+ debug("omx_update_size: send to execute state");
+ OMX_SendCommand(st->video_render, OMX_CommandStateSet,
+ OMX_StateExecuting, NULL);
+ block_until_state_changed(st->video_render, OMX_StateExecuting);
+
+exit:
+ return err;
+}
+
+
+int omx_display_input_buffer(struct omx_state* st,
+ void** pbuf, uint32_t* plen)
+{
+ if (!st->buffers) return EINVAL;
+
+ *pbuf = st->buffers[0]->pBuffer;
+ *plen = st->buffers[0]->nAllocLen;
+
+ st->buffers[0]->nFilledLen = *plen;
+ st->buffers[0]->nOffset = 0;
+
+ return 0;
+}
+
+
+int omx_display_flush_buffer(struct omx_state* st)
+{
+ if (OMX_EmptyThisBuffer(st->video_render, st->buffers[0])
+ != OMX_ErrorNone) {
+ warning("OMX_EmptyThisBuffer error");
+ }
+
+ return 0;
+}
diff --git a/modules/omx/omx.h b/modules/omx/omx.h
new file mode 100644
index 0000000..13d8928
--- /dev/null
+++ b/modules/omx/omx.h
@@ -0,0 +1,46 @@
+/**
+ * @file omx.h Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+#ifdef RASPBERRY_PI
+#include <IL/OMX_Core.h>
+#include <IL/OMX_Video.h>
+#include <IL/OMX_Broadcom.h>
+#else
+#include <OMX_Core.h>
+#include <OMX_Component.h>
+#include <OMX_Video.h>
+
+#undef OMX_VERSION
+#define OMX_VERSION 0x01010101
+#define OMX_ERROR_NONE 0
+#endif
+
+#include <pthread.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Needed for usleep to appear */
+#define _BSD_SOURCE
+#include <unistd.h>
+
+struct omx_state {
+ OMX_HANDLETYPE video_render;
+ OMX_BUFFERHEADERTYPE** buffers;
+ int num_buffers;
+ int current_buffer;
+};
+
+int omx_init(struct omx_state* st);
+void omx_deinit(struct omx_state* st);
+
+int omx_display_input_buffer(struct omx_state* st,
+ void** pbuf, uint32_t* plen);
+int omx_display_flush_buffer(struct omx_state* st);
+
+int omx_display_enable(struct omx_state* st,
+ int width, int height, int stride);
+void omx_display_disable(struct omx_state* st);
diff --git a/modules/opengl/module.mk b/modules/opengl/module.mk
new file mode 100644
index 0000000..4b348ed
--- /dev/null
+++ b/modules/opengl/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := opengl
+$(MOD)_SRCS += opengl.m
+$(MOD)_LFLAGS += -framework OpenGL -framework Cocoa -lobjc
+
+include mk/mod.mk
diff --git a/modules/opengl/opengl.m b/modules/opengl/opengl.m
new file mode 100644
index 0000000..a95649c
--- /dev/null
+++ b/modules/opengl/opengl.m
@@ -0,0 +1,534 @@
+/**
+ * @file opengl.m Video driver for OpenGL on MacOSX
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <Cocoa/Cocoa.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup opengl opengl
+ *
+ * Video display module for OpenGL on MacOSX
+ */
+
+
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 101200)
+#define NSTitledWindowMask NSWindowStyleMaskTitled
+#define NSClosableWindowMask NSWindowStyleMaskClosable
+#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable
+#endif
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /**< Inheritance (1st) */
+ struct vidsz size; /**< Current size */
+ NSOpenGLContext *ctx;
+ NSWindow *win;
+ GLhandleARB PHandle;
+ char *prog;
+};
+
+
+static struct vidisp *vid; /**< OPENGL Video-display */
+
+
+static const char *FProgram=
+ "uniform sampler2DRect Ytex;\n"
+ "uniform sampler2DRect Utex,Vtex;\n"
+ "void main(void) {\n"
+ " float nx,ny,r,g,b,y,u,v;\n"
+ " vec4 txl,ux,vx;"
+ " nx=gl_TexCoord[0].x;\n"
+ " ny=%d.0-gl_TexCoord[0].y;\n"
+ " y=texture2DRect(Ytex,vec2(nx,ny)).r;\n"
+ " u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n"
+ " v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n"
+
+ " y=1.1643*(y-0.0625);\n"
+ " u=u-0.5;\n"
+ " v=v-0.5;\n"
+
+ " r=y+1.5958*v;\n"
+ " g=y-0.39173*u-0.81290*v;\n"
+ " b=y+2.017*u;\n"
+
+ " gl_FragColor=vec4(r,g,b,1.0);\n"
+ "}\n";
+
+
+static void destructor(void *arg)
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ struct vidisp_st *st = arg;
+
+ if (st->ctx) {
+ [st->ctx clearDrawable];
+ [st->ctx release];
+ }
+
+ [st->win close];
+
+ if (st->PHandle) {
+ glUseProgramObjectARB(0);
+ glDeleteObjectARB(st->PHandle);
+ }
+
+ mem_deref(st->prog);
+
+ [pool release];
+}
+
+
+static int create_window(struct vidisp_st *st)
+{
+ NSRect rect = NSMakeRect(0, 0, 100, 100);
+ NSUInteger style;
+
+ if (st->win)
+ return 0;
+
+ style = NSTitledWindowMask |
+ NSClosableWindowMask |
+ NSMiniaturizableWindowMask;
+
+ st->win = [[NSWindow alloc] initWithContentRect:rect
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:FALSE];
+ if (!st->win) {
+ warning("opengl: could not create NSWindow\n");
+ return ENOMEM;
+ }
+
+ [st->win setLevel:NSFloatingWindowLevel];
+
+ return 0;
+}
+
+
+static void opengl_reset(struct vidisp_st *st, const struct vidsz *sz)
+{
+ if (st->PHandle) {
+ glUseProgramObjectARB(0);
+ glDeleteObjectARB(st->PHandle);
+ st->PHandle = 0;
+ st->prog = mem_deref(st->prog);
+ }
+
+ st->size = *sz;
+}
+
+
+static int setup_shader(struct vidisp_st *st, int width, int height)
+{
+ GLhandleARB FSHandle, PHandle;
+ const char *progv[1];
+ char buf[1024];
+ int err, i;
+
+ if (st->PHandle)
+ return 0;
+
+ err = re_sdprintf(&st->prog, FProgram, height);
+ if (err)
+ return err;
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, width, 0, height, -1, 1);
+ glViewport(0, 0, width, height);
+ glClearColor(0, 0, 0, 0);
+ glColor3f(1.0f, 0.84f, 0.0f);
+ glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
+
+ /* Set up program objects. */
+ PHandle = glCreateProgramObjectARB();
+ FSHandle = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
+
+ /* Compile the shader. */
+ progv[0] = st->prog;
+ glShaderSourceARB(FSHandle, 1, progv, NULL);
+ glCompileShaderARB(FSHandle);
+
+ /* Print the compilation log. */
+ glGetObjectParameterivARB(FSHandle, GL_OBJECT_COMPILE_STATUS_ARB, &i);
+ if (i != 1) {
+ warning("opengl: shader compile failed\n");
+ return ENOSYS;
+ }
+
+ glGetInfoLogARB(FSHandle, sizeof(buf), NULL, buf);
+
+ /* Create a complete program object. */
+ glAttachObjectARB(PHandle, FSHandle);
+ glLinkProgramARB(PHandle);
+
+ /* And print the link log. */
+ glGetInfoLogARB(PHandle, sizeof(buf), NULL, buf);
+
+ /* Finally, use the program. */
+ glUseProgramObjectARB(PHandle);
+
+ st->PHandle = PHandle;
+
+ return 0;
+}
+
+
+static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ NSOpenGLPixelFormatAttribute attr[] = {
+ NSOpenGLPFAColorSize, 32,
+ NSOpenGLPFADepthSize, 16,
+ NSOpenGLPFADoubleBuffer,
+ 0
+ };
+ NSOpenGLPixelFormat *fmt;
+ NSAutoreleasePool *pool;
+ struct vidisp_st *st;
+ GLint vsync = 1;
+ int err = 0;
+
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ pool = [[NSAutoreleasePool alloc] init];
+ if (!pool)
+ return ENOMEM;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
+ if (!fmt) {
+ err = ENOMEM;
+ warning("opengl: Failed creating OpenGL format\n");
+ goto out;
+ }
+
+ st->ctx = [[NSOpenGLContext alloc] initWithFormat:fmt
+ shareContext:nil];
+
+ [fmt release];
+
+ if (!st->ctx) {
+ err = ENOMEM;
+ warning("opengl: Failed creating OpenGL context\n");
+ goto out;
+ }
+
+ /* Use provided view, or create our own */
+ if (prm && prm->view) {
+ [st->ctx setView:prm->view];
+ }
+ else {
+ err = create_window(st);
+ if (err)
+ goto out;
+
+ if (prm)
+ prm->view = [st->win contentView];
+ }
+
+ /* Enable vertical sync */
+ [st->ctx setValues:&vsync forParameter:NSOpenGLCPSwapInterval];
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ [pool release];
+
+ return err;
+}
+
+
+static inline void draw_yuv(GLhandleARB PHandle, int height,
+ const uint8_t *Ytex, int linesizeY,
+ const uint8_t *Utex, int linesizeU,
+ const uint8_t *Vtex, int linesizeV)
+{
+ int i;
+
+ /* This might not be required, but should not hurt. */
+ glEnable(GL_TEXTURE_2D);
+
+ /* Select texture unit 1 as the active unit and bind the U texture. */
+ glActiveTexture(GL_TEXTURE1);
+ i = glGetUniformLocationARB(PHandle, "Utex");
+ glUniform1iARB(i,1); /* Bind Utex to texture unit 1 */
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT,1);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+ glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
+ glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE,
+ linesizeU, height/2, 0,
+ GL_LUMINANCE,GL_UNSIGNED_BYTE,Utex);
+
+ /* Select texture unit 2 as the active unit and bind the V texture. */
+ glActiveTexture(GL_TEXTURE2);
+ i = glGetUniformLocationARB(PHandle, "Vtex");
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT,2);
+ glUniform1iARB(i,2); /* Bind Vtext to texture unit 2 */
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+
+ glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
+ glTexImage2D(GL_TEXTURE_RECTANGLE_EXT,0,GL_LUMINANCE,
+ linesizeV, height/2, 0,
+ GL_LUMINANCE,GL_UNSIGNED_BYTE,Vtex);
+
+ /* Select texture unit 0 as the active unit and bind the Y texture. */
+ glActiveTexture(GL_TEXTURE0);
+ i = glGetUniformLocationARB(PHandle,"Ytex");
+ glUniform1iARB(i,0); /* Bind Ytex to texture unit 0 */
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT,3);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+ glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
+
+ glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_LUMINANCE,
+ linesizeY, height, 0,
+ GL_LUMINANCE, GL_UNSIGNED_BYTE, Ytex);
+}
+
+
+static inline void draw_blit(int width, int height)
+{
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Draw image */
+
+ glBegin(GL_QUADS);
+ {
+ glTexCoord2i(0, 0);
+ glVertex2i(0, 0);
+ glTexCoord2i(width, 0);
+ glVertex2i(width, 0);
+ glTexCoord2i(width, height);
+ glVertex2i(width, height);
+ glTexCoord2i(0, height);
+ glVertex2i(0, height);
+ }
+ glEnd();
+}
+
+
+static inline void draw_rgb(const uint8_t *pic, int w, int h)
+{
+ glEnable(GL_TEXTURE_RECTANGLE_EXT);
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1);
+
+ glTextureRangeAPPLE(GL_TEXTURE_RECTANGLE_EXT, w * h * 2, pic);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,
+ GL_TEXTURE_STORAGE_HINT_APPLE,
+ GL_STORAGE_SHARED_APPLE);
+ glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER,
+ GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER,
+ GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S,
+ GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T,
+ GL_CLAMP_TO_EDGE);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA, w, h, 0,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pic);
+
+ /* draw */
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glEnable(GL_TEXTURE_2D);
+
+ glViewport(0, 0, w, h);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glOrtho( (GLfloat)0, (GLfloat)w, (GLfloat)0, (GLfloat)h, -1.0, 1.0);
+
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 1);
+
+ glMatrixMode(GL_TEXTURE);
+ glLoadIdentity();
+
+ glBegin(GL_QUADS);
+ {
+ glTexCoord2f(0.0f, 0.0f);
+ glVertex2f(0.0f, h);
+ glTexCoord2f(0.0f, h);
+ glVertex2f(0.0f, 0.0f);
+ glTexCoord2f(w, h);
+ glVertex2f(w, 0.0f);
+ glTexCoord2f(w, 0.0f);
+ glVertex2f(w, h);
+ }
+ glEnd();
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ NSAutoreleasePool *pool;
+ bool upd = false;
+ int err = 0;
+
+ pool = [[NSAutoreleasePool alloc] init];
+ if (!pool)
+ return ENOMEM;
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ if (st->size.w && st->size.h) {
+ info("opengl: reset: %u x %u ---> %u x %u\n",
+ st->size.w, st->size.h,
+ frame->size.w, frame->size.h);
+ }
+
+ opengl_reset(st, &frame->size);
+
+ upd = true;
+ }
+
+ if (upd && st->win) {
+
+ const NSSize size = {frame->size.w, frame->size.h};
+ char capt[256];
+
+ [st->win setContentSize:size];
+
+ if (title) {
+ re_snprintf(capt, sizeof(capt), "%s - %u x %u",
+ title, frame->size.w, frame->size.h);
+ }
+ else {
+ re_snprintf(capt, sizeof(capt), "%u x %u",
+ frame->size.w, frame->size.h);
+ }
+
+ [st->win setTitle:[NSString stringWithUTF8String:capt]];
+
+ [st->win makeKeyAndOrderFront:nil];
+ [st->win display];
+ [st->win center];
+
+ [st->ctx clearDrawable];
+ [st->ctx setView:[st->win contentView]];
+ }
+
+ [st->ctx makeCurrentContext];
+
+ if (frame->fmt == VID_FMT_YUV420P) {
+
+ if (!st->PHandle) {
+
+ debug("opengl: using Vertex shader with YUV420P\n");
+
+ err = setup_shader(st, frame->size.w, frame->size.h);
+ if (err)
+ goto out;
+ }
+
+ draw_yuv(st->PHandle, frame->size.h,
+ frame->data[0], frame->linesize[0],
+ frame->data[1], frame->linesize[1],
+ frame->data[2], frame->linesize[2]);
+ draw_blit(frame->size.w, frame->size.h);
+ }
+ else if (frame->fmt == VID_FMT_RGB32) {
+
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glViewport(0, 0, frame->size.w, frame->size.h);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ draw_rgb(frame->data[0], frame->size.w, frame->size.h);
+ }
+ else {
+ warning("opengl: unknown pixel format %s\n",
+ vidfmt_name(frame->fmt));
+ err = EINVAL;
+ }
+
+ [st->ctx flushBuffer];
+
+ out:
+ [pool release];
+
+ return err;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st)
+ return;
+
+ [st->win orderOut:nil];
+}
+
+
+static int module_init(void)
+{
+ NSApplication *app;
+ int err;
+
+ app = [NSApplication sharedApplication];
+ if (!app)
+ return ENOSYS;
+
+ err = vidisp_register(&vid, baresip_vidispl(),
+ "opengl", alloc, NULL, display, hide);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(opengl) = {
+ "opengl",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/opengles/context.m b/modules/opengles/context.m
new file mode 100644
index 0000000..354c74f
--- /dev/null
+++ b/modules/opengles/context.m
@@ -0,0 +1,115 @@
+/**
+ * @file context.m OpenGLES Context for iOS
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <UIKit/UIKit.h>
+#include <QuartzCore/CAEAGLLayer.h>
+#include <OpenGLES/ES1/gl.h>
+#include <OpenGLES/ES1/glext.h>
+#include "opengles.h"
+
+
+@interface GlView : UIView
+{
+ struct vidisp_st *st;
+}
+@end
+
+
+static EAGLContext *ctx = NULL;
+
+
+@implementation GlView
+
+
++ (Class)layerClass
+{
+ return [CAEAGLLayer class];
+}
+
+
+- (id)initWithFrame:(CGRect)frame vidisp:(struct vidisp_st *)vst
+{
+ self = [super initWithFrame:frame];
+ if (!self)
+ return nil;
+
+ self.layer.opaque = YES;
+
+ st = vst;
+
+ return self;
+}
+
+
+- (void) render_sel:(id)unused
+{
+ (void)unused;
+
+ if (!ctx) {
+ UIWindow* window = [UIApplication sharedApplication].keyWindow;
+
+ ctx = [EAGLContext alloc];
+ [ctx initWithAPI:kEAGLRenderingAPIOpenGLES1];
+
+ [EAGLContext setCurrentContext:ctx];
+
+ [window addSubview:self];
+ [window bringSubviewToFront:self];
+
+ [window makeKeyAndVisible];
+ }
+
+ if (!st->framebuffer) {
+
+ opengles_addbuffers(st);
+
+ [ctx renderbufferStorage:GL_RENDERBUFFER_OES
+ fromDrawable:(CAEAGLLayer*)self.layer];
+ }
+
+ opengles_render(st);
+
+ [ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
+}
+
+
+- (void) dealloc
+{
+ [ctx release];
+
+ [super dealloc];
+}
+
+
+@end
+
+
+void context_destroy(struct vidisp_st *st)
+{
+ [(UIView *)st->view release];
+}
+
+
+int context_init(struct vidisp_st *st)
+{
+ UIWindow* window = [UIApplication sharedApplication].keyWindow;
+
+ st->view = [[GlView new] initWithFrame:window.bounds vidisp:st];
+
+ return 0;
+}
+
+
+void context_render(struct vidisp_st *st)
+{
+ UIView *view = st->view;
+
+ [view performSelectorOnMainThread:@selector(render_sel:)
+ withObject:nil
+ waitUntilDone:YES];
+}
diff --git a/modules/opengles/module.mk b/modules/opengles/module.mk
new file mode 100644
index 0000000..a9c5300
--- /dev/null
+++ b/modules/opengles/module.mk
@@ -0,0 +1,16 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := opengles
+$(MOD)_SRCS += opengles.c
+
+ifeq ($(OS),darwin)
+$(MOD)_SRCS += context.m
+
+$(MOD)_LFLAGS += -lobjc -framework CoreGraphics -framework CoreFoundation
+endif
+
+include mk/mod.mk
diff --git a/modules/opengles/opengles.c b/modules/opengles/opengles.c
new file mode 100644
index 0000000..1284de8
--- /dev/null
+++ b/modules/opengles/opengles.c
@@ -0,0 +1,297 @@
+/**
+ * @file opengles.c Video driver for OpenGLES
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <OpenGLES/ES1/gl.h>
+#include <OpenGLES/ES1/glext.h>
+#include "opengles.h"
+
+
+/**
+ * @defgroup opengles opengles
+ *
+ * Video display module for OpenGLES on Android
+ */
+
+
+static struct vidisp *vid;
+
+
+static int texture_init(struct vidisp_st *st)
+{
+ glGenTextures(1, &st->texture_id);
+ if (!st->texture_id)
+ return ENOMEM;
+
+ glBindTexture(GL_TEXTURE_2D, st->texture_id);
+ glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+ st->vf->size.w, st->vf->size.h, 0,
+ GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ return 0;
+}
+
+
+static void texture_render(struct vidisp_st *st)
+{
+ static const GLfloat coords[4 * 2] = {
+ 0.0, 1.0,
+ 1.0, 1.0,
+ 0.0, 0.0,
+ 1.0, 0.0
+ };
+
+ glBindTexture(GL_TEXTURE_2D, st->texture_id);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+ st->vf->size.w, st->vf->size.h, 0,
+ GL_RGB, GL_UNSIGNED_SHORT_5_6_5, st->vf->data[0]);
+
+ /* Setup the vertices */
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 0, st->vertices);
+
+ /* Setup the texture coordinates */
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glTexCoordPointer(2, GL_FLOAT, 0, coords);
+
+ glBindTexture(GL_TEXTURE_2D, st->texture_id);
+
+ glEnable(GL_TEXTURE_2D);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ glDisable(GL_TEXTURE_2D);
+}
+
+
+static void setup_layout(struct vidisp_st *st, const struct vidsz *screensz,
+ struct vidrect *ortho, struct vidrect *vp)
+{
+ int x, y, w, h, i = 0;
+
+ w = st->vf->size.w;
+ h = st->vf->size.h;
+
+ st->vertices[i++] = 0;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = w;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = h;
+ st->vertices[i++] = 0;
+ st->vertices[i++] = w;
+ st->vertices[i++] = h;
+ st->vertices[i++] = 0;
+
+ x = (screensz->w - w) / 2;
+ y = (screensz->h - h) / 2;
+
+ if (x < 0) {
+ vp->x = 0;
+ ortho->x = -x;
+ }
+ else {
+ vp->x = x;
+ ortho->x = 0;
+ }
+
+ if (y < 0) {
+ vp->y = 0;
+ ortho->y = -y;
+ }
+ else {
+ vp->y = y;
+ ortho->y = 0;
+ }
+
+ vp->w = screensz->w - 2 * vp->x;
+ vp->h = screensz->h - 2 * vp->y;
+
+ ortho->w = w - ortho->x;
+ ortho->h = h - ortho->y;
+}
+
+
+void opengles_addbuffers(struct vidisp_st *st)
+{
+ glGenFramebuffersOES(1, &st->framebuffer);
+ glGenRenderbuffersOES(1, &st->renderbuffer);
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer);
+}
+
+
+void opengles_render(struct vidisp_st *st)
+{
+ if (!st->texture_id) {
+
+ struct vidrect ortho, vp;
+ struct vidsz bufsz;
+ int err = 0;
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
+ GL_RENDERBUFFER_WIDTH_OES,
+ (GLint *)&bufsz.w);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
+ GL_RENDERBUFFER_HEIGHT_OES,
+ (GLint *)&bufsz.h);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
+ GL_COLOR_ATTACHMENT0_OES,
+ GL_RENDERBUFFER_OES,
+ st->renderbuffer);
+
+ err = texture_init(st);
+ if (err)
+ return;
+
+ glBindRenderbufferOES(GL_FRAMEBUFFER_OES, st->renderbuffer);
+
+ setup_layout(st, &bufsz, &ortho, &vp);
+
+
+ /* Set up Viewports etc. */
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, st->framebuffer);
+
+ glViewport(vp.x, vp.y, vp.w, vp.h);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glOrthof(ortho.x, ortho.w, ortho.y, ortho.h, 0.0f, 1.0f);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glDisable(GL_DEPTH_TEST);
+ glDisableClientState(GL_COLOR_ARRAY);
+ }
+
+ texture_render(st);
+
+ glDisable(GL_TEXTURE_2D);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glEnable(GL_DEPTH_TEST);
+
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, st->renderbuffer);
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ glDeleteTextures(1, &st->texture_id);
+ glDeleteFramebuffersOES(1, &st->framebuffer);
+ glDeleteRenderbuffersOES(1, &st->renderbuffer);
+
+ context_destroy(st);
+
+ mem_deref(st->vf);
+}
+
+
+static int opengles_alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh,
+ void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+
+ (void)prm;
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ err = context_init(st);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int opengles_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ int err;
+
+ (void)title;
+
+ if (!st->vf) {
+ if (frame->size.w & 3) {
+ warning("opengles: width must be multiple of 4\n");
+ return EINVAL;
+ }
+
+ err = vidframe_alloc(&st->vf, VID_FMT_RGB565, &frame->size);
+ if (err)
+ return err;
+ }
+
+ vidconv(st->vf, frame, NULL);
+
+ context_render(st);
+
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ return vidisp_register(&vid, baresip_vidispl(),
+ "opengles", opengles_alloc, NULL,
+ opengles_display, NULL);
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(opengles) = {
+ "opengles",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/opengles/opengles.h b/modules/opengles/opengles.h
new file mode 100644
index 0000000..6c61a1e
--- /dev/null
+++ b/modules/opengles/opengles.h
@@ -0,0 +1,28 @@
+/**
+ * @file opengles.h Internal API to OpenGLES module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /* pointer to base-class (inheritance) */
+ struct vidframe *vf;
+
+ /* GLES: */
+ GLuint framebuffer;
+ GLuint renderbuffer;
+ GLuint texture_id;
+ GLfloat vertices[4 * 3];
+
+ void *view;
+};
+
+
+void opengles_addbuffers(struct vidisp_st *st);
+void opengles_render(struct vidisp_st *st);
+
+
+int context_init(struct vidisp_st *st);
+void context_destroy(struct vidisp_st *st);
+void context_render(struct vidisp_st *st);
diff --git a/modules/opensles/module.mk b/modules/opensles/module.mk
new file mode 100644
index 0000000..30ecb4c
--- /dev/null
+++ b/modules/opensles/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := opensles
+$(MOD)_SRCS += opensles.c
+$(MOD)_SRCS += player.c
+$(MOD)_SRCS += recorder.c
+$(MOD)_LFLAGS += -lOpenSLES
+
+include mk/mod.mk
diff --git a/modules/opensles/opensles.c b/modules/opensles/opensles.c
new file mode 100644
index 0000000..2201bf2
--- /dev/null
+++ b/modules/opensles/opensles.c
@@ -0,0 +1,79 @@
+/**
+ * @file opensles.c OpenSLES audio driver
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <SLES/OpenSLES.h>
+#include "SLES/OpenSLES_Android.h"
+#include "opensles.h"
+
+
+/**
+ * @defgroup opensles opensles
+ *
+ * Audio driver module for Android OpenSLES
+ */
+
+
+SLObjectItf engineObject = NULL;
+SLEngineItf engineEngine;
+
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+static int module_init(void)
+{
+ SLEngineOption engineOption[] = {
+ { (SLuint32) SL_ENGINEOPTION_THREADSAFE,
+ (SLuint32) SL_BOOLEAN_TRUE },
+ };
+ SLresult r;
+ int err;
+
+ r = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
+ &engineEngine);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ err = auplay_register(&auplay, baresip_auplayl(),
+ "opensles", opensles_player_alloc);
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "opensles", opensles_recorder_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ auplay = mem_deref(auplay);
+ ausrc = mem_deref(ausrc);
+
+ if (engineObject != NULL) {
+ (*engineObject)->Destroy(engineObject);
+ engineObject = NULL;
+ engineEngine = NULL;
+ }
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(opensles) = {
+ "opensles",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/opensles/opensles.h b/modules/opensles/opensles.h
new file mode 100644
index 0000000..a3641e7
--- /dev/null
+++ b/modules/opensles/opensles.h
@@ -0,0 +1,18 @@
+/**
+ * @file opensles.h OpenSLES audio driver -- internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+extern SLObjectItf engineObject;
+extern SLEngineItf engineEngine;
+
+
+int opensles_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int opensles_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/opensles/player.c b/modules/opensles/player.c
new file mode 100644
index 0000000..59964f0
--- /dev/null
+++ b/modules/opensles/player.c
@@ -0,0 +1,203 @@
+/**
+ * @file opensles/player.c OpenSLES audio driver -- playback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <SLES/OpenSLES.h>
+#include "SLES/OpenSLES_Android.h"
+#include "opensles.h"
+
+
+#define N_PLAY_QUEUE_BUFFERS 2
+#define PTIME 10
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ auplay_write_h *wh;
+ void *arg;
+ int16_t *sampv[N_PLAY_QUEUE_BUFFERS];
+ size_t sampc;
+ uint8_t bufferId;
+
+ SLObjectItf outputMixObject;
+ SLObjectItf bqPlayerObject;
+ SLPlayItf bqPlayerPlay;
+ SLAndroidSimpleBufferQueueItf BufferQueue;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ if (st->bqPlayerObject != NULL)
+ (*st->bqPlayerObject)->Destroy(st->bqPlayerObject);
+
+ if (st->outputMixObject != NULL)
+ (*st->outputMixObject)->Destroy(st->outputMixObject);
+
+ st->bufferId = 0;
+ for (int i=0; i<N_PLAY_QUEUE_BUFFERS; i++) {
+ mem_deref(st->sampv[i]);
+ }
+}
+
+
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
+{
+ struct auplay_st *st = context;
+
+ st->wh(st->sampv[st->bufferId], st->sampc, st->arg);
+
+ (*st->BufferQueue)->Enqueue(bq /*st->BufferQueue*/,
+ st->sampv[st->bufferId], st->sampc * 2);
+
+ st->bufferId = ( st->bufferId + 1 ) % N_PLAY_QUEUE_BUFFERS;
+}
+
+
+static int createOutput(struct auplay_st *st)
+{
+ const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
+ const SLboolean req[1] = {SL_BOOLEAN_FALSE};
+ SLresult r;
+
+ r = (*engineEngine)->CreateOutputMix(engineEngine,
+ &st->outputMixObject, 1, ids, req);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->outputMixObject)->Realize(st->outputMixObject,
+ SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ return 0;
+}
+
+
+static int createPlayer(struct auplay_st *st, struct auplay_prm *prm)
+{
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
+ };
+ uint32_t ch_mask = prm->ch == 2
+ ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT
+ : SL_SPEAKER_FRONT_CENTER;
+ SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch,
+ prm->srate * 1000,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ ch_mask,
+ SL_BYTEORDER_LITTLEENDIAN};
+ SLDataSource audioSrc = {&loc_bufq, &format_pcm};
+ SLDataLocator_OutputMix loc_outmix = {
+ SL_DATALOCATOR_OUTPUTMIX, st->outputMixObject
+ };
+ SLDataSink audioSnk = {&loc_outmix, NULL};
+ const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND};
+ const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+ SLresult r;
+
+ r = (*engineEngine)->CreateAudioPlayer(engineEngine,
+ &st->bqPlayerObject,
+ &audioSrc, &audioSnk,
+ ARRAY_SIZE(ids), ids, req);
+ if (SL_RESULT_SUCCESS != r) {
+ warning("opensles: CreateAudioPlayer error: r = %d\n", r);
+ return ENODEV;
+ }
+
+ r = (*st->bqPlayerObject)->Realize(st->bqPlayerObject,
+ SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject,
+ SL_IID_PLAY,
+ &st->bqPlayerPlay);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->bqPlayerObject)->GetInterface(st->bqPlayerObject,
+ SL_IID_BUFFERQUEUE,
+ &st->BufferQueue);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->BufferQueue)->RegisterCallback(st->BufferQueue,
+ bqPlayerCallback, st);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->bqPlayerPlay)->SetPlayState(st->bqPlayerPlay,
+ SL_PLAYSTATE_PLAYING);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ return 0;
+}
+
+
+int opensles_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err;
+ (void)device;
+
+ if (!stp || !ap || !prm || !wh)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("opensles: player: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ debug("opensles: opening player %uHz, %uchannels\n",
+ prm->srate, prm->ch);
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * PTIME / 1000;
+
+ st->bufferId = 0;
+ for (int i=0; i<N_PLAY_QUEUE_BUFFERS; i++) {
+ st->sampv[i] = mem_zalloc(2 * st->sampc, NULL);
+ if (!st->sampv[i]) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ err = createOutput(st);
+ if (err)
+ goto out;
+
+ err = createPlayer(st, prm);
+ if (err)
+ goto out;
+
+ /* kick-start the buffer callback */
+ bqPlayerCallback(st->BufferQueue, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/opensles/recorder.c b/modules/opensles/recorder.c
new file mode 100644
index 0000000..b26190b
--- /dev/null
+++ b/modules/opensles/recorder.c
@@ -0,0 +1,214 @@
+/**
+ * @file opensles/recorder.c OpenSLES audio driver -- recording
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <string.h>
+#include <SLES/OpenSLES.h>
+#include "SLES/OpenSLES_Android.h"
+#include "opensles.h"
+
+
+#define N_REC_QUEUE_BUFFERS 2
+#define PTIME 10
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+
+ int16_t *sampv[N_REC_QUEUE_BUFFERS];
+ size_t sampc;
+ uint8_t bufferId;
+ ausrc_read_h *rh;
+ void *arg;
+
+ SLObjectItf recObject;
+ SLRecordItf recRecord;
+ SLAndroidSimpleBufferQueueItf recBufferQueue;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->recObject != NULL) {
+ SLuint32 state;
+
+ if (SL_OBJECT_STATE_UNREALIZED !=
+ (*st->recObject)->GetState(st->recObject,&state)) {
+ (*st->recObject)->Destroy(st->recObject);
+ }
+ }
+
+ st->bufferId = 0;
+ for (int i=0; i<N_REC_QUEUE_BUFFERS; i++) {
+ mem_deref(st->sampv[i]);
+ }
+}
+
+
+static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
+{
+ struct ausrc_st *st = context;
+
+ (void)bq;
+
+ st->rh(st->sampv[st->bufferId], st->sampc, st->arg);
+
+ st->bufferId = ( st->bufferId + 1 ) % N_REC_QUEUE_BUFFERS;
+
+ memset(st->sampv[st->bufferId], 0, st->sampc * 2);
+
+ (*st->recBufferQueue)->Enqueue(st->recBufferQueue,
+ st->sampv[st->bufferId],
+ st->sampc * 2);
+}
+
+
+static int createAudioRecorder(struct ausrc_st *st, struct ausrc_prm *prm)
+{
+ SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
+ SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT,
+ NULL};
+ SLDataSource audioSrc = {&loc_dev, NULL};
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
+ };
+ int speakers = prm->ch > 1
+ ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT
+ : SL_SPEAKER_FRONT_CENTER;
+ SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, prm->ch,
+ prm->srate * 1000,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16,
+ speakers,
+ SL_BYTEORDER_LITTLEENDIAN};
+ SLDataSink audioSnk = {&loc_bq, &format_pcm};
+ const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
+ const SLboolean req[1] = {SL_BOOLEAN_TRUE};
+ SLresult r;
+
+ r = (*engineEngine)->CreateAudioRecorder(engineEngine,
+ &st->recObject,
+ &audioSrc,
+ &audioSnk, 1, id, req);
+ if (SL_RESULT_SUCCESS != r) {
+ warning("opensles: CreateAudioRecorder failed: r = %d\n", r);
+ return ENODEV;
+ }
+
+ r = (*st->recObject)->Realize(st->recObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->recObject)->GetInterface(st->recObject, SL_IID_RECORD,
+ &st->recRecord);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->recObject)->GetInterface(st->recObject,
+ SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &st->recBufferQueue);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->recBufferQueue)->RegisterCallback(st->recBufferQueue,
+ bqRecorderCallback,
+ st);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ return 0;
+}
+
+
+static int startRecording(struct ausrc_st *st)
+{
+ SLresult r;
+
+ (*st->recRecord)->SetRecordState(st->recRecord,
+ SL_RECORDSTATE_STOPPED);
+ (*st->recBufferQueue)->Clear(st->recBufferQueue);
+
+ st->bufferId = 0;
+ r = (*st->recBufferQueue)->Enqueue(st->recBufferQueue,
+ st->sampv[st->bufferId],
+ st->sampc * 2);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ r = (*st->recRecord)->SetRecordState(st->recRecord,
+ SL_RECORDSTATE_RECORDING);
+ if (SL_RESULT_SUCCESS != r)
+ return ENODEV;
+
+ return 0;
+}
+
+
+int opensles_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("opensles: record: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ debug("opensles: opening recorder %uHz, %uchannels\n",
+ prm->srate, prm->ch);
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * PTIME / 1000;
+ st->bufferId = 0;
+ for (int i=0; i<N_REC_QUEUE_BUFFERS; i++) {
+ st->sampv[i] = mem_zalloc(2 * st->sampc, NULL);
+ if (!st->sampv[i]) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ err = createAudioRecorder(st, prm);
+ if (err)
+ goto out;
+
+ err = startRecording(st);
+ if (err) {
+ warning("opensles: failed to start recorder\n");
+ goto out;
+ }
+
+ out:
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/opus/decode.c b/modules/opus/decode.c
new file mode 100644
index 0000000..f2d67b1
--- /dev/null
+++ b/modules/opus/decode.c
@@ -0,0 +1,101 @@
+/**
+ * @file opus/decode.c Opus Decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <opus/opus.h>
+#include "opus.h"
+
+
+struct audec_state {
+ OpusDecoder *dec;
+ unsigned ch;
+};
+
+
+static void destructor(void *arg)
+{
+ struct audec_state *ads = arg;
+
+ if (ads->dec)
+ opus_decoder_destroy(ads->dec);
+}
+
+
+int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp)
+{
+ struct audec_state *ads;
+ int opuserr, err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac || !ac->ch)
+ return EINVAL;
+
+ ads = *adsp;
+
+ if (ads)
+ return 0;
+
+ ads = mem_zalloc(sizeof(*ads), destructor);
+ if (!ads)
+ return ENOMEM;
+
+ ads->ch = ac->ch;
+
+ ads->dec = opus_decoder_create(ac->srate, ac->ch, &opuserr);
+ if (!ads->dec) {
+ warning("opus: decoder create: %s\n", opus_strerror(opuserr));
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(ads);
+ else
+ *adsp = ads;
+
+ return err;
+}
+
+
+int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int n;
+
+ if (!ads || !sampv || !sampc || !buf)
+ return EINVAL;
+
+ n = opus_decode(ads->dec, buf, (opus_int32)len,
+ sampv, (int)(*sampc/ads->ch), 0);
+ if (n < 0) {
+ warning("opus: decode error: %s\n", opus_strerror(n));
+ return EPROTO;
+ }
+
+ *sampc = n * ads->ch;
+
+ return 0;
+}
+
+
+int opus_decode_pkloss(struct audec_state *ads, int16_t *sampv, size_t *sampc)
+{
+ int n;
+
+ if (!ads || !sampv || !sampc)
+ return EINVAL;
+
+ n = opus_decode(ads->dec, NULL, 0, sampv, (int)(*sampc/ads->ch), 0);
+ if (n < 0)
+ return EPROTO;
+
+ *sampc = n * ads->ch;
+
+ return 0;
+}
diff --git a/modules/opus/encode.c b/modules/opus/encode.c
new file mode 100644
index 0000000..7ee1ca2
--- /dev/null
+++ b/modules/opus/encode.c
@@ -0,0 +1,187 @@
+/**
+ * @file opus/encode.c Opus Encode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <opus/opus.h>
+#include "opus.h"
+
+
+struct auenc_state {
+ OpusEncoder *enc;
+ unsigned ch;
+};
+
+
+static void destructor(void *arg)
+{
+ struct auenc_state *aes = arg;
+
+ if (aes->enc)
+ opus_encoder_destroy(aes->enc);
+}
+
+
+static opus_int32 srate2bw(opus_int32 srate)
+{
+ if (srate >= 48000)
+ return OPUS_BANDWIDTH_FULLBAND;
+ else if (srate >= 24000)
+ return OPUS_BANDWIDTH_SUPERWIDEBAND;
+ else if (srate >= 16000)
+ return OPUS_BANDWIDTH_WIDEBAND;
+ else if (srate >= 12000)
+ return OPUS_BANDWIDTH_MEDIUMBAND;
+ else
+ return OPUS_BANDWIDTH_NARROWBAND;
+}
+
+
+#if 0
+static const char *bwname(opus_int32 bw)
+{
+ switch (bw) {
+ case OPUS_BANDWIDTH_FULLBAND: return "full";
+ case OPUS_BANDWIDTH_SUPERWIDEBAND: return "superwide";
+ case OPUS_BANDWIDTH_WIDEBAND: return "wide";
+ case OPUS_BANDWIDTH_MEDIUMBAND: return "medium";
+ case OPUS_BANDWIDTH_NARROWBAND: return "narrow";
+ default: return "???";
+ }
+}
+
+
+static const char *chname(opus_int32 ch)
+{
+ switch (ch) {
+ case OPUS_AUTO: return "auto";
+ case 1: return "mono";
+ case 2: return "stereo";
+ default: return "???";
+ }
+}
+#endif
+
+
+int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *param, const char *fmtp)
+{
+ struct auenc_state *aes;
+ struct opus_param prm, conf_prm;
+ opus_int32 fch, vbr;
+ const struct aucodec *auc = ac;
+
+ (void)param;
+
+ if (!aesp || !ac || !ac->ch)
+ return EINVAL;
+
+ debug("opus: encoder fmtp (%s)\n", fmtp);
+
+ /* Save the incoming OPUS parameters from SDP offer */
+ if (str_isset(fmtp)) {
+ opus_mirror_params(fmtp);
+ }
+
+ aes = *aesp;
+
+ if (!aes) {
+ const opus_int32 complex = 10;
+ int opuserr;
+
+ aes = mem_zalloc(sizeof(*aes), destructor);
+ if (!aes)
+ return ENOMEM;
+
+ aes->ch = ac->ch;
+
+ aes->enc = opus_encoder_create(ac->srate, ac->ch,
+ /* this has big impact on cpu */
+ OPUS_APPLICATION_AUDIO,
+ &opuserr);
+ if (!aes->enc) {
+ warning("opus: encoder create: %s\n",
+ opus_strerror(opuserr));
+ mem_deref(aes);
+ return ENOMEM;
+ }
+
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_COMPLEXITY(complex));
+
+ *aesp = aes;
+ }
+
+ prm.srate = 48000;
+ prm.bitrate = OPUS_AUTO;
+ prm.stereo = 1;
+ prm.cbr = 0;
+ prm.inband_fec = 0;
+ prm.dtx = 0;
+
+ opus_decode_fmtp(&prm, fmtp);
+
+ conf_prm.bitrate = OPUS_AUTO;
+ opus_decode_fmtp(&conf_prm, auc->fmtp);
+
+ if ((prm.bitrate == OPUS_AUTO) ||
+ ((conf_prm.bitrate != OPUS_AUTO) &&
+ (conf_prm.bitrate < prm.bitrate)))
+ prm.bitrate = conf_prm.bitrate;
+
+ fch = prm.stereo ? OPUS_AUTO : 1;
+ vbr = prm.cbr ? 0 : 1;
+
+ (void)opus_encoder_ctl(aes->enc,
+ OPUS_SET_MAX_BANDWIDTH(srate2bw(prm.srate)));
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_BITRATE(prm.bitrate));
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_FORCE_CHANNELS(fch));
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_VBR(vbr));
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_INBAND_FEC(prm.inband_fec));
+ (void)opus_encoder_ctl(aes->enc, OPUS_SET_DTX(prm.dtx));
+
+
+#if 0
+ {
+ opus_int32 bw, complex;
+
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_MAX_BANDWIDTH(&bw));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_BITRATE(&prm.bitrate));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_FORCE_CHANNELS(&fch));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_VBR(&vbr));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_INBAND_FEC(&prm.inband_fec));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_DTX(&prm.dtx));
+ (void)opus_encoder_ctl(aes->enc, OPUS_GET_COMPLEXITY(&complex));
+
+ debug("opus: encode bw=%s bitrate=%i fch=%s "
+ "vbr=%i fec=%i dtx=%i complex=%i\n",
+ bwname(bw), prm.bitrate, chname(fch),
+ vbr, prm.inband_fec, prm.dtx, complex);
+ }
+#endif
+
+ return 0;
+}
+
+
+int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ opus_int32 n;
+
+ if (!aes || !buf || !len || !sampv)
+ return EINVAL;
+
+ n = opus_encode(aes->enc, sampv, (int)(sampc/aes->ch),
+ buf, (opus_int32)(*len));
+ if (n < 0) {
+ warning("opus: encode error: %s\n", opus_strerror((int)n));
+ return EPROTO;
+ }
+
+ *len = n;
+
+ return 0;
+}
diff --git a/modules/opus/module.mk b/modules/opus/module.mk
new file mode 100644
index 0000000..ded0f13
--- /dev/null
+++ b/modules/opus/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := opus
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += opus.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_LFLAGS += -lopus -lm
+
+include mk/mod.mk
diff --git a/modules/opus/opus.c b/modules/opus/opus.c
new file mode 100644
index 0000000..7acccec
--- /dev/null
+++ b/modules/opus/opus.c
@@ -0,0 +1,173 @@
+/**
+ * @file opus.c Opus Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <opus/opus.h>
+#include "opus.h"
+
+
+/**
+ * @defgroup opus opus
+ *
+ * The OPUS audio codec
+ *
+ * Supported version: libopus 1.0.0 or later
+ *
+ * Configuration options:
+ *
+ \verbatim
+ opus_bitrate 128000 # Average bitrate in [bps]
+ opus_cbr {yes,no} # Constant Bitrate (inverse of VBR)
+ opus_inbandfec {yes,no} # Enable inband Forward Error Correction (FEC)
+ opus_dtx {yes,no} # Enable Discontinuous Transmission (DTX)
+ \endverbatim
+ *
+ * References:
+ *
+ * RFC 6716 Definition of the Opus Audio Codec
+ * RFC 7587 RTP Payload Format for the Opus Speech and Audio Codec
+ *
+ * http://opus-codec.org/downloads/
+ */
+
+
+static bool opus_mirror;
+static char fmtp[256] = "";
+static char fmtp_mirror[256];
+
+
+static int opus_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ bool mirror;
+
+ (void)arg;
+ (void)offer;
+
+ if (!mb || !fmt)
+ return 0;
+
+ mirror = !offer && str_isset(fmtp_mirror);
+
+ return mbuf_printf(mb, "a=fmtp:%s %s\r\n",
+ fmt->id, mirror ? fmtp_mirror : fmtp);
+}
+
+
+static struct aucodec opus = {
+ .name = "opus",
+ .srate = 48000,
+ .crate = 48000,
+ .ch = 2,
+ .fmtp = fmtp,
+ .encupdh = opus_encode_update,
+ .ench = opus_encode_frm,
+ .decupdh = opus_decode_update,
+ .dech = opus_decode_frm,
+ .plch = opus_decode_pkloss,
+};
+
+
+void opus_mirror_params(const char *x)
+{
+ if (!opus_mirror)
+ return;
+
+ info("opus: mirror parameters: \"%s\"\n", x);
+
+ str_ncpy(fmtp_mirror, x, sizeof(fmtp_mirror));
+}
+
+
+static int module_init(void)
+{
+ struct conf *conf = conf_cur();
+ uint32_t value;
+ char *p = fmtp + str_len(fmtp);
+ bool b, stereo = true, sprop_stereo = true;
+ int n = 0;
+
+ conf_get_bool(conf, "opus_stereo", &stereo);
+ conf_get_bool(conf, "opus_sprop_stereo", &sprop_stereo);
+
+ /* always set stereo parameter first */
+ n = re_snprintf(p, sizeof(fmtp) - str_len(p),
+ "stereo=%d;sprop-stereo=%d", stereo, sprop_stereo);
+ if (n <= 0)
+ return ENOMEM;
+
+ p += n;
+
+ if (0 == conf_get_u32(conf, "opus_bitrate", &value)) {
+
+ n = re_snprintf(p, sizeof(fmtp) - str_len(p),
+ ";maxaveragebitrate=%d", value);
+ if (n <= 0)
+ return ENOMEM;
+
+ p += n;
+ }
+
+ if (0 == conf_get_bool(conf, "opus_cbr", &b)) {
+
+ n = re_snprintf(p, sizeof(fmtp) - str_len(p),
+ ";cbr=%d", b);
+ if (n <= 0)
+ return ENOMEM;
+
+ p += n;
+ }
+
+ if (0 == conf_get_bool(conf, "opus_inbandfec", &b)) {
+
+ n = re_snprintf(p, sizeof(fmtp) - str_len(p),
+ ";useinbandfec=%d", b);
+ if (n <= 0)
+ return ENOMEM;
+
+ p += n;
+ }
+
+ if (0 == conf_get_bool(conf, "opus_dtx", &b)) {
+
+ n = re_snprintf(p, sizeof(fmtp) - str_len(p),
+ ";usedtx=%d", b);
+ if (n <= 0)
+ return ENOMEM;
+
+ p += n;
+ }
+
+ (void)conf_get_bool(conf, "opus_mirror", &opus_mirror);
+
+ if (opus_mirror) {
+ opus.fmtp = NULL;
+ opus.fmtp_ench = opus_fmtp_enc;
+ }
+
+ debug("opus: fmtp=\"%s\"\n", fmtp);
+
+ aucodec_register(baresip_aucodecl(), &opus);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aucodec_unregister(&opus);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(opus) = {
+ "opus",
+ "audio codec",
+ module_init,
+ module_close,
+};
diff --git a/modules/opus/opus.h b/modules/opus/opus.h
new file mode 100644
index 0000000..d521652
--- /dev/null
+++ b/modules/opus/opus.h
@@ -0,0 +1,37 @@
+/**
+ * @file opus.h Private Opus Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct opus_param {
+ opus_int32 srate;
+ opus_int32 bitrate;
+ opus_int32 stereo;
+ opus_int32 cbr;
+ opus_int32 inband_fec;
+ opus_int32 dtx;
+};
+
+
+/* Encode */
+int opus_encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp);
+int opus_encode_frm(struct auenc_state *aes, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc);
+
+
+/* Decode */
+int opus_decode_update(struct audec_state **adsp, const struct aucodec *ac,
+ const char *fmtp);
+int opus_decode_frm(struct audec_state *ads, int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len);
+int opus_decode_pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc);
+
+
+/* SDP */
+void opus_decode_fmtp(struct opus_param *prm, const char *fmtp);
+
+
+void opus_mirror_params(const char *fmtp);
diff --git a/modules/opus/sdp.c b/modules/opus/sdp.c
new file mode 100644
index 0000000..024c8a6
--- /dev/null
+++ b/modules/opus/sdp.c
@@ -0,0 +1,51 @@
+/**
+ * @file opus/sdp.c Opus SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include <opus/opus.h>
+#include "opus.h"
+
+
+static void assign_if(opus_int32 *v, const struct pl *pl,
+ uint32_t min, uint32_t max)
+{
+ const uint32_t val = pl_u32(pl);
+
+ if (val < min || val > max)
+ return;
+
+ *v = val;
+}
+
+
+void opus_decode_fmtp(struct opus_param *prm, const char *fmtp)
+{
+ struct pl pl, val;
+
+ if (!prm || !fmtp)
+ return;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "maxplaybackrate", &val))
+ assign_if(&prm->srate, &val, 8000, 48000);
+
+ if (fmt_param_get(&pl, "maxaveragebitrate", &val))
+ assign_if(&prm->bitrate, &val, 6000, 510000);
+
+ if (fmt_param_get(&pl, "stereo", &val))
+ assign_if(&prm->stereo, &val, 0, 1);
+
+ if (fmt_param_get(&pl, "cbr", &val))
+ assign_if(&prm->cbr, &val, 0, 1);
+
+ if (fmt_param_get(&pl, "useinbandfec", &val))
+ assign_if(&prm->inband_fec, &val, 0, 1);
+
+ if (fmt_param_get(&pl, "usedtx", &val))
+ assign_if(&prm->dtx, &val, 0, 1);
+}
diff --git a/modules/oss/module.mk b/modules/oss/module.mk
new file mode 100644
index 0000000..dd4b09a
--- /dev/null
+++ b/modules/oss/module.mk
@@ -0,0 +1,18 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := oss
+$(MOD)_SRCS += oss.c
+$(MOD)_LFLAGS +=
+
+ifeq ($(OS), openbsd)
+$(MOD)_LFLAGS += -lossaudio
+endif
+ifeq ($(OS), netbsd)
+$(MOD)_LFLAGS += -lossaudio
+endif
+
+include mk/mod.mk
diff --git a/modules/oss/oss.c b/modules/oss/oss.c
new file mode 100644
index 0000000..0d47dd1
--- /dev/null
+++ b/modules/oss/oss.c
@@ -0,0 +1,364 @@
+/**
+ * @file oss.c Open Sound System (OSS) driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#if defined(NETBSD) || defined(OPENBSD)
+#include <soundcard.h>
+#elif defined (LINUX)
+#include <linux/soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#ifdef SOLARIS
+#include <sys/filio.h>
+#endif
+
+
+/**
+ * @defgroup oss oss
+ *
+ * Open Sound System (OSS) audio driver module
+ *
+ *
+ * References:
+ *
+ * http://www.4front-tech.com/linux.html
+ */
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ pthread_t thread;
+ bool run;
+ int fd;
+ int16_t *sampv;
+ size_t sampc;
+ ausrc_read_h *rh;
+ ausrc_error_h *errh;
+ void *arg;
+};
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ pthread_t thread;
+ bool run;
+ int fd;
+ int16_t *sampv;
+ size_t sampc;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+static char oss_dev[64] = "/dev/dsp";
+
+
+/*
+ * Automatically calculate the fragment size depending on sampling rate
+ * and number of channels. More entries can be added to the table below.
+ *
+ * NOTE. Powermac 8200 and linux 2.4.18 gives:
+ * SNDCTL_DSP_SETFRAGMENT: Invalid argument
+ */
+static int set_fragment(int fd, uint32_t sampc)
+{
+ static const struct {
+ uint16_t max;
+ uint16_t size;
+ } fragv[] = {
+ {10, 7}, /* 10 x 2^7 = 1280 = 4 x 320 */
+ {15, 7}, /* 15 x 2^7 = 1920 = 6 x 320 */
+ {20, 7}, /* 20 x 2^7 = 2560 = 8 x 320 */
+ {25, 7}, /* 25 x 2^7 = 3200 = 10 x 320 */
+ {15, 8}, /* 15 x 2^8 = 3840 = 12 x 320 */
+ {20, 8}, /* 20 x 2^8 = 5120 = 16 x 320 */
+ {25, 8} /* 25 x 2^8 = 6400 = 20 x 320 */
+ };
+ size_t i;
+ const uint32_t buf_size = 2 * sampc;
+
+ for (i=0; i<ARRAY_SIZE(fragv); i++) {
+ const uint16_t frag_max = fragv[i].max;
+ const uint16_t frag_size = fragv[i].size;
+ const uint32_t fragment_size = frag_max * (1<<frag_size);
+
+ if (0 == (fragment_size%buf_size)) {
+ int fragment = (frag_max<<16) | frag_size;
+
+ if (0 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT,
+ &fragment)) {
+ return 0;
+ }
+ }
+ }
+
+ return ENODEV;
+}
+
+
+static int oss_reset(int fd, uint32_t srate, uint8_t ch, int sampc,
+ int nonblock)
+{
+ int format = AFMT_S16_NE; /* native endian */
+ int speed = srate;
+ int channels = ch;
+ int blocksize = 0;
+ int err;
+
+ err = set_fragment(fd, sampc);
+ if (err)
+ return err;
+
+ if (0 != ioctl(fd, FIONBIO, &nonblock))
+ return errno;
+ if (0 != ioctl(fd, SNDCTL_DSP_SETFMT, &format))
+ return errno;
+ if (0 != ioctl(fd, SNDCTL_DSP_CHANNELS, &channels))
+ return errno;
+ if (2 == channels) {
+ int stereo = 1;
+ if (0 != ioctl(fd, SNDCTL_DSP_STEREO, &stereo))
+ return errno;
+ }
+ if (0 != ioctl(fd, SNDCTL_DSP_SPEED, &speed))
+ return errno;
+
+ (void)ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize);
+
+ info("oss: init: %d Hz %d ch, blocksize=%d\n",
+ speed, channels, blocksize);
+
+ return 0;
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (-1 != st->fd) {
+ (void)close(st->fd);
+ }
+
+ mem_deref(st->sampv);
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (-1 != st->fd) {
+ (void)close(st->fd);
+ }
+
+ mem_deref(st->sampv);
+}
+
+
+static void *record_thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+ int n;
+
+ while (st->run) {
+
+ n = read(st->fd, st->sampv, st->sampc*2);
+ if (n <= 0)
+ continue;
+
+ st->rh(st->sampv, n/2, st->arg);
+ }
+
+ return NULL;
+}
+
+
+static void *play_thread(void *arg)
+{
+ struct auplay_st *st = arg;
+ int n;
+
+ while (st->run) {
+
+ st->wh(st->sampv, st->sampc, st->arg);
+
+ n = write(st->fd, st->sampv, st->sampc*2);
+ if (n < 0) {
+ warning("oss: write: %m\n", errno);
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+
+static int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)errh;
+
+ if (!stp || !as || !prm || prm->fmt != AUFMT_S16LE || !rh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->fd = -1;
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ if (!device)
+ device = oss_dev;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->sampv = mem_alloc(2 * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->fd = open(device, O_RDONLY);
+ if (st->fd < 0) {
+ err = errno;
+ goto out;
+ }
+
+ err = oss_reset(st->fd, prm->srate, prm->ch, st->sampc, 0);
+ if (err)
+ goto out;
+
+ st->as = as;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, record_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err;
+
+ if (!stp || !ap || !prm || prm->fmt != AUFMT_S16LE || !wh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->fd = -1;
+ st->wh = wh;
+ st->arg = arg;
+
+ if (!device)
+ device = oss_dev;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->sampv = mem_alloc(st->sampc * 2, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->fd = open(device, O_WRONLY);
+ if (st->fd < 0) {
+ err = errno;
+ goto out;
+ }
+
+ err = oss_reset(st->fd, prm->srate, prm->ch, st->sampc, 0);
+ if (err)
+ goto out;
+
+ st->ap = ap;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, play_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = ausrc_register(&ausrc, baresip_ausrcl(), "oss", src_alloc);
+ err |= auplay_register(&auplay, baresip_auplayl(), "oss", play_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(oss) = {
+ "oss",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/pcp/listener.c b/modules/pcp/listener.c
new file mode 100644
index 0000000..7747672
--- /dev/null
+++ b/modules/pcp/listener.c
@@ -0,0 +1,124 @@
+/**
+ * @file listener.c Port Control Protocol module -- multicast listener
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <re.h>
+#include <rew.h>
+#include <baresip.h>
+#include "pcp.h"
+
+
+/*
+ * Listen for incoming notifications on unicast/multicast port 5350
+ */
+
+
+struct pcp_listener {
+ struct udp_sock *us;
+ struct sa srv;
+ struct sa group;
+ pcp_msg_h *msgh;
+ void *arg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct pcp_listener *pl = arg;
+
+ if (sa_isset(&pl->group, SA_ADDR))
+ (void)udp_multicast_leave(pl->us, &pl->group);
+
+ mem_deref(pl->us);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct pcp_listener *pl = arg;
+ struct pcp_msg *msg;
+ int err;
+
+#if 0
+ if (!sa_cmp(src, &pl->srv, SA_ADDR)) {
+ debug("pcp: listener: ignore %zu bytes from non-server %J\n",
+ mb->end, src);
+ return;
+ }
+#endif
+
+ err = pcp_msg_decode(&msg, mb);
+ if (err)
+ return;
+
+ /* Validate PCP request */
+ if (!msg->hdr.resp) {
+ info("pcp: listener: ignore request from %J\n", src);
+ goto out;
+ }
+
+ if (pl->msgh)
+ pl->msgh(msg, pl->arg);
+
+ out:
+ mem_deref(msg);
+}
+
+
+int pcp_listen(struct pcp_listener **plp, const struct sa *srv,
+ pcp_msg_h *msgh, void *arg)
+{
+ struct pcp_listener *pl;
+ struct sa laddr;
+ int err;
+
+ if (!plp || !srv || !msgh)
+ return EINVAL;
+
+ pl = mem_zalloc(sizeof(*pl), destructor);
+ if (!pl)
+ return ENOMEM;
+
+ pl->srv = *srv;
+ pl->msgh = msgh;
+ pl->arg = arg;
+
+ /* note: must listen on ANY to get multicast working */
+ sa_init(&laddr, sa_af(srv));
+ sa_set_port(&laddr, PCP_PORT_CLI);
+
+ err = udp_listen(&pl->us, &laddr, udp_recv, pl);
+ if (err)
+ goto out;
+
+ switch (sa_af(&laddr)) {
+
+ case AF_INET:
+ err = sa_set_str(&pl->group, "224.0.0.1", 0);
+ break;
+
+ case AF_INET6:
+ err = sa_set_str(&pl->group, "ff02::1", 0);
+ break;
+
+ default:
+ err = EAFNOSUPPORT;
+ break;
+ }
+ if (err)
+ goto out;
+
+ err = udp_multicast_join(pl->us, &pl->group);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(pl);
+ else
+ *plp = pl;
+
+ return err;
+}
diff --git a/modules/pcp/module.mk b/modules/pcp/module.mk
new file mode 100644
index 0000000..99a7c34
--- /dev/null
+++ b/modules/pcp/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := pcp
+$(MOD)_SRCS += pcp.c listener.c
+$(MOD)_CFLAGS += -I$(SYSROOT)/local/include/rew
+$(MOD)_LFLAGS += -lrew
+
+include mk/mod.mk
diff --git a/modules/pcp/pcp.c b/modules/pcp/pcp.c
new file mode 100644
index 0000000..4fbc41c
--- /dev/null
+++ b/modules/pcp/pcp.c
@@ -0,0 +1,365 @@
+/**
+ * @file pcp.c Port Control Protocol for Media NAT-traversal
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rew.h>
+#include <baresip.h>
+#include "pcp.h"
+
+
+/**
+ * @defgroup pcp pcp
+ *
+ * Port Control Protocol (PCP)
+ *
+ * This module implements the medianat interface with PCP, which is
+ * the successor of the NAT-PMP protocol.
+ */
+
+
+enum {
+ LIFETIME = 120 /* seconds */
+};
+
+struct mnat_sess {
+ struct le le;
+ struct list medial;
+ mnat_estab_h *estabh;
+ void *arg;
+};
+
+struct mnat_media {
+
+ struct comp {
+ struct pcp_request *pcp;
+ struct mnat_media *media; /* pointer to parent */
+ unsigned id;
+ bool granted;
+ } compv[2];
+ unsigned compc;
+
+ struct le le;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ uint32_t srv_epoch;
+};
+
+
+static struct mnat *mnat;
+static struct sa pcp_srv;
+static struct list sessl;
+static struct pcp_listener *lsnr;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_unlink(&sess->le);
+ list_flush(&sess->medial);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+ unsigned i;
+
+ list_unlink(&m->le);
+
+ for (i=0; i<m->compc; i++) {
+ struct comp *comp = &m->compv[i];
+
+ mem_deref(comp->pcp);
+ }
+
+ mem_deref(m->sdpm);
+}
+
+
+static void complete(struct mnat_sess *sess, int err, const char *reason)
+{
+ mnat_estab_h *estabh = sess->estabh;
+ void *arg = sess->arg;
+
+ sess->estabh = NULL;
+
+ if (estabh) {
+ estabh(err, 0, reason, arg);
+ }
+}
+
+
+static bool all_components_granted(const struct mnat_media *m)
+{
+ unsigned i;
+
+ if (!m || !m->compc)
+ return false;
+
+ for (i=0; i<m->compc; i++) {
+ const struct comp *comp = &m->compv[i];
+ if (!comp->granted)
+ return false;
+ }
+
+ return true;
+}
+
+
+static void is_complete(struct mnat_sess *sess)
+{
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+
+ if (!all_components_granted(m))
+ return;
+ }
+
+ complete(sess, 0, "done");
+}
+
+
+/* todo: detect multiple responses */
+static void pcp_resp_handler(int err, struct pcp_msg *msg, void *arg)
+{
+ struct comp *comp = arg;
+ struct mnat_media *m = comp->media;
+ const struct pcp_map *map;
+
+ if (err) {
+ warning("pcp: mapping error: %m\n", err);
+
+ complete(m->sess, err, NULL);
+ return;
+ }
+ else if (msg->hdr.result != PCP_SUCCESS) {
+ warning("pcp: mapping error: %s\n",
+ pcp_result_name(msg->hdr.result));
+
+ re_printf("%H\n", pcp_msg_print, msg);
+
+ complete(m->sess, EPROTO, "pcp error");
+ return;
+ }
+
+ map = pcp_msg_payload(msg);
+
+ info("pcp: %s: mapping for %s:"
+ " internal_port=%u, external_addr=%J\n",
+ sdp_media_name(m->sdpm),
+ comp->id==1 ? "RTP" : "RTCP",
+ map->int_port, &map->ext_addr);
+
+ /* Update SDP media with external IP-address mapping */
+ if (comp->id == 1)
+ sdp_media_set_laddr(m->sdpm, &map->ext_addr);
+ else
+ sdp_media_set_laddr_rtcp(m->sdpm, &map->ext_addr);
+
+ comp->granted = true;
+ m->srv_epoch = msg->hdr.epoch;
+
+ is_complete(m->sess);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err = 0;
+ (void)af;
+ (void)port;
+ (void)user;
+ (void)pass;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ list_append(&sessl, &sess->le, sess);
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ struct sa laddr;
+ struct pcp_map map;
+ unsigned i;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ m->compc = sock2 ? 2 : 1;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sess = sess;
+ m->sdpm = mem_ref(sdpm);
+
+ for (i=0; i<m->compc; i++) {
+ struct comp *comp = &m->compv[i];
+
+ comp->id = i+1;
+ comp->media = m;
+
+ err = udp_local_get(i==0 ? sock1 : sock2, &laddr);
+ if (err)
+ goto out;
+
+ rand_bytes(map.nonce, sizeof(map.nonce));
+ map.proto = proto;
+ map.int_port = sa_port(&laddr);
+ /* note: using same address-family as the PCP server */
+ sa_init(&map.ext_addr, sa_af(&pcp_srv));
+
+ info("pcp: %s: internal port for %s is %u\n",
+ sdp_media_name(sdpm),
+ i==0 ? "RTP" : "RTCP",
+ map.int_port);
+
+ err = pcp_request(&comp->pcp, NULL, &pcp_srv, PCP_MAP,
+ LIFETIME, &map, pcp_resp_handler, comp, 0);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(m);
+ else if (mp) {
+ *mp = m;
+ }
+
+ return err;
+}
+
+
+static void media_refresh(struct mnat_media *media)
+{
+ unsigned i;
+
+ for (i=0; i<media->compc; i++) {
+ struct comp *comp = &media->compv[i];
+
+ pcp_force_refresh(comp->pcp);
+ }
+}
+
+
+static void refresh_session(struct mnat_sess *sess, uint32_t epoch_time)
+{
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+
+ if (epoch_time < m->srv_epoch) {
+ info("pcp: detected PCP Server reboot!\n");
+ media_refresh(m);
+ }
+
+ m->srv_epoch = epoch_time;
+ }
+}
+
+
+static void pcp_msg_handler(const struct pcp_msg *msg, void *arg)
+{
+ struct le *le;
+
+ (void)arg;
+
+ info("pcp: received notification: %H\n", pcp_msg_print, msg);
+
+ if (msg->hdr.opcode == PCP_ANNOUNCE) {
+
+ for (le = sessl.head; le; le = le->next) {
+
+ struct mnat_sess *sess = le->data;
+
+ refresh_session(sess, msg->hdr.epoch);
+ }
+ }
+}
+
+
+static int module_init(void)
+{
+ struct pl pl;
+ int err;
+
+ if (0 == conf_get(conf_cur(), "pcp_server", &pl)) {
+ err = sa_decode(&pcp_srv, pl.p, pl.l);
+ if (err)
+ return err;
+ }
+ else {
+ err = net_default_gateway_get(net_af(baresip_network()),
+ &pcp_srv);
+ if (err)
+ return err;
+ sa_set_port(&pcp_srv, PCP_PORT_SRV);
+ }
+
+ info("pcp: using PCP server at %J\n", &pcp_srv);
+
+ /* NOTE: if multiple applications are listening on port 5350
+ then this will not work */
+ err = pcp_listen(&lsnr, &pcp_srv, pcp_msg_handler, 0);
+ if (err) {
+ info("pcp: could not enable listener: %m\n", err);
+ err = 0;
+ }
+
+ return mnat_register(&mnat, baresip_mnatl(), "pcp", NULL,
+ session_alloc, media_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ lsnr = mem_deref(lsnr);
+ mnat = mem_deref(mnat);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(pcp) = {
+ "pcp",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/pcp/pcp.h b/modules/pcp/pcp.h
new file mode 100644
index 0000000..6f9e8a9
--- /dev/null
+++ b/modules/pcp/pcp.h
@@ -0,0 +1,15 @@
+/**
+ * @file pcp.h Port Control Protocol module -- internal interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+/* listener */
+
+struct pcp_listener;
+
+typedef void (pcp_msg_h)(const struct pcp_msg *msg, void *arg);
+
+int pcp_listen(struct pcp_listener **plp, const struct sa *srv,
+ pcp_msg_h *msgh, void *arg);
diff --git a/modules/plc/module.mk b/modules/plc/module.mk
new file mode 100644
index 0000000..bc8ae4f
--- /dev/null
+++ b/modules/plc/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := plc
+$(MOD)_SRCS += plc.c
+$(MOD)_LFLAGS += "-lspandsp"
+
+include mk/mod.mk
diff --git a/modules/plc/plc.c b/modules/plc/plc.c
new file mode 100644
index 0000000..8b220ed
--- /dev/null
+++ b/modules/plc/plc.c
@@ -0,0 +1,120 @@
+/**
+ * @file plc.c PLC -- Packet Loss Concealment
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
+
+#include <spandsp.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup plc plc
+ *
+ * Packet Loss Concealment (PLC) audio-filter using spandsp
+ *
+ */
+
+
+struct plc_st {
+ struct aufilt_dec_st af; /* base class */
+ plc_state_t plc;
+ size_t sampc;
+};
+
+
+static void destructor(void *arg)
+{
+ struct plc_st *st = arg;
+
+ list_unlink(&st->af.le);
+}
+
+
+static int update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct plc_st *st;
+ int err = 0;
+ (void)ctx;
+ (void)af;
+
+ if (!stp || !prm)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ /* XXX: add support for stereo PLC */
+ if (prm->ch != 1) {
+ warning("plc: only mono supported (ch=%u)\n", prm->ch);
+ return ENOSYS;
+ }
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (!plc_init(&st->plc)) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_dec_st *)st;
+
+ return err;
+}
+
+
+/*
+ * PLC is only valid for Decoding (RX)
+ *
+ * NOTE: sampc == 0 , means Packet loss
+ */
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct plc_st *plc = (struct plc_st *)st;
+
+ if (*sampc)
+ plc_rx(&plc->plc, sampv, (int)*sampc);
+ else
+ *sampc = plc_fillin(&plc->plc, sampv, (int)plc->sampc);
+
+ return 0;
+}
+
+
+static struct aufilt plc = {
+ LE_INIT, "plc", NULL, NULL, update, decode
+};
+
+
+static int module_init(void)
+{
+ aufilt_register(baresip_aufiltl(), &plc);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&plc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(plc) = {
+ "plc",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/portaudio/module.mk b/modules/portaudio/module.mk
new file mode 100644
index 0000000..81e5b80
--- /dev/null
+++ b/modules/portaudio/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := portaudio
+$(MOD)_SRCS += portaudio.c
+$(MOD)_LFLAGS += -lportaudio
+
+include mk/mod.mk
diff --git a/modules/portaudio/portaudio.c b/modules/portaudio/portaudio.c
new file mode 100644
index 0000000..b856502
--- /dev/null
+++ b/modules/portaudio/portaudio.c
@@ -0,0 +1,346 @@
+/**
+ * @file portaudio.c Portaudio sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <portaudio.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup portaudio portaudio
+ *
+ * Portaudio audio driver
+ *
+ * (portaudio v19 is required)
+ *
+ *
+ * References:
+ *
+ * http://www.portaudio.com/
+ */
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ PaStream *stream_rd;
+ ausrc_read_h *rh;
+ void *arg;
+ volatile bool ready;
+ unsigned ch;
+};
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ PaStream *stream_wr;
+ auplay_write_h *wh;
+ void *arg;
+ volatile bool ready;
+ unsigned ch;
+};
+
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+
+/*
+ * This routine will be called by the PortAudio engine when audio is needed.
+ * It may called at interrupt level on some machines so don't do anything
+ * that could mess up the system like calling malloc() or free().
+ */
+static int read_callback(const void *inputBuffer, void *outputBuffer,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo *timeInfo,
+ PaStreamCallbackFlags statusFlags, void *userData)
+{
+ struct ausrc_st *st = userData;
+ size_t sampc;
+
+ (void)outputBuffer;
+ (void)timeInfo;
+ (void)statusFlags;
+
+ if (!st->ready)
+ return paAbort;
+
+ sampc = frameCount * st->ch;
+
+ st->rh(inputBuffer, sampc, st->arg);
+
+ return paContinue;
+}
+
+
+static int write_callback(const void *inputBuffer, void *outputBuffer,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo *timeInfo,
+ PaStreamCallbackFlags statusFlags, void *userData)
+{
+ struct auplay_st *st = userData;
+ size_t sampc;
+
+ (void)inputBuffer;
+ (void)timeInfo;
+ (void)statusFlags;
+
+ if (!st->ready)
+ return paAbort;
+
+ sampc = frameCount * st->ch;
+
+ st->wh(outputBuffer, sampc, st->arg);
+
+ return paContinue;
+}
+
+
+static PaSampleFormat aufmt_to_pasampleformat(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return paInt16;
+ case AUFMT_FLOAT: return paFloat32;
+ default: return 0;
+ }
+}
+
+
+static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm,
+ uint32_t dev)
+{
+ PaStreamParameters prm_in;
+ PaError err;
+ unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000;
+
+ memset(&prm_in, 0, sizeof(prm_in));
+ prm_in.device = dev;
+ prm_in.channelCount = prm->ch;
+ prm_in.sampleFormat = aufmt_to_pasampleformat(prm->fmt);
+ prm_in.suggestedLatency = 0.100;
+
+ st->stream_rd = NULL;
+ err = Pa_OpenStream(&st->stream_rd, &prm_in, NULL, prm->srate,
+ frames_per_buffer, paNoFlag, read_callback, st);
+ if (paNoError != err) {
+ warning("portaudio: read: Pa_OpenStream: %s\n",
+ Pa_GetErrorText(err));
+ return EINVAL;
+ }
+
+ err = Pa_StartStream(st->stream_rd);
+ if (paNoError != err) {
+ warning("portaudio: read: Pa_StartStream: %s\n",
+ Pa_GetErrorText(err));
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int write_stream_open(struct auplay_st *st,
+ const struct auplay_prm *prm, uint32_t dev)
+{
+ PaStreamParameters prm_out;
+ PaError err;
+ unsigned long frames_per_buffer = prm->srate * prm->ptime / 1000;
+
+ memset(&prm_out, 0, sizeof(prm_out));
+ prm_out.device = dev;
+ prm_out.channelCount = prm->ch;
+ prm_out.sampleFormat = aufmt_to_pasampleformat(prm->fmt);
+ prm_out.suggestedLatency = 0.100;
+
+ st->stream_wr = NULL;
+ err = Pa_OpenStream(&st->stream_wr, NULL, &prm_out, prm->srate,
+ frames_per_buffer, paNoFlag, write_callback, st);
+ if (paNoError != err) {
+ warning("portaudio: write: Pa_OpenStream: %s\n",
+ Pa_GetErrorText(err));
+ return EINVAL;
+ }
+
+ err = Pa_StartStream(st->stream_wr);
+ if (paNoError != err) {
+ warning("portaudio: write: Pa_StartStream: %s\n",
+ Pa_GetErrorText(err));
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ st->ready = false;
+
+ if (st->stream_rd) {
+ Pa_AbortStream(st->stream_rd);
+ Pa_CloseStream(st->stream_rd);
+ }
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ st->ready = false;
+
+ if (st->stream_wr) {
+ Pa_AbortStream(st->stream_wr);
+ Pa_CloseStream(st->stream_wr);
+ }
+}
+
+
+static int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ PaDeviceIndex dev_index;
+ int err;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ if (str_isset(device))
+ dev_index = atoi(device);
+ else
+ dev_index = Pa_GetDefaultInputDevice();
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+ st->ch = prm->ch;
+
+ st->ready = true;
+
+ err = read_stream_open(st, prm, dev_index);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ PaDeviceIndex dev_index;
+ int err;
+
+ (void)device;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ if (str_isset(device))
+ dev_index = atoi(device);
+ else
+ dev_index = Pa_GetDefaultOutputDevice();
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+ st->ch = prm->ch;
+
+ st->ready = true;
+
+ err = write_stream_open(st, prm, dev_index);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int pa_init(void)
+{
+ PaError paerr;
+ int i, n, err = 0;
+
+ paerr = Pa_Initialize();
+ if (paNoError != paerr) {
+ warning("portaudio: init: %s\n", Pa_GetErrorText(paerr));
+ return ENODEV;
+ }
+
+ n = Pa_GetDeviceCount();
+
+ info("portaudio: device count is %d\n", n);
+
+ for (i=0; i<n; i++) {
+ const PaDeviceInfo *devinfo;
+
+ devinfo = Pa_GetDeviceInfo(i);
+
+ debug("portaudio: device %d: %s\n", i, devinfo->name);
+ (void)devinfo;
+ }
+
+ if (paNoDevice != Pa_GetDefaultInputDevice())
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "portaudio", src_alloc);
+
+ if (paNoDevice != Pa_GetDefaultOutputDevice())
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "portaudio", play_alloc);
+
+ return err;
+}
+
+
+static int pa_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ Pa_Terminate();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(portaudio) = {
+ "portaudio",
+ "sound",
+ pa_init,
+ pa_close
+};
diff --git a/modules/presence/module.mk b/modules/presence/module.mk
new file mode 100644
index 0000000..b9b6a22
--- /dev/null
+++ b/modules/presence/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := presence
+$(MOD)_SRCS += presence.c subscriber.c notifier.c publisher.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/presence/notifier.c b/modules/presence/notifier.c
new file mode 100644
index 0000000..7fc0a67
--- /dev/null
+++ b/modules/presence/notifier.c
@@ -0,0 +1,219 @@
+/**
+ * @file notifier.c Presence notifier
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "presence.h"
+
+
+/*
+ * Notifier - other people are subscribing to the status of our AOR.
+ * we must maintain a list of active notifications. we receive a SUBSCRIBE
+ * message from peer, and send NOTIFY to all peers when the Status changes
+ */
+
+
+struct notifier {
+ struct le le;
+ struct sipnot *not;
+ struct ua *ua;
+};
+
+static struct list notifierl;
+
+
+static const char *presence_status_str(enum presence_status st)
+{
+ switch (st) {
+
+ case PRESENCE_OPEN: return "open";
+ case PRESENCE_CLOSED: return "closed";
+ default: return "?";
+ }
+}
+
+
+static int notify(struct notifier *not, enum presence_status status)
+{
+ const char *aor = ua_aor(not->ua);
+ struct mbuf *mb;
+ int err;
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_printf(mb,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n"
+ "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\r\n"
+ " xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\"\r\n"
+ " xmlns:rpid=\"urn:ietf:params:xml:ns:pidf:rpid\"\r\n"
+ " entity=\"%s\">\r\n"
+ " <dm:person id=\"p4159\"><rpid:activities/></dm:person>\r\n"
+ " <tuple id=\"t4109\">\r\n"
+ " <status>\r\n"
+ " <basic>%s</basic>\r\n"
+ " </status>\r\n"
+ " <contact>%s</contact>\r\n"
+ " </tuple>\r\n"
+ "</presence>\r\n"
+ ,aor, presence_status_str(status), aor);
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sipevent_notify(not->not, mb, SIPEVENT_ACTIVE, 0, 0);
+ if (err) {
+ warning("presence: notify to %s failed (%m)\n", aor, err);
+ }
+
+ out:
+ mem_deref(mb);
+ return err;
+}
+
+
+static void sipnot_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct notifier *not = arg;
+
+ if (err) {
+ info("presence: notifier closed (%m)\n", err);
+ }
+ else if (msg) {
+ info("presence: notifier closed (%u %r)\n",
+ msg->scode, &msg->reason);
+ }
+
+ mem_deref(not);
+}
+
+
+static void destructor(void *arg)
+{
+ struct notifier *not = arg;
+
+ list_unlink(&not->le);
+ mem_deref(not->not);
+ mem_deref(not->ua);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ return account_auth(arg, username, password, realm);
+}
+
+
+static int notifier_alloc(struct notifier **notp,
+ const struct sip_msg *msg,
+ const struct sipevent_event *se, struct ua *ua)
+{
+ struct notifier *not;
+ int err;
+
+ if (!msg || !se)
+ return EINVAL;
+
+ not = mem_zalloc(sizeof(*not), destructor);
+ if (!not)
+ return ENOMEM;
+
+ not->ua = mem_ref(ua);
+
+ err = sipevent_accept(&not->not, uag_sipevent_sock(),
+ msg, NULL, se, 200, "OK",
+ 600, 600, 600,
+ ua_cuser(not->ua), "application/pidf+xml",
+ auth_handler, ua_account(not->ua), true,
+ sipnot_close_handler, not, NULL);
+ if (err) {
+ warning("presence: sipevent_accept failed: %m\n", err);
+ goto out;
+ }
+
+ list_append(&notifierl, &not->le, not);
+
+ out:
+ if (err)
+ mem_deref(not);
+ else if (notp)
+ *notp = not;
+
+ return err;
+}
+
+
+static int notifier_add(const struct sip_msg *msg, struct ua *ua)
+{
+ const struct sip_hdr *hdr;
+ struct sipevent_event se;
+ struct notifier *not;
+ int err;
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_EVENT);
+ if (!hdr)
+ return EPROTO;
+
+ err = sipevent_event_decode(&se, &hdr->val);
+ if (err)
+ return err;
+
+ if (pl_strcasecmp(&se.event, "presence")) {
+ info("presence: unexpected event '%r'\n", &se.event);
+ return EPROTO;
+ }
+
+ err = notifier_alloc(&not, msg, &se, ua);
+ if (err)
+ return err;
+
+ (void)notify(not, ua_presence_status(ua));
+
+ return 0;
+}
+
+
+void notifier_update_status(struct ua *ua)
+{
+ struct le *le;
+
+ for (le = notifierl.head; le; le = le->next) {
+
+ struct notifier *not = le->data;
+
+ if (not->ua == ua)
+ (void)notify(not, ua_presence_status(not->ua));
+ }
+}
+
+
+static bool sub_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+
+ if (notifier_add(msg, ua))
+ (void)sip_treply(NULL, uag_sip(), msg, 400, "Bad Presence");
+
+ return true;
+}
+
+
+int notifier_init(void)
+{
+ uag_set_sub_handler(sub_handler);
+
+ return 0;
+}
+
+
+void notifier_close(void)
+{
+ list_flush(&notifierl);
+ uag_set_sub_handler(NULL);
+}
diff --git a/modules/presence/presence.c b/modules/presence/presence.c
new file mode 100644
index 0000000..9fcdf83
--- /dev/null
+++ b/modules/presence/presence.c
@@ -0,0 +1,123 @@
+/**
+ * @file presence.c Presence module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "presence.h"
+
+
+static int status_update(struct ua *current_ua,
+ const enum presence_status new_status)
+{
+ if (ua_presence_status(current_ua) == new_status)
+ return 0;
+
+ info("presence: update status of '%s' from '%s' to '%s'\n",
+ ua_aor(current_ua),
+ contact_presence_str(ua_presence_status(current_ua)),
+ contact_presence_str(new_status));
+
+ ua_presence_status_set(current_ua, new_status);
+
+ publisher_update_status(current_ua);
+ notifier_update_status(current_ua);
+
+ return 0;
+}
+
+
+static int cmd_online(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ return status_update(uag_current(), PRESENCE_OPEN);
+}
+
+
+static int cmd_offline(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ return status_update(uag_current(), PRESENCE_CLOSED);
+}
+
+
+static const struct cmd cmdv[] = {
+ {"presence_online", '[', 0, "Set presence online", cmd_online },
+ {"presence_offline", ']', 0, "Set presence offline", cmd_offline },
+};
+
+
+static void event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ (void)call;
+ (void)prm;
+ (void)arg;
+
+ debug("presence: ua=%p got event %d (%s)\n", ua, ev,
+ uag_event_str(ev));
+
+ if (ev == UA_EVENT_SHUTDOWN) {
+
+ publisher_close();
+ notifier_close();
+ subscriber_close_all();
+ }
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = subscriber_init();
+ if (err)
+ return err;
+
+ err = publisher_init();
+ if (err)
+ return err;
+
+ err = notifier_init();
+ if (err)
+ return err;
+
+ err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+ if (err)
+ return err;
+
+ err = uag_event_register(event_handler, NULL);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ uag_event_unregister(event_handler);
+
+ cmd_unregister(baresip_commands(), cmdv);
+
+ publisher_close();
+
+ notifier_close();
+
+ subscriber_close();
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(presence) = {
+ "presence",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/presence/presence.h b/modules/presence/presence.h
new file mode 100644
index 0000000..75d8e48
--- /dev/null
+++ b/modules/presence/presence.h
@@ -0,0 +1,19 @@
+/**
+ * @file presence.h Presence module interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+int subscriber_init(void);
+void subscriber_close(void);
+void subscriber_close_all(void);
+
+
+int notifier_init(void);
+void notifier_close(void);
+void notifier_update_status(struct ua *ua);
+
+
+int publisher_init(void);
+void publisher_close(void);
+void publisher_update_status(struct ua *ua);
diff --git a/modules/presence/publisher.c b/modules/presence/publisher.c
new file mode 100644
index 0000000..f443852
--- /dev/null
+++ b/modules/presence/publisher.c
@@ -0,0 +1,309 @@
+/**
+ * @file publisher.c Presence Publisher (RFC 3903)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * Copyright (C) 2014 Juha Heinanen
+ */
+
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "presence.h"
+
+
+struct publisher {
+ struct le le;
+ struct tmr tmr;
+ unsigned failc;
+ char *etag;
+ unsigned int expires;
+ unsigned int refresh;
+ struct ua *ua;
+};
+
+static struct list publ = LIST_INIT;
+
+static void tmr_handler(void *arg);
+static int publish(struct publisher *pub);
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct publisher *pub = arg;
+ const struct sip_hdr *etag_hdr;
+
+ if (err)
+ return;
+
+ if (msg->scode < 200) {
+ return;
+ }
+
+ if (msg->scode < 300) {
+
+ if (pub->expires == 0)
+ return;
+
+ etag_hdr = sip_msg_xhdr(msg, "SIP-ETag");
+ if (etag_hdr) {
+ mem_deref(pub->etag);
+ pl_strdup(&(pub->etag), &(etag_hdr->val));
+ pub->refresh = 1;
+ tmr_start(&pub->tmr, pub->expires * 900,
+ tmr_handler, pub);
+ }
+ else {
+ warning("%s: publisher got 200 OK without etag\n",
+ ua_aor(pub->ua));
+ }
+ }
+ else if (msg->scode == 412) {
+
+ mem_deref(pub->etag);
+ pub->etag = NULL;
+ pub->refresh = 0;
+ publish(pub);
+
+ }
+ else {
+ warning("%s: publisher got error response %u %r\n",
+ ua_aor(pub->ua), msg->scode, &msg->reason);
+ }
+
+ return;
+}
+
+
+/* move this to presence.c */
+static const char *presence_status_str(enum presence_status st)
+{
+ switch (st) {
+
+ case PRESENCE_OPEN: return "open";
+ case PRESENCE_CLOSED: return "closed";
+ case PRESENCE_UNKNOWN: return "unknown";
+ default: return "?";
+ }
+}
+
+
+static int publish(struct publisher *pub)
+{
+ int err;
+ const char *aor = ua_aor(pub->ua);
+ struct mbuf *mb;
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ if (pub->expires && !pub->refresh)
+ err = mbuf_printf(mb,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n"
+ "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\r\n"
+ " xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\"\r\n"
+ " xmlns:rpid=\"urn:ietf:params:xml:ns:pidf:rpid\"\r\n"
+ " entity=\"%s\">\r\n"
+ " <dm:person id=\"p4159\"><rpid:activities/></dm:person>\r\n"
+ " <tuple id=\"t4109\">\r\n"
+ " <status>\r\n"
+ " <basic>%s</basic>\r\n"
+ " </status>\r\n"
+ " <contact>%s</contact>\r\n"
+ " </tuple>\r\n"
+ "</presence>\r\n"
+ ,aor,
+ presence_status_str(ua_presence_status(pub->ua)), aor);
+ else
+ err = mbuf_printf(mb, "");
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ /* XXX: can be simplified with 1 function call, by adding a
+ print-handler that prints "SIP-If-Match: ETAG" */
+ if (pub->etag)
+ err = sip_req_send(pub->ua, "PUBLISH", aor,
+ pub->expires ? response_handler : NULL,
+ pub,
+ "%s"
+ "Event: presence\r\n"
+ "Expires: %u\r\n"
+ "SIP-If-Match: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ pub->expires
+ ? "Content-Type: application/pidf+xml\r\n"
+ : "",
+
+ pub->expires,
+ pub->etag,
+ mbuf_get_left(mb),
+ mbuf_buf(mb),
+ mbuf_get_left(mb));
+ else
+ err = sip_req_send(pub->ua, "PUBLISH", aor,
+ pub->expires ? response_handler : NULL,
+ pub,
+ "%s"
+ "Event: presence\r\n"
+ "Expires: %u\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ pub->expires
+ ? "Content-Type: application/pidf+xml\r\n"
+ : "",
+ pub->expires,
+ mbuf_get_left(mb),
+ mbuf_buf(mb),
+ mbuf_get_left(mb));
+ if (err) {
+ warning("publisher: send PUBLISH: (%m)\n", err);
+ }
+
+out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/* move to presence.c */
+static uint32_t wait_fail(unsigned failc)
+{
+ switch (failc) {
+
+ case 1: return 30;
+ case 2: return 300;
+ case 3: return 3600;
+ default: return 86400;
+ }
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct publisher *pub = arg;
+
+ if (publish(pub))
+ tmr_start(&pub->tmr, wait_fail(++pub->failc) * 1000,
+ tmr_handler, pub);
+ else
+ pub->failc = 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct publisher *pub = arg;
+
+ list_unlink(&pub->le);
+ tmr_cancel(&pub->tmr);
+ mem_deref(pub->ua);
+ mem_deref(pub->etag);
+}
+
+
+void publisher_update_status(struct ua *ua)
+{
+ struct le *le;
+
+ for (le = publ.head; le; le = le->next) {
+
+ struct publisher *pub = le->data;
+
+ if (pub->ua == ua) {
+ pub->refresh = 0;
+ publish(pub);
+ }
+ }
+}
+
+
+static int publisher_alloc(struct ua *ua)
+{
+ struct publisher *pub;
+
+ pub = mem_zalloc(sizeof(*pub), destructor);
+ if (!pub)
+ return ENOMEM;
+
+ pub->ua = mem_ref(ua);
+ pub->expires = account_pubint(ua_account(ua));
+
+ tmr_init(&pub->tmr);
+ tmr_start(&pub->tmr, 10, tmr_handler, pub);
+
+ list_append(&publ, &pub->le, pub);
+
+ return 0;
+}
+
+
+static void pub_ua_event_handler(struct ua *ua,
+ enum ua_event ev,
+ struct call *call,
+ const char *prm,
+ void *arg )
+{
+ (void)call;
+ (void)prm;
+ (void)arg;
+
+ if (account_pubint(ua_account(ua)) == 0)
+ return;
+
+ if (ev == UA_EVENT_REGISTER_OK) {
+ if (ua_presence_status(ua) == PRESENCE_UNKNOWN) {
+ ua_presence_status_set(ua, PRESENCE_OPEN);
+ publisher_update_status(ua);
+ }
+ }
+}
+
+
+int publisher_init(void)
+{
+ struct le *le;
+ int err = 0;
+
+ uag_event_register(pub_ua_event_handler, NULL);
+
+ for (le = list_head(uag_list()); le; le = le->next) {
+
+ struct ua *ua = le->data;
+ struct account *acc = ua_account(ua);
+
+ if (account_pubint(acc) == 0)
+ continue;
+
+ err |= publisher_alloc(ua);
+ }
+
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void publisher_close(void)
+{
+ struct le *le;
+
+ uag_event_unregister(pub_ua_event_handler);
+
+ for (le = list_head(&publ); le; le = le->next) {
+
+ struct publisher *pub = le->data;
+
+ ua_presence_status_set(pub->ua, PRESENCE_CLOSED);
+ pub->expires = 0;
+ publish(pub);
+ }
+
+ list_flush(&publ);
+}
diff --git a/modules/presence/subscriber.c b/modules/presence/subscriber.c
new file mode 100644
index 0000000..11da6d4
--- /dev/null
+++ b/modules/presence/subscriber.c
@@ -0,0 +1,382 @@
+/**
+ * @file subscriber.c Presence subscriber
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "presence.h"
+
+
+/*
+ * Subscriber - we subscribe to the status information of N resources.
+ *
+ * For each entry in the address book marked with ;presence=p2p,
+ * we send a SUBSCRIBE to that person, and expect to receive
+ * a NOTIFY when her status changes.
+ */
+
+
+/** Constants */
+enum {
+ SHUTDOWN_DELAY = 500 /**< Delay before un-registering [ms] */
+};
+
+
+struct presence {
+ struct le le;
+ struct sipsub *sub;
+ struct tmr tmr;
+ enum presence_status status;
+ unsigned failc;
+ struct contact *contact;
+ struct ua *ua;
+ bool shutdown;
+};
+
+static struct list presencel;
+
+
+static void tmr_handler(void *arg);
+
+
+static uint32_t wait_term(const struct sipevent_substate *substate)
+{
+ uint32_t wait;
+
+ switch (substate->reason) {
+
+ case SIPEVENT_DEACTIVATED:
+ case SIPEVENT_TIMEOUT:
+ wait = 5;
+ break;
+
+ case SIPEVENT_REJECTED:
+ case SIPEVENT_NORESOURCE:
+ wait = 3600;
+ break;
+
+ case SIPEVENT_PROBATION:
+ case SIPEVENT_GIVEUP:
+ default:
+ wait = 300;
+ if (pl_isset(&substate->retry_after))
+ wait = max(wait, pl_u32(&substate->retry_after));
+ break;
+ }
+
+ return wait;
+}
+
+
+static uint32_t wait_fail(unsigned failc)
+{
+ switch (failc) {
+
+ case 1: return 30;
+ case 2: return 300;
+ case 3: return 3600;
+ default: return 86400;
+ }
+}
+
+
+static void notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ enum presence_status status = PRESENCE_CLOSED;
+ struct presence *pres = arg;
+ const struct sip_hdr *type_hdr, *length_hdr;
+ struct pl pl;
+
+ if (pres->shutdown)
+ goto done;
+
+ pres->failc = 0;
+
+ type_hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_TYPE);
+
+ if (!type_hdr) {
+
+ length_hdr = sip_msg_hdr(msg, SIP_HDR_CONTENT_LENGTH);
+ if (0 == pl_strcmp(&length_hdr->val, "0")) {
+
+ status = PRESENCE_UNKNOWN;
+ goto done;
+ }
+ }
+
+ if (!type_hdr ||
+ 0 != pl_strcasecmp(&type_hdr->val, "application/pidf+xml")) {
+
+ if (type_hdr)
+ warning("presence: unsupported content-type: '%r'\n",
+ &type_hdr->val);
+
+ sip_treplyf(NULL, NULL, sip, msg, false,
+ 415, "Unsupported Media Type",
+ "Accept: application/pidf+xml\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ return;
+ }
+
+ if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb),
+ "<basic[ \t]*>[^<]+</basic[ \t]*>", NULL, &pl, NULL)) {
+ if (!pl_strcasecmp(&pl, "open"))
+ status = PRESENCE_OPEN;
+ }
+
+ if (!re_regex((const char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb),
+ "<rpid:away[ \t]*/>", NULL)) {
+
+ status = PRESENCE_CLOSED;
+ }
+ else if (!re_regex((const char *)mbuf_buf(msg->mb),
+ mbuf_get_left(msg->mb),
+ "<rpid:busy[ \t]*/>", NULL)) {
+
+ status = PRESENCE_BUSY;
+ }
+ else if (!re_regex((const char *)mbuf_buf(msg->mb),
+ mbuf_get_left(msg->mb),
+ "<rpid:on-the-phone[ \t]*/>", NULL)) {
+
+ status = PRESENCE_BUSY;
+ }
+
+done:
+ (void)sip_treply(NULL, sip, msg, 200, "OK");
+
+ contact_set_presence(pres->contact, status);
+
+ if (pres->shutdown)
+ mem_deref(pres);
+}
+
+
+static void close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate, void *arg)
+{
+ struct presence *pres = arg;
+ uint32_t wait;
+
+ pres->sub = mem_deref(pres->sub);
+
+ info("presence: subscriber closed <%r>: ",
+ &contact_addr(pres->contact)->auri);
+
+ if (substate) {
+ info("%s", sipevent_reason_name(substate->reason));
+ wait = wait_term(substate);
+ }
+ else if (msg) {
+ info("%u %r", msg->scode, &msg->reason);
+ wait = wait_fail(++pres->failc);
+ }
+ else {
+ info("%m", err);
+ wait = wait_fail(++pres->failc);
+ }
+
+ info("; will retry in %u secs (failc=%u)\n", wait, pres->failc);
+
+ tmr_start(&pres->tmr, wait * 1000, tmr_handler, pres);
+
+ contact_set_presence(pres->contact, PRESENCE_UNKNOWN);
+}
+
+
+static void destructor(void *arg)
+{
+ struct presence *pres = arg;
+
+ debug("presence: subscriber destroyed\n");
+
+ list_unlink(&pres->le);
+ tmr_cancel(&pres->tmr);
+ mem_deref(pres->contact);
+ mem_deref(pres->sub);
+ mem_deref(pres->ua);
+}
+
+
+static void deref_handler(void *arg)
+{
+ struct presence *pres = arg;
+ mem_deref(pres);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ return account_auth(arg, username, password, realm);
+}
+
+
+static int subscribe(struct presence *pres)
+{
+ const char *routev[1];
+ struct ua *ua;
+ char uri[256];
+ int err;
+
+ /* We use the first UA */
+ ua = uag_find_aor(NULL);
+ if (!ua) {
+ warning("presence: no UA found\n");
+ return ENOENT;
+ }
+
+ mem_deref(pres->ua);
+ pres->ua = mem_ref(ua);
+
+ pl_strcpy(&contact_addr(pres->contact)->auri, uri, sizeof(uri));
+
+ routev[0] = ua_outbound(ua);
+
+ err = sipevent_subscribe(&pres->sub, uag_sipevent_sock(), uri, NULL,
+ ua_aor(ua), "presence", NULL, 600,
+ ua_cuser(ua), routev, routev[0] ? 1 : 0,
+ auth_handler, ua_account(ua), true, NULL,
+ notify_handler, close_handler, pres,
+ "%H", ua_print_supported, ua);
+ if (err) {
+ warning("presence: sipevent_subscribe failed: %m\n", err);
+ }
+
+ return err;
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct presence *pres = arg;
+
+ if (subscribe(pres)) {
+ tmr_start(&pres->tmr, wait_fail(++pres->failc) * 1000,
+ tmr_handler, pres);
+ }
+}
+
+
+static int presence_alloc(struct contact *contact)
+{
+ struct presence *pres;
+
+ pres = mem_zalloc(sizeof(*pres), destructor);
+ if (!pres)
+ return ENOMEM;
+
+ pres->status = PRESENCE_UNKNOWN;
+ pres->contact = mem_ref(contact);
+
+ tmr_init(&pres->tmr);
+ tmr_start(&pres->tmr, 1000, tmr_handler, pres);
+
+ list_append(&presencel, &pres->le, pres);
+
+ return 0;
+}
+
+
+static void contact_handler(struct contact *contact,
+ bool removed, void *arg)
+{
+ struct le *le;
+ struct pl val;
+ struct presence *pres = NULL;
+ struct sip_addr *addr = contact_addr(contact);
+ (void)arg;
+
+ if (0 == msg_param_decode(&addr->params, "presence", &val) &&
+ 0 == pl_strcasecmp(&val, "p2p")) {
+ if (!removed) {
+ if (presence_alloc(contact) != 0) {
+ warning("presence: presence_alloc failed\n");
+ return;
+ }
+ }
+ else {
+ /* Find matching presence element for contact */
+ for (le = list_head(&presencel); le; le = le->next) {
+ pres = (struct presence*)le->data;
+ if (pres->contact == contact) {
+ break;
+ }
+ pres = NULL;
+ }
+
+ if (pres) {
+ mem_deref(pres);
+ }
+ else {
+ warning("presence: No contact to remove\n");
+ }
+ }
+ }
+}
+
+
+int subscriber_init(void)
+{
+ struct contacts *contacts = baresip_contacts();
+ struct le *le;
+ int err = 0;
+
+ for (le = list_head(contact_list(contacts)); le; le = le->next) {
+
+ struct contact *c = le->data;
+ struct sip_addr *addr = contact_addr(c);
+ struct pl val;
+
+ if (0 == msg_param_decode(&addr->params, "presence", &val) &&
+ 0 == pl_strcasecmp(&val, "p2p")) {
+
+ err |= presence_alloc(le->data);
+ }
+ }
+
+ info("Subscribing to %u contacts\n", list_count(&presencel));
+
+ contact_set_update_handler(contacts, contact_handler, NULL);
+
+ return err;
+}
+
+
+void subscriber_close(void)
+{
+ contact_set_update_handler(baresip_contacts(), NULL, NULL);
+ list_flush(&presencel);
+}
+
+
+void subscriber_close_all(void)
+{
+ struct le *le;
+
+ info("presence: subscriber: closing %u subs\n",
+ list_count(&presencel));
+
+ contact_set_update_handler(baresip_contacts(), NULL, NULL);
+
+ le = presencel.head;
+ while (le) {
+
+ struct presence *pres = le->data;
+ le = le->next;
+
+ debug("presence: shutdown: sub=%p\n", pres->sub);
+
+ pres->shutdown = true;
+ if (pres->sub) {
+ pres->sub = mem_deref(pres->sub);
+ tmr_start(&pres->tmr, SHUTDOWN_DELAY,
+ deref_handler, pres);
+ }
+ else
+ mem_deref(pres);
+ }
+}
diff --git a/modules/pulse/module.mk b/modules/pulse/module.mk
new file mode 100644
index 0000000..d5f7698
--- /dev/null
+++ b/modules/pulse/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2016 Creytiv.com
+#
+
+MOD := pulse
+$(MOD)_SRCS += pulse.c
+$(MOD)_SRCS += player.c
+$(MOD)_SRCS += recorder.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs libpulse-simple)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags libpulse-simple)
+
+include mk/mod.mk
diff --git a/modules/pulse/player.c b/modules/pulse/player.c
new file mode 100644
index 0000000..d65e7a8
--- /dev/null
+++ b/modules/pulse/player.c
@@ -0,0 +1,153 @@
+/**
+ * @file pulse/player.c Pulseaudio sound driver - player
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "pulse.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+
+ pa_simple *s;
+ pthread_t thread;
+ bool run;
+ void *sampv;
+ size_t sampc;
+ size_t sampsz;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ /* Wait for termination of other thread */
+ if (st->run) {
+ debug("pulse: stopping playback thread\n");
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->s)
+ pa_simple_free(st->s);
+
+ mem_deref(st->sampv);
+}
+
+
+static void *write_thread(void *arg)
+{
+ struct auplay_st *st = arg;
+ const size_t num_bytes = st->sampc * st->sampsz;
+ int ret, pa_error = 0;
+
+ while (st->run) {
+
+ st->wh(st->sampv, st->sampc, st->arg);
+
+ ret = pa_simple_write(st->s, st->sampv, num_bytes, &pa_error);
+ if (ret < 0) {
+ warning("pulse: pa_simple_write error (%s)\n",
+ pa_strerror(pa_error));
+ }
+ }
+
+ return NULL;
+}
+
+
+static int aufmt_to_pulse_format(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return PA_SAMPLE_S16NE;
+ case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE;
+ default: return 0;
+ }
+}
+
+
+int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ pa_sample_spec ss;
+ pa_buffer_attr attr;
+ int err = 0, pa_error = 0;
+
+ if (!stp || !ap || !prm || !wh)
+ return EINVAL;
+
+ debug("pulse: opening player (%u Hz, %d channels, device '%s')\n",
+ prm->srate, prm->ch, device);
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ st->sampsz = aufmt_sample_size(prm->fmt);
+
+ st->sampv = mem_alloc(st->sampsz * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ss.format = aufmt_to_pulse_format(prm->fmt);
+ ss.channels = prm->ch;
+ ss.rate = prm->srate;
+
+ attr.maxlength = (uint32_t)-1;
+ attr.tlength = (uint32_t)pa_usec_to_bytes(prm->ptime * 1000, &ss);
+ attr.prebuf = (uint32_t)-1;
+ attr.minreq = (uint32_t)-1;
+ attr.fragsize = (uint32_t)-1;
+
+ st->s = pa_simple_new(NULL,
+ "Baresip",
+ PA_STREAM_PLAYBACK,
+ str_isset(device) ? device : 0,
+ "VoIP Playback",
+ &ss,
+ NULL,
+ &attr,
+ &pa_error);
+ if (!st->s) {
+ warning("pulse: could not connect to server (%s)\n",
+ pa_strerror(pa_error));
+ err = ENODEV;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, write_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ debug("pulse: playback started\n");
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/pulse/pulse.c b/modules/pulse/pulse.c
new file mode 100644
index 0000000..01f6914
--- /dev/null
+++ b/modules/pulse/pulse.c
@@ -0,0 +1,54 @@
+/**
+ * @file pulse.c Pulseaudio sound driver
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "pulse.h"
+
+
+/**
+ * @defgroup pulse pulse
+ *
+ * Audio driver module for Pulseaudio
+ *
+ * This module is experimental and work-in-progress. It is using
+ * the pulseaudio "simple" interface.
+ */
+
+
+static struct auplay *auplay;
+static struct ausrc *ausrc;
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = auplay_register(&auplay, baresip_auplayl(),
+ "pulse", pulse_player_alloc);
+ err |= ausrc_register(&ausrc, baresip_ausrcl(),
+ "pulse", pulse_recorder_alloc);
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ auplay = mem_deref(auplay);
+ ausrc = mem_deref(ausrc);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(pulse) = {
+ "pulse",
+ "audio",
+ module_init,
+ module_close,
+};
diff --git a/modules/pulse/pulse.h b/modules/pulse/pulse.h
new file mode 100644
index 0000000..51c392f
--- /dev/null
+++ b/modules/pulse/pulse.h
@@ -0,0 +1,14 @@
+/**
+ * @file pulse.h Pulseaudio sound driver -- internal API
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+int pulse_player_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
+int pulse_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
diff --git a/modules/pulse/recorder.c b/modules/pulse/recorder.c
new file mode 100644
index 0000000..ca6fb6a
--- /dev/null
+++ b/modules/pulse/recorder.c
@@ -0,0 +1,193 @@
+/**
+ * @file pulse/recorder.c Pulseaudio sound driver - recorder
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "pulse.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+
+ pa_simple *s;
+ pthread_t thread;
+ bool run;
+ void *sampv;
+ size_t sampc;
+ size_t sampsz;
+ uint32_t ptime;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ /* Wait for termination of other thread */
+ if (st->run) {
+ debug("pulse: stopping record thread\n");
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->s)
+ pa_simple_free(st->s);
+
+ mem_deref(st->sampv);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+ const size_t num_bytes = st->sampc * st->sampsz;
+ int ret, pa_error = 0;
+ uint64_t now, last_read, diff;
+ unsigned dropped = 0;
+ bool init = true;
+
+ if (pa_simple_flush(st->s, &pa_error)) {
+ warning("pulse: pa_simple_flush error (%s)\n",
+ pa_strerror(pa_error));
+ }
+
+ last_read = tmr_jiffies();
+
+ while (st->run) {
+
+ ret = pa_simple_read(st->s, st->sampv, num_bytes, &pa_error);
+ if (ret < 0) {
+ warning("pulse: pa_simple_write error (%s)\n",
+ pa_strerror(pa_error));
+ continue;
+ }
+
+ /* Some devices might send a burst of samples right after the
+ initialization - filter them out */
+ if (init) {
+ now = tmr_jiffies();
+ diff = (now > last_read)? now - last_read : 0;
+
+ if (diff < st->ptime / 2) {
+ last_read = now;
+ ++dropped;
+ continue;
+ }
+ else {
+ init = false;
+
+ if (dropped)
+ debug("pulse: dropped %u frames of "
+ "garbage at the beginning of "
+ "the recording\n", dropped);
+ }
+ }
+
+ st->rh(st->sampv, st->sampc, st->arg);
+ }
+
+ return NULL;
+}
+
+
+static int aufmt_to_pulse_format(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return PA_SAMPLE_S16NE;
+ case AUFMT_FLOAT: return PA_SAMPLE_FLOAT32NE;
+ default: return 0;
+ }
+}
+
+
+int pulse_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ pa_sample_spec ss;
+ pa_buffer_attr attr;
+ int pa_error;
+ int err;
+
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ debug("pulse: opening recorder (%u Hz, %d channels, device '%s')\n",
+ prm->srate, prm->ch, device);
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ st->sampsz = aufmt_sample_size(prm->fmt);
+ st->ptime = prm->ptime;
+
+ st->sampv = mem_alloc(st->sampsz * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ss.format = aufmt_to_pulse_format(prm->fmt);
+ ss.channels = prm->ch;
+ ss.rate = prm->srate;
+
+ attr.maxlength = (uint32_t)-1;
+ attr.tlength = (uint32_t)-1;
+ attr.prebuf = (uint32_t)-1;
+ attr.minreq = (uint32_t)-1;
+ attr.fragsize = (uint32_t)pa_usec_to_bytes(prm->ptime * 1000, &ss);
+
+ st->s = pa_simple_new(NULL,
+ "Baresip",
+ PA_STREAM_RECORD,
+ str_isset(device) ? device : 0,
+ "VoIP Record",
+ &ss,
+ NULL,
+ &attr,
+ &pa_error);
+ if (!st->s) {
+ warning("pulse: could not connect to server (%s)\n",
+ pa_strerror(pa_error));
+ err = ENODEV;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ debug("pulse: recording started\n");
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/qtcapture/module.mk b/modules/qtcapture/module.mk
new file mode 100644
index 0000000..3c73139
--- /dev/null
+++ b/modules/qtcapture/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := qtcapture
+$(MOD)_SRCS += qtcapture.m
+$(MOD)_LFLAGS += -framework Cocoa -framework QTKit -framework CoreVideo
+
+include mk/mod.mk
diff --git a/modules/qtcapture/qtcapture.m b/modules/qtcapture/qtcapture.m
new file mode 100644
index 0000000..e4fddc2
--- /dev/null
+++ b/modules/qtcapture/qtcapture.m
@@ -0,0 +1,402 @@
+/**
+ * @file qtcapture.m Video source using QTKit QTCapture
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <QTKit/QTKit.h>
+
+
+static void frame_handler(struct vidsrc_st *st,
+ const CVImageBufferRef videoFrame);
+static struct vidsrc *vidsrc;
+
+
+@interface qtcap : NSObject
+{
+ QTCaptureSession *sess;
+ QTCaptureDeviceInput *input;
+ QTCaptureDecompressedVideoOutput *output;
+ struct vidsrc_st *vsrc;
+}
+@end
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ qtcap *cap;
+ struct lock *lock;
+ struct vidsz app_sz;
+ struct vidsz sz;
+ struct mbuf *buf;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ bool started;
+#ifdef QTCAPTURE_RUNLOOP
+ struct tmr tmr;
+#endif
+};
+
+
+@implementation qtcap
+
+
+- (id)init:(struct vidsrc_st *)st
+ dev:(const char *)name
+{
+ NSAutoreleasePool *pool;
+ QTCaptureDevice *dev;
+ BOOL success = NO;
+ NSError *err;
+
+ pool = [[NSAutoreleasePool alloc] init];
+ if (!pool)
+ return nil;
+
+ self = [super init];
+ if (!self)
+ goto out;
+
+ vsrc = st;
+ sess = [[QTCaptureSession alloc] init];
+ if (!sess)
+ goto out;
+
+ if (str_isset(name)) {
+ NSString *s = [NSString stringWithUTF8String:name];
+ dev = [QTCaptureDevice deviceWithUniqueID:s];
+ info("qtcapture: using device: %s\n", name);
+ }
+ else {
+ dev = [QTCaptureDevice
+ defaultInputDeviceWithMediaType:QTMediaTypeVideo];
+ }
+
+ success = [dev open:&err];
+ if (!success)
+ goto out;
+
+ input = [[QTCaptureDeviceInput alloc] initWithDevice:dev];
+ success = [sess addInput:input error:&err];
+ if (!success)
+ goto out;
+
+ output = [[QTCaptureDecompressedVideoOutput alloc] init];
+ [output setDelegate:self];
+ [output setPixelBufferAttributes:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:st->app_sz.h], kCVPixelBufferHeightKey,
+ [NSNumber numberWithInt:st->app_sz.w], kCVPixelBufferWidthKey,
+#if 0
+ /* This does not work reliably */
+ [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8Planar],
+ (id)kCVPixelBufferPixelFormatTypeKey,
+#endif
+ nil]];
+
+ success = [sess addOutput:output error:&err];
+ if (!success)
+ goto out;
+
+ /* Start */
+ [sess startRunning];
+
+ out:
+ if (!success && self) {
+ [self dealloc];
+ self = nil;
+ }
+
+ [pool release];
+
+ return self;
+}
+
+
+- (void)stop:(id)unused
+{
+ (void)unused;
+
+ [sess stopRunning];
+
+ if ([[input device] isOpen]) {
+ [[input device] close];
+ [sess removeInput:input];
+ [input release];
+ }
+
+ if (output) {
+ [output setDelegate:nil];
+ [sess removeOutput:output];
+ [output release];
+ }
+}
+
+
+- (void)dealloc
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [self performSelectorOnMainThread:@selector(stop:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ [sess release];
+
+ [super dealloc];
+
+ [pool release];
+}
+
+
+- (void)captureOutput:(QTCaptureOutput *)captureOutput
+ didOutputVideoFrame:(CVImageBufferRef)videoFrame
+ withSampleBuffer:(QTSampleBuffer *)sampleBuffer
+ fromConnection:(QTCaptureConnection *)connection
+{
+ (void)captureOutput;
+ (void)sampleBuffer;
+ (void)connection;
+
+#if 0
+ printf("got frame: %zu x %zu - fmt=0x%08x\n",
+ CVPixelBufferGetWidth(videoFrame),
+ CVPixelBufferGetHeight(videoFrame),
+ CVPixelBufferGetPixelFormatType(videoFrame));
+#endif
+
+ frame_handler(vsrc, videoFrame);
+}
+
+
+@end
+
+
+static enum vidfmt get_pixfmt(OSType type)
+{
+ switch (type) {
+
+ case kCVPixelFormatType_420YpCbCr8Planar: return VID_FMT_YUV420P;
+ case kCVPixelFormatType_422YpCbCr8: return VID_FMT_UYVY422;
+ case 0x79757673: /* yuvs */ return VID_FMT_YUYV422;
+ case kCVPixelFormatType_32ARGB: return VID_FMT_ARGB;
+ default: return -1;
+ }
+}
+
+
+static inline void avpict_init_planar(struct vidframe *p,
+ const CVImageBufferRef f)
+{
+ int i;
+
+ if (!p)
+ return;
+
+ for (i=0; i<3; i++) {
+ p->data[i] = CVPixelBufferGetBaseAddressOfPlane(f, i);
+ p->linesize[i] = (int)CVPixelBufferGetBytesPerRowOfPlane(f, i);
+ }
+
+ p->data[3] = NULL;
+ p->linesize[3] = 0;
+}
+
+
+static inline void avpict_init_chunky(struct vidframe *p,
+ const CVImageBufferRef f)
+{
+ p->data[0] = CVPixelBufferGetBaseAddress(f);
+ p->linesize[0] = (int)CVPixelBufferGetBytesPerRow(f);
+
+ p->data[1] = p->data[2] = p->data[3] = NULL;
+ p->linesize[1] = p->linesize[2] = p->linesize[3] = 0;
+}
+
+
+static void frame_handler(struct vidsrc_st *st,
+ const CVImageBufferRef videoFrame)
+{
+ struct vidframe src;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ enum vidfmt vidfmt;
+
+ lock_write_get(st->lock);
+ frameh = st->frameh;
+ arg = st->arg;
+ lock_rel(st->lock);
+
+ if (!frameh)
+ return;
+
+ vidfmt = get_pixfmt(CVPixelBufferGetPixelFormatType(videoFrame));
+ if (vidfmt == (enum vidfmt)-1) {
+ warning("qtcapture: unknown pixel format: 0x%08x\n",
+ CVPixelBufferGetPixelFormatType(videoFrame));
+ return;
+ }
+
+ st->started = true;
+
+ st->sz.w = (int)CVPixelBufferGetWidth(videoFrame);
+ st->sz.h = (int)CVPixelBufferGetHeight(videoFrame);
+
+ CVPixelBufferLockBaseAddress(videoFrame, 0);
+
+ if (CVPixelBufferIsPlanar(videoFrame))
+ avpict_init_planar(&src, videoFrame);
+ else
+ avpict_init_chunky(&src, videoFrame);
+
+ src.fmt = vidfmt;
+ src.size = st->sz;
+
+ CVPixelBufferUnlockBaseAddress(videoFrame, 0);
+
+ frameh(&src, arg);
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+#ifdef QTCAPTURE_RUNLOOP
+ tmr_cancel(&st->tmr);
+#endif
+
+ lock_write_get(st->lock);
+ st->frameh = NULL;
+ lock_rel(st->lock);
+
+ [st->cap dealloc];
+
+ mem_deref(st->buf);
+ mem_deref(st->lock);
+}
+
+
+#ifdef QTCAPTURE_RUNLOOP
+static void tmr_handler(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ /* Check if frame_handler was called */
+ if (st->started)
+ return;
+
+ tmr_start(&st->tmr, 100, tmr_handler, st);
+
+ /* Simulate the Run-Loop */
+ (void)CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
+}
+#endif
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ if (size)
+ st->app_sz = *size;
+
+ err = lock_alloc(&st->lock);
+ if (err)
+ goto out;
+
+ st->cap = [[qtcap alloc] init:st dev:dev];
+ if (!st->cap) {
+ err = ENODEV;
+ goto out;
+ }
+
+#ifdef QTCAPTURE_RUNLOOP
+ tmr_start(&st->tmr, 10, tmr_handler, st);
+#endif
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static void device_info(void)
+{
+ NSAutoreleasePool *pool;
+ NSArray *devs;
+
+ pool = [[NSAutoreleasePool alloc] init];
+ if (!pool)
+ return;
+
+ devs = [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
+
+ if (devs && [devs count] > 1) {
+ QTCaptureDevice *d;
+
+ debug("qtcapture: devices:\n");
+
+ for (d in devs) {
+ NSString *name = [d localizedDisplayName];
+
+ debug(" %s: %s\n",
+ [[d uniqueID] UTF8String],
+ [name UTF8String]);
+ }
+ }
+
+ [pool release];
+}
+
+
+static int module_init(void)
+{
+ device_info();
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "qtcapture", alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(qtcapture) = {
+ "qtcapture",
+ "vidsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/rst/audio.c b/modules/rst/audio.c
new file mode 100644
index 0000000..4bb7e0d
--- /dev/null
+++ b/modules/rst/audio.c
@@ -0,0 +1,273 @@
+/**
+ * @file rst/audio.c MP3/ICY HTTP Audio Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <mpg123.h>
+#include "rst.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* pointer to base-class (inheritance) */
+ pthread_t thread;
+ struct rst *rst;
+ mpg123_handle *mp3;
+ struct aubuf *aubuf;
+ ausrc_read_h *rh;
+ ausrc_error_h *errh;
+ void *arg;
+ bool run;
+ uint32_t ptime;
+ size_t sampc;
+ size_t sampsz;
+};
+
+
+static struct ausrc *ausrc;
+
+
+static void destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ rst_set_audio(st->rst, NULL);
+ mem_deref(st->rst);
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->mp3) {
+ mpg123_close(st->mp3);
+ mpg123_delete(st->mp3);
+ }
+
+ mem_deref(st->aubuf);
+}
+
+
+static void *play_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct ausrc_st *st = arg;
+ void *sampv;
+ size_t num_bytes = st->sampc * st->sampsz;
+
+ sampv = mem_alloc(num_bytes, NULL);
+ if (!sampv)
+ return NULL;
+
+ while (st->run) {
+
+ sys_msleep(4);
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+#if 1
+ if (now > ts + 100) {
+ debug("rst: cpu lagging behind (%u ms)\n",
+ now - ts);
+ }
+#endif
+
+ aubuf_read(st->aubuf, sampv, num_bytes);
+
+ st->rh(sampv, st->sampc, st->arg);
+
+ ts += st->ptime;
+ }
+
+ mem_deref(sampv);
+
+ return NULL;
+}
+
+
+static inline int decode(struct ausrc_st *st)
+{
+ int err, ch, encoding;
+ struct mbuf *mb;
+ long srate;
+
+ mb = mbuf_alloc(4096);
+ if (!mb)
+ return ENOMEM;
+
+ err = mpg123_read(st->mp3, mb->buf, mb->size, &mb->end);
+
+ switch (err) {
+
+ case MPG123_NEW_FORMAT:
+ mpg123_getformat(st->mp3, &srate, &ch, &encoding);
+ info("rst: new format: %i hz, %i ch, encoding 0x%04x\n",
+ srate, ch, encoding);
+ /*@fallthrough@*/
+
+ case MPG123_OK:
+ case MPG123_NEED_MORE:
+ if (mb->end == 0)
+ break;
+ aubuf_append(st->aubuf, mb);
+ break;
+
+ default:
+ warning("rst: mpg123_read error: %s\n",
+ mpg123_plain_strerror(err));
+ break;
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz)
+{
+ int err;
+
+ if (!st)
+ return;
+
+ err = mpg123_feed(st->mp3, buf, sz);
+ if (err)
+ return;
+
+ while (MPG123_OK == decode(st))
+ ;
+}
+
+
+static int aufmt_to_encoding(enum aufmt fmt)
+{
+ switch (fmt) {
+
+ case AUFMT_S16LE: return MPG123_ENC_SIGNED_16;
+ case AUFMT_FLOAT: return MPG123_ENC_FLOAT_32;
+ case AUFMT_S24_3LE: return MPG123_ENC_SIGNED_24; /* NOTE: endian */
+ default: return 0;
+ }
+}
+
+
+static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *dev,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ st->mp3 = mpg123_new(NULL, &err);
+ if (!st->mp3) {
+ err = ENODEV;
+ goto out;
+ }
+
+ err = mpg123_open_feed(st->mp3);
+ if (err != MPG123_OK) {
+ warning("rst: mpg123_open_feed: %s\n",
+ mpg123_strerror(st->mp3));
+ err = ENODEV;
+ goto out;
+ }
+
+ /* Set wanted output format */
+ mpg123_format_none(st->mp3);
+ mpg123_format(st->mp3, prm->srate, prm->ch,
+ aufmt_to_encoding(prm->fmt));
+ mpg123_volume(st->mp3, 0.3);
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+ st->sampsz = aufmt_sample_size(prm->fmt);
+
+ st->ptime = prm->ptime;
+
+ info("rst: audio ptime=%u sampc=%zu aubuf=[%u:%u]\n",
+ st->ptime, st->sampc,
+ prm->srate * prm->ch * 2,
+ prm->srate * prm->ch * 40);
+
+ /* 1 - 20 seconds of audio */
+ err = aubuf_alloc(&st->aubuf,
+ prm->srate * prm->ch * st->sampsz,
+ prm->srate * prm->ch * st->sampsz * 20);
+ if (err)
+ goto out;
+
+ if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) {
+ st->rst = mem_ref(*ctx);
+ }
+ else {
+ err = rst_alloc(&st->rst, dev);
+ if (err)
+ goto out;
+
+ if (ctx)
+ *ctx = (struct media_ctx *)st->rst;
+ }
+
+ rst_set_audio(st->rst, st);
+
+ st->run = true;
+
+ err = pthread_create(&st->thread, NULL, play_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int rst_audio_init(void)
+{
+ int err;
+
+ err = mpg123_init();
+ if (err != MPG123_OK) {
+ warning("rst: mpg123_init: %s\n", mpg123_plain_strerror(err));
+ return ENODEV;
+ }
+
+ return ausrc_register(&ausrc, baresip_ausrcl(), "rst", alloc_handler);
+}
+
+
+void rst_audio_close(void)
+{
+ ausrc = mem_deref(ausrc);
+
+ mpg123_exit();
+}
diff --git a/modules/rst/module.mk b/modules/rst/module.mk
new file mode 100644
index 0000000..a52fdbe
--- /dev/null
+++ b/modules/rst/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2011 Creytiv.com
+#
+
+MOD := rst
+$(MOD)_SRCS += audio.c
+$(MOD)_SRCS += rst.c
+$(MOD)_SRCS += video.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs cairo libmpg123)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags cairo libmpg123)
+
+include mk/mod.mk
diff --git a/modules/rst/rst.c b/modules/rst/rst.c
new file mode 100644
index 0000000..83298a9
--- /dev/null
+++ b/modules/rst/rst.c
@@ -0,0 +1,423 @@
+/**
+ * @file rst.c MP3/ICY HTTP AV Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "rst.h"
+
+
+/**
+ * @defgroup rst rst
+ *
+ * Audio and video source module using mpg123 as input
+ *
+ * The module 'rst' is using the mpg123 to play streaming
+ * media (MP3) and provide this as an internal audio/video source.
+ *
+ * Example config:
+ \verbatim
+ audio_source rst,http://relay.slayradio.org:8000/
+ video_source rst,http://relay.slayradio.org:8000/
+ \endverbatim
+ */
+
+
+enum {
+ RETRY_WAIT = 10000,
+};
+
+
+struct rst {
+ const char *id;
+ struct ausrc_st *ausrc_st;
+ struct vidsrc_st *vidsrc_st;
+ struct tmr tmr;
+ struct dns_query *dnsq;
+ struct tcp_conn *tc;
+ struct mbuf *mb;
+ char *host;
+ char *path;
+ char *name;
+ char *meta;
+ bool head_recv;
+ size_t metaint;
+ size_t metasz;
+ size_t bytec;
+ uint16_t port;
+};
+
+
+static int rst_connect(struct rst *rst);
+
+
+static void destructor(void *arg)
+{
+ struct rst *rst = arg;
+
+ tmr_cancel(&rst->tmr);
+ mem_deref(rst->dnsq);
+ mem_deref(rst->tc);
+ mem_deref(rst->mb);
+ mem_deref(rst->host);
+ mem_deref(rst->path);
+ mem_deref(rst->name);
+ mem_deref(rst->meta);
+}
+
+
+static void reconnect(void *arg)
+{
+ struct rst *rst = arg;
+ int err;
+
+ rst->mb = mem_deref(rst->mb);
+ rst->name = mem_deref(rst->name);
+ rst->meta = mem_deref(rst->meta);
+
+ rst->head_recv = false;
+ rst->metaint = 0;
+ rst->metasz = 0;
+ rst->bytec = 0;
+
+ err = rst_connect(rst);
+ if (err)
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ struct rst *rst = arg;
+ size_t n;
+
+ if (!rst->head_recv) {
+
+ struct pl hdr, name, metaint, eoh;
+
+ if (rst->mb) {
+ size_t pos;
+ int err;
+
+ pos = rst->mb->pos;
+
+ rst->mb->pos = rst->mb->end;
+
+ err = mbuf_write_mem(rst->mb, mbuf_buf(mb),
+ mbuf_get_left(mb));
+ if (err) {
+ warning("rst: buffer write error: %m\n", err);
+ rst->tc = mem_deref(rst->tc);
+ tmr_start(&rst->tmr, RETRY_WAIT,
+ reconnect, rst);
+ return;
+ }
+
+ rst->mb->pos = pos;
+ }
+ else {
+ rst->mb = mem_ref(mb);
+ }
+
+ if (re_regex((const char *)mbuf_buf(rst->mb),
+ mbuf_get_left(rst->mb),
+ "[^\r\n]1\r\n\r\n", &eoh))
+ return;
+
+ rst->head_recv = true;
+
+ hdr.p = (const char *)mbuf_buf(rst->mb);
+ hdr.l = eoh.p + 5 - hdr.p;
+
+ if (!re_regex(hdr.p, hdr.l, "icy-name:[ \t]*[^\r\n]+\r\n",
+ NULL, &name))
+ (void)pl_strdup(&rst->name, &name);
+
+ if (!re_regex(hdr.p, hdr.l, "icy-metaint:[ \t]*[0-9]+\r\n",
+ NULL, &metaint))
+ rst->metaint = pl_u32(&metaint);
+
+ if (rst->metaint == 0) {
+ info("rst: icy meta interval not available\n");
+ rst->tc = mem_deref(rst->tc);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+
+ rst_video_update(rst->vidsrc_st, rst->name, NULL);
+
+ rst->mb->pos += hdr.l;
+
+ info("rst: name='%s' metaint=%zu\n", rst->name, rst->metaint);
+
+ if (rst->mb->pos >= rst->mb->end)
+ return;
+
+ mb = rst->mb;
+ }
+
+ while (mb->pos < mb->end) {
+
+ if (rst->metasz > 0) {
+
+ n = min(mbuf_get_left(mb), rst->metasz - rst->bytec);
+
+ if (rst->meta)
+ mbuf_read_mem(mb,
+ (uint8_t *)&rst->meta[rst->bytec],
+ n);
+ else
+ mb->pos += n;
+
+ rst->bytec += n;
+#if 0
+ info("rst: metadata %zu bytes\n", n);
+#endif
+ if (rst->bytec >= rst->metasz) {
+#if 0
+ info("rst: metadata: [%s]\n", rst->meta);
+#endif
+ rst->metasz = 0;
+ rst->bytec = 0;
+
+ rst_video_update(rst->vidsrc_st, rst->name,
+ rst->meta);
+ }
+ }
+ else if (rst->bytec < rst->metaint) {
+
+ n = min(mbuf_get_left(mb), rst->metaint - rst->bytec);
+
+ rst_audio_feed(rst->ausrc_st, mbuf_buf(mb), n);
+
+ rst->bytec += n;
+ mb->pos += n;
+#if 0
+ info("rst: mp3data %zu bytes\n", n);
+#endif
+ }
+ else {
+ rst->metasz = mbuf_read_u8(mb) * 16;
+ rst->bytec = 0;
+
+ rst->meta = mem_deref(rst->meta);
+ rst->meta = mem_zalloc(rst->metasz + 1, NULL);
+#if 0
+ info("rst: metalength %zu bytes\n", rst->metasz);
+#endif
+ }
+ }
+}
+
+
+static void estab_handler(void *arg)
+{
+ struct rst *rst = arg;
+ struct mbuf *mb;
+ int err;
+
+ info("rst: connection established\n");
+
+ mb = mbuf_alloc(512);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = mbuf_printf(mb,
+ "GET %s HTTP/1.0\r\n"
+ "Icy-MetaData: 1\r\n"
+ "\r\n",
+ rst->path);
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = tcp_send(rst->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ warning("rst: error sending HTTP request: %m\n", err);
+ }
+
+ mem_deref(mb);
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct rst *rst = arg;
+
+ info("rst: tcp closed: %m\n", err);
+
+ rst->tc = mem_deref(rst->tc);
+
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+}
+
+
+static void dns_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct rst *rst = arg;
+ struct dnsrr *rr;
+ struct sa srv;
+
+ (void)err;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ rr = dns_rrlist_find(ansl, rst->host, DNS_TYPE_A, DNS_CLASS_IN, true);
+ if (!rr) {
+ warning("rst: unable to resolve: %s\n", rst->host);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+
+ sa_set_in(&srv, rr->rdata.a.addr, rst->port);
+
+ err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler,
+ close_handler, rst);
+ if (err) {
+ warning("rst: tcp connect error: %m\n", err);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+}
+
+
+static int rst_connect(struct rst *rst)
+{
+ struct sa srv;
+ int err;
+
+ if (!sa_set_str(&srv, rst->host, rst->port)) {
+
+ err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler,
+ close_handler, rst);
+ if (err) {
+ warning("rst: tcp connect error: %m\n", err);
+ }
+ }
+ else {
+ err = dnsc_query(&rst->dnsq, net_dnsc(baresip_network()),
+ rst->host, DNS_TYPE_A,
+ DNS_CLASS_IN, true, dns_handler, rst);
+ if (err) {
+ warning("rst: dns query error: %m\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+int rst_alloc(struct rst **rstp, const char *dev)
+{
+ struct pl host, port, path;
+ struct rst *rst;
+ int err;
+
+ if (!rstp || !dev)
+ return EINVAL;
+
+ if (re_regex(dev, strlen(dev), "http://[^:/]+[:]*[0-9]*[^]+",
+ &host, NULL, &port, &path)) {
+ warning("rst: bad http url: %s\n", dev);
+ return EBADMSG;
+ }
+
+ rst = mem_zalloc(sizeof(*rst), destructor);
+ if (!rst)
+ return ENOMEM;
+
+ rst->id = "rst";
+
+ err = pl_strdup(&rst->host, &host);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&rst->path, &path);
+ if (err)
+ goto out;
+
+ rst->port = pl_u32(&port);
+ rst->port = rst->port ? rst->port : 80;
+
+ err = rst_connect(rst);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(rst);
+ else
+ *rstp = rst;
+
+ return err;
+}
+
+
+void rst_set_audio(struct rst *rst, struct ausrc_st *st)
+{
+ if (!rst)
+ return;
+
+ rst->ausrc_st = st;
+}
+
+
+void rst_set_video(struct rst *rst, struct vidsrc_st *st)
+{
+ if (!rst)
+ return;
+
+ rst->vidsrc_st = st;
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = rst_audio_init();
+ if (err)
+ goto out;
+
+ err = rst_video_init();
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ rst_audio_close();
+ rst_video_close();
+ }
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ rst_audio_close();
+ rst_video_close();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(rst) = {
+ "rst",
+ "avsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/rst/rst.h b/modules/rst/rst.h
new file mode 100644
index 0000000..950a7de
--- /dev/null
+++ b/modules/rst/rst.h
@@ -0,0 +1,26 @@
+/**
+ * @file rst.h MP3/ICY HTTP AV Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+
+/* Shared AV state */
+struct rst;
+
+int rst_alloc(struct rst **rstp, const char *dev);
+void rst_set_audio(struct rst *rst, struct ausrc_st *st);
+void rst_set_video(struct rst *rst, struct vidsrc_st *st);
+
+
+/* Audio */
+void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz);
+int rst_audio_init(void);
+void rst_audio_close(void);
+
+
+/* Video */
+void rst_video_update(struct vidsrc_st *st, const char *name,
+ const char *meta);
+int rst_video_init(void);
+void rst_video_close(void);
diff --git a/modules/rst/video.c b/modules/rst/video.c
new file mode 100644
index 0000000..bf59daf
--- /dev/null
+++ b/modules/rst/video.c
@@ -0,0 +1,280 @@
+/**
+ * @file rst/video.c MP3/ICY HTTP Video Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <pthread.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <cairo/cairo.h>
+#include "rst.h"
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* pointer to base-class (inheritance) */
+ pthread_mutex_t mutex;
+ pthread_t thread;
+ struct vidsrc_prm prm;
+ struct vidsz size;
+ struct rst *rst;
+ cairo_surface_t *surface;
+ cairo_t *cairo;
+ struct vidframe *frame;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ bool run;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ rst_set_video(st->rst, NULL);
+ mem_deref(st->rst);
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->cairo)
+ cairo_destroy(st->cairo);
+
+ if (st->surface)
+ cairo_surface_destroy(st->surface);
+
+ mem_deref(st->frame);
+}
+
+
+static void *video_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct vidsrc_st *st = arg;
+
+ while (st->run) {
+
+ sys_msleep(4);
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+
+ pthread_mutex_lock(&st->mutex);
+ st->frameh(st->frame, st->arg);
+ pthread_mutex_unlock(&st->mutex);
+
+ ts += 1000/st->prm.fps;
+ }
+
+ return NULL;
+}
+
+
+static void background(cairo_t *cr, unsigned width, unsigned height)
+{
+ cairo_pattern_t *pat;
+ double r, g, b;
+
+ pat = cairo_pattern_create_linear(0.0, 0.0, 0.0, height);
+ if (!pat)
+ return;
+
+ r = 0.0;
+ g = 0.0;
+ b = 0.8;
+
+ cairo_pattern_add_color_stop_rgba(pat, 1, r, g, b, 1);
+ cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0.2, 1);
+ cairo_rectangle(cr, 0, 0, width, height);
+ cairo_set_source(cr, pat);
+ cairo_fill(cr);
+
+ cairo_pattern_destroy(pat);
+}
+
+
+static void icy_printf(cairo_t *cr, int x, int y, double size,
+ const char *fmt, ...)
+{
+ char buf[4096] = "";
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* Draw text */
+ cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(cr, size);
+ cairo_move_to(cr, x, y);
+ cairo_text_path(cr, buf);
+ cairo_set_source_rgb(cr, 1, 1, 1);
+ cairo_fill(cr);
+}
+
+
+static size_t linelen(const struct pl *pl)
+{
+ size_t len = 72, i;
+
+ if (pl->l <= len)
+ return pl->l;
+
+ for (i=len; i>1; i--) {
+
+ if (pl->p[i-1] == ' ') {
+ len = i;
+ break;
+ }
+ }
+
+ return len;
+}
+
+
+void rst_video_update(struct vidsrc_st *st, const char *name, const char *meta)
+{
+ struct vidframe frame;
+
+ if (!st)
+ return;
+
+ background(st->cairo, st->size.w, st->size.h);
+
+ icy_printf(st->cairo, 50, 100, 40.0, "%s", name);
+
+ if (meta) {
+
+ struct pl title;
+
+ if (!re_regex(meta, strlen(meta),
+ "StreamTitle='[ \t]*[^;]+;", NULL, &title)) {
+
+ unsigned i;
+
+ title.l--;
+
+ for (i=0; title.l; i++) {
+
+ const size_t len = linelen(&title);
+
+ icy_printf(st->cairo, 50, 150 + 25*i, 18.0,
+ "%b", title.p, len);
+
+ title.p += len;
+ title.l -= len;
+ }
+ }
+ }
+
+ vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size,
+ cairo_image_surface_get_data(st->surface));
+
+ pthread_mutex_lock(&st->mutex);
+ vidconv(st->frame, &frame, NULL);
+ pthread_mutex_unlock(&st->mutex);
+}
+
+
+static int alloc_handler(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !vs || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ st->vs = vs;
+ st->prm = *prm;
+ st->size = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ size->w, size->h);
+ if (!st->surface) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->cairo = cairo_create(st->surface);
+ if (!st->cairo) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size);
+ if (err)
+ goto out;
+
+ vidframe_fill(st->frame, 0, 0, 0);
+
+ if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) {
+ st->rst = mem_ref(*ctx);
+ }
+ else {
+ err = rst_alloc(&st->rst, dev);
+ if (err)
+ goto out;
+
+ if (ctx)
+ *ctx = (struct media_ctx *)st->rst;
+ }
+
+ rst_set_video(st->rst, st);
+
+ st->run = true;
+
+ err = pthread_create(&st->thread, NULL, video_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int rst_video_init(void)
+{
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "rst", alloc_handler, NULL);
+}
+
+
+void rst_video_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+}
diff --git a/modules/sdl/module.mk b/modules/sdl/module.mk
new file mode 100644
index 0000000..9e45104
--- /dev/null
+++ b/modules/sdl/module.mk
@@ -0,0 +1,18 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := sdl
+$(MOD)_SRCS += sdl.c
+$(MOD)_SRCS += util.c
+
+$(MOD)_LFLAGS += -lSDL
+ifeq ($(OS),darwin)
+# note: APP_LFLAGS is needed, as main.o links to -lSDLmain
+APP_LFLAGS += -lSDL -lSDLmain -lobjc \
+ -framework CoreFoundation -framework Foundation -framework Cocoa
+endif
+
+include mk/mod.mk
diff --git a/modules/sdl/sdl.c b/modules/sdl/sdl.c
new file mode 100644
index 0000000..02facd7
--- /dev/null
+++ b/modules/sdl/sdl.c
@@ -0,0 +1,327 @@
+/**
+ * @file sdl/sdl.c SDL - Simple DirectMedia Layer v1.2
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <SDL/SDL.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "sdl.h"
+
+
+/**
+ * @defgroup sdl sdl
+ *
+ * Video display using Simple DirectMedia Layer (SDL)
+ */
+
+
+/** Local constants */
+enum {
+ KEY_RELEASE_VAL = 250 /**< Key release value in [ms] */
+};
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance */
+};
+
+/** Global SDL data */
+static struct {
+ struct tmr tmr;
+ SDL_Surface *screen; /**< SDL Surface */
+ SDL_Overlay *bmp; /**< SDL YUV Overlay */
+ struct vidsz size; /**< Current size */
+ vidisp_resize_h *resizeh; /**< Screen resize handler */
+ void *arg; /**< Handler argument */
+ bool fullscreen;
+ bool open;
+} sdl;
+
+
+static struct vidisp *vid;
+
+
+static void event_handler(void *arg);
+
+
+static void sdl_reset(void)
+{
+ if (sdl.bmp) {
+ SDL_FreeYUVOverlay(sdl.bmp);
+ sdl.bmp = NULL;
+ }
+
+ if (sdl.screen) {
+ SDL_FreeSurface(sdl.screen);
+ sdl.screen = NULL;
+ }
+}
+
+
+static void handle_resize(int w, int h)
+{
+ struct vidsz size;
+
+ size.w = w;
+ size.h = h;
+
+ /* notify app */
+ if (sdl.resizeh)
+ sdl.resizeh(&size, sdl.arg);
+}
+
+
+static void timeout(void *arg)
+{
+ (void)arg;
+
+ tmr_start(&sdl.tmr, 1, event_handler, NULL);
+
+ /* Emulate key-release */
+ ui_input_key(baresip_uis(), KEYCODE_REL, NULL);
+}
+
+
+static void event_handler(void *arg)
+{
+ SDL_Event event;
+ char ch;
+
+ (void)arg;
+
+ tmr_start(&sdl.tmr, 100, event_handler, NULL);
+
+ while (SDL_PollEvent(&event)) {
+
+ switch (event.type) {
+
+ case SDL_KEYDOWN:
+
+ switch (event.key.keysym.sym) {
+
+ case SDLK_ESCAPE:
+ if (!sdl.fullscreen)
+ break;
+
+ sdl.fullscreen = false;
+ sdl_reset();
+ break;
+
+ case SDLK_f:
+ if (sdl.fullscreen)
+ break;
+
+ sdl.fullscreen = true;
+ sdl_reset();
+ break;
+
+ default:
+ ch = event.key.keysym.unicode & 0x7f;
+
+ /* Relay key-press to UI subsystem */
+ if (isprint(ch)) {
+ tmr_start(&sdl.tmr, KEY_RELEASE_VAL,
+ timeout, NULL);
+ ui_input_key(baresip_uis(), ch, NULL);
+ }
+ break;
+ }
+
+ break;
+
+ case SDL_VIDEORESIZE:
+ handle_resize(event.resize.w, event.resize.h);
+ break;
+
+ case SDL_QUIT:
+ ui_input_key(baresip_uis(), 'q', NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+static int sdl_open(void)
+{
+ if (sdl.open)
+ return 0;
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ warning("sdl: unable to init SDL: %s\n", SDL_GetError());
+ return ENODEV;
+ }
+
+ SDL_EnableUNICODE(1);
+
+ tmr_start(&sdl.tmr, 100, event_handler, NULL);
+ sdl.open = true;
+
+ return 0;
+}
+
+
+static void sdl_close(void)
+{
+ tmr_cancel(&sdl.tmr);
+ sdl_reset();
+
+ if (sdl.open) {
+ SDL_Quit();
+ sdl.open = false;
+ }
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+ (void)st;
+
+ sdl_close();
+}
+
+
+static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err;
+
+ /* Not used by SDL */
+ (void)prm;
+ (void)dev;
+
+ if (sdl.open)
+ return EBUSY;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ sdl.resizeh = resizeh;
+ sdl.arg = arg;
+
+ err = sdl_open();
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+/**
+ * Display a video frame
+ *
+ * @param st Video display state
+ * @param title Window title
+ * @param frame Video frame
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @note: On Darwin, this must be called from the main thread
+ */
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ SDL_Rect rect;
+
+ if (!st || !sdl.open)
+ return EINVAL;
+
+ if (!vidsz_cmp(&sdl.size, &frame->size)) {
+ if (sdl.size.w && sdl.size.h) {
+ info("sdl: reset size %u x %u ---> %u x %u\n",
+ sdl.size.w, sdl.size.h,
+ frame->size.w, frame->size.h);
+ }
+ sdl_reset();
+ }
+
+ if (!sdl.screen) {
+ int flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL;
+ char capt[256];
+
+ if (sdl.fullscreen)
+ flags |= SDL_FULLSCREEN;
+ else if (sdl.resizeh)
+ flags |= SDL_RESIZABLE;
+
+ if (title) {
+ re_snprintf(capt, sizeof(capt), "%s - %u x %u",
+ title, frame->size.w, frame->size.h);
+ }
+ else {
+ re_snprintf(capt, sizeof(capt), "%u x %u",
+ frame->size.w, frame->size.h);
+ }
+
+ SDL_WM_SetCaption(capt, capt);
+
+ sdl.screen = SDL_SetVideoMode(frame->size.w, frame->size.h,
+ 0, flags);
+ if (!sdl.screen) {
+ warning("sdl: unable to get video screen: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+
+ sdl.size = frame->size;
+ }
+
+ if (!sdl.bmp) {
+ sdl.bmp = SDL_CreateYUVOverlay(frame->size.w, frame->size.h,
+ SDL_YV12_OVERLAY, sdl.screen);
+ if (!sdl.bmp) {
+ warning("sdl: unable to create overlay: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+ }
+
+ SDL_LockYUVOverlay(sdl.bmp);
+ picture_copy(sdl.bmp->pixels, sdl.bmp->pitches, frame);
+ SDL_UnlockYUVOverlay(sdl.bmp);
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.w = sdl.size.w;
+ rect.h = sdl.size.h;
+
+ SDL_DisplayYUVOverlay(sdl.bmp, &rect);
+
+ return 0;
+}
+
+
+static int module_init(void)
+{
+ return vidisp_register(&vid, baresip_vidispl(),
+ "sdl", alloc, NULL, display, NULL);
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl) = {
+ "sdl",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/sdl/sdl.h b/modules/sdl/sdl.h
new file mode 100644
index 0000000..992f70d
--- /dev/null
+++ b/modules/sdl/sdl.h
@@ -0,0 +1,9 @@
+/**
+ * @file sdl.h Simple DirectMedia Layer module -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+void picture_copy(uint8_t *data[4], uint16_t linesize[4],
+ const struct vidframe *frame);
diff --git a/modules/sdl/util.c b/modules/sdl/util.c
new file mode 100644
index 0000000..59a1cdd
--- /dev/null
+++ b/modules/sdl/util.c
@@ -0,0 +1,54 @@
+/**
+ * @file util.c Simple DirectMedia Layer module -- utilities
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "sdl.h"
+
+
+static void img_copy_plane(uint8_t *dst, int dst_wrap,
+ const uint8_t *src, int src_wrap,
+ int width, int height)
+{
+ if (!dst || !src)
+ return;
+
+ for (;height > 0; height--) {
+ memcpy(dst, src, width);
+ dst += dst_wrap;
+ src += src_wrap;
+ }
+}
+
+
+static int get_plane_bytewidth(int width, int plane)
+{
+ if (plane == 1 || plane == 2)
+ width = -((-width) >> 1);
+
+ return (width * 8 + 7) >> 3;
+}
+
+
+void picture_copy(uint8_t *data[4], uint16_t linesize[4],
+ const struct vidframe *frame)
+{
+ const int map[3] = {0, 2, 1};
+ int i;
+
+ for (i=0; i<3; i++) {
+ int h;
+ int bwidth = get_plane_bytewidth(frame->size.w, i);
+ h = frame->size.h;
+ if (i == 1 || i == 2) {
+ h = -((-frame->size.h) >> 1);
+ }
+ img_copy_plane(data[map[i]], linesize[map[i]],
+ frame->data[i], frame->linesize[i],
+ bwidth, h);
+ }
+}
diff --git a/modules/sdl2/module.mk b/modules/sdl2/module.mk
new file mode 100644
index 0000000..958f7ea
--- /dev/null
+++ b/modules/sdl2/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := sdl2
+$(MOD)_SRCS += sdl.c
+$(MOD)_LFLAGS += -lSDL2
+
+include mk/mod.mk
diff --git a/modules/sdl2/sdl.c b/modules/sdl2/sdl.c
new file mode 100644
index 0000000..c2413bb
--- /dev/null
+++ b/modules/sdl2/sdl.c
@@ -0,0 +1,343 @@
+/**
+ * @file sdl2/sdl.c Simple DirectMedia Layer module for SDL v2.0
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <SDL2/SDL.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup sdl2 sdl2
+ *
+ * Video display using Simple DirectMedia Layer version 2 (SDL2)
+ */
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /**< Inheritance (1st) */
+ SDL_Window *window; /**< SDL Window */
+ SDL_Renderer *renderer; /**< SDL Renderer */
+ SDL_Texture *texture; /**< Texture for pixels */
+ struct vidsz size; /**< Current size */
+ enum vidfmt fmt; /**< Current pixel format */
+ bool fullscreen; /**< Fullscreen flag */
+ struct tmr tmr;
+ Uint32 flags;
+};
+
+
+static struct vidisp *vid;
+
+
+static void event_handler(void *arg);
+
+
+static uint32_t match_fmt(enum vidfmt fmt)
+{
+ switch (fmt) {
+
+ case VID_FMT_YUV420P: return SDL_PIXELFORMAT_IYUV;
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+ case VID_FMT_NV12: return SDL_PIXELFORMAT_NV12;
+#endif
+ case VID_FMT_RGB32: return SDL_PIXELFORMAT_ARGB8888;
+ default: return SDL_PIXELFORMAT_UNKNOWN;
+ }
+}
+
+
+static uint32_t chroma_step(enum vidfmt fmt)
+{
+ switch (fmt) {
+
+ case VID_FMT_YUV420P: return 2;
+ case VID_FMT_NV12: return 1;
+ case VID_FMT_RGB32: return 0;
+ default: return 0;
+ }
+}
+
+
+static void sdl_reset(struct vidisp_st *st)
+{
+ if (st->texture) {
+ /*SDL_DestroyTexture(st->texture);*/
+ st->texture = NULL;
+ }
+
+ if (st->renderer) {
+ /*SDL_DestroyRenderer(st->renderer);*/
+ st->renderer = NULL;
+ }
+
+ if (st->window) {
+ SDL_DestroyWindow(st->window);
+ st->window = NULL;
+ }
+}
+
+
+static void event_handler(void *arg)
+{
+ struct vidisp_st *st = arg;
+ SDL_Event event;
+
+ tmr_start(&st->tmr, 100, event_handler, st);
+
+ /* NOTE: events must be checked from main thread */
+ while (SDL_PollEvent(&event)) {
+
+ if (event.type == SDL_KEYDOWN) {
+
+ switch (event.key.keysym.sym) {
+
+ case SDLK_f:
+ /* press key 'f' to toggle fullscreen */
+ st->fullscreen = !st->fullscreen;
+ info("sdl: %sable fullscreen mode\n",
+ st->fullscreen ? "en" : "dis");
+
+ if (st->fullscreen)
+ st->flags |=
+ SDL_WINDOW_FULLSCREEN_DESKTOP;
+ else
+ st->flags &=
+ ~SDL_WINDOW_FULLSCREEN_DESKTOP;
+
+ SDL_SetWindowFullscreen(st->window, st->flags);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ tmr_cancel(&st->tmr);
+ sdl_reset(st);
+}
+
+
+static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+
+ /* Not used by SDL */
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ if (!stp || !vd)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+ st->fullscreen = prm ? prm->fullscreen : false;
+
+ tmr_start(&st->tmr, 100, event_handler, st);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ void *pixels;
+ uint8_t *d;
+ int dpitch, ret;
+ unsigned i, h;
+ uint32_t format;
+
+ if (!st || !frame)
+ return EINVAL;
+
+ format = match_fmt(frame->fmt);
+ if (format == SDL_PIXELFORMAT_UNKNOWN) {
+ warning("sdl2: pixel format not supported (%s)\n",
+ vidfmt_name(frame->fmt));
+ return ENOTSUP;
+ }
+
+ if (!vidsz_cmp(&st->size, &frame->size) || frame->fmt != st->fmt) {
+ if (st->size.w && st->size.h) {
+ info("sdl: reset size:"
+ " %s %u x %u ---> %s %u x %u\n",
+ vidfmt_name(st->fmt), st->size.w, st->size.h,
+ vidfmt_name(frame->fmt),
+ frame->size.w, frame->size.h);
+ }
+ sdl_reset(st);
+ }
+
+ if (!st->window) {
+ char capt[256];
+
+ st->flags = SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS;
+
+ if (st->fullscreen)
+ st->flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+
+ if (title) {
+ re_snprintf(capt, sizeof(capt), "%s - %u x %u",
+ title, frame->size.w, frame->size.h);
+ }
+ else {
+ re_snprintf(capt, sizeof(capt), "%u x %u",
+ frame->size.w, frame->size.h);
+ }
+
+ st->window = SDL_CreateWindow(capt,
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ frame->size.w, frame->size.h,
+ st->flags);
+ if (!st->window) {
+ warning("sdl: unable to create sdl window: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+
+ st->size = frame->size;
+ st->fmt = frame->fmt;
+
+ SDL_RaiseWindow(st->window);
+ SDL_SetWindowBordered(st->window, true);
+ SDL_ShowWindow(st->window);
+ }
+
+ if (!st->renderer) {
+
+ Uint32 flags = 0;
+
+ flags |= SDL_RENDERER_ACCELERATED;
+ flags |= SDL_RENDERER_PRESENTVSYNC;
+
+ st->renderer = SDL_CreateRenderer(st->window, -1, flags);
+ if (!st->renderer) {
+ warning("sdl: unable to create renderer: %s\n",
+ SDL_GetError());
+ return ENOMEM;
+ }
+ }
+
+ if (!st->texture) {
+
+ st->texture = SDL_CreateTexture(st->renderer,
+ format,
+ SDL_TEXTUREACCESS_STREAMING,
+ frame->size.w, frame->size.h);
+ if (!st->texture) {
+ warning("sdl: unable to create texture: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+ }
+
+ ret = SDL_LockTexture(st->texture, NULL, &pixels, &dpitch);
+ if (ret != 0) {
+ warning("sdl: unable to lock texture (ret=%d)\n", ret);
+ return ENODEV;
+ }
+
+ d = pixels;
+ for (i=0; i<3; i++) {
+
+ const uint8_t *s = frame->data[i];
+ unsigned sz, dsz, hstep, wstep;
+
+ if (!frame->data[i] || !frame->linesize[i])
+ break;
+
+ hstep = i==0 ? 1 : 2;
+ wstep = i==0 ? 1 : chroma_step(frame->fmt);
+
+ dsz = dpitch / wstep;
+ sz = min(frame->linesize[i], dsz);
+
+ for (h = 0; h < frame->size.h; h += hstep) {
+
+ memcpy(d, s, sz);
+
+ s += frame->linesize[i];
+ d += dsz;
+ }
+ }
+
+ SDL_UnlockTexture(st->texture);
+
+ /* Blit the sprite onto the screen */
+ SDL_RenderCopy(st->renderer, st->texture, NULL, NULL);
+
+ /* Update the screen! */
+ SDL_RenderPresent(st->renderer);
+
+ return 0;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st || !st->window)
+ return;
+
+ SDL_HideWindow(st->window);
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ if (SDL_VideoInit(NULL) < 0) {
+ warning("sdl2: unable to init Video: %s\n",
+ SDL_GetError());
+ return ENODEV;
+ }
+
+ err = vidisp_register(&vid, baresip_vidispl(),
+ "sdl2", alloc, NULL, display, hide);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ SDL_VideoQuit();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sdl2) = {
+ "sdl2",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/selfview/module.mk b/modules/selfview/module.mk
new file mode 100644
index 0000000..2719433
--- /dev/null
+++ b/modules/selfview/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := selfview
+$(MOD)_SRCS += selfview.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/selfview/selfview.c b/modules/selfview/selfview.c
new file mode 100644
index 0000000..5cc1bed
--- /dev/null
+++ b/modules/selfview/selfview.c
@@ -0,0 +1,280 @@
+/**
+ * @file selfview.c Selfview Video-Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup selfview selfview
+ *
+ * Show a selfview of the captured video stream
+ *
+ * Example config:
+ \verbatim
+ video_selfview pip # {window,pip}
+ selfview_size 64x64
+ \endverbatim
+ */
+
+
+/* shared state */
+struct selfview {
+ struct lock *lock; /**< Protect frame */
+ struct vidframe *frame; /**< Copy of encoded frame */
+};
+
+struct selfview_enc {
+ struct vidfilt_enc_st vf; /**< Inheritance */
+ struct selfview *selfview; /**< Ref. to shared state */
+ struct vidisp_st *disp; /**< Selfview display */
+};
+
+struct selfview_dec {
+ struct vidfilt_dec_st vf; /**< Inheritance */
+ struct selfview *selfview; /**< Ref. to shared state */
+};
+
+
+static struct vidsz selfview_size = {0, 0};
+
+
+static void destructor(void *arg)
+{
+ struct selfview *st = arg;
+
+ lock_write_get(st->lock);
+ mem_deref(st->frame);
+ lock_rel(st->lock);
+ mem_deref(st->lock);
+}
+
+
+static void encode_destructor(void *arg)
+{
+ struct selfview_enc *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->selfview);
+ mem_deref(st->disp);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct selfview_dec *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->selfview);
+}
+
+
+static int selfview_alloc(struct selfview **selfviewp, void **ctx)
+{
+ struct selfview *selfview;
+ int err;
+
+ if (!selfviewp || !ctx)
+ return EINVAL;
+
+ if (*ctx) {
+ *selfviewp = mem_ref(*ctx);
+ }
+ else {
+ selfview = mem_zalloc(sizeof(*selfview), destructor);
+ if (!selfview)
+ return ENOMEM;
+
+ err = lock_alloc(&selfview->lock);
+ if (err)
+ return err;
+
+ *ctx = selfview;
+ *selfviewp = selfview;
+ }
+
+ return 0;
+}
+
+
+static int encode_update(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct selfview_enc *st;
+ int err;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = selfview_alloc(&st->selfview, ctx);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct vidfilt_dec_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct selfview_dec *st;
+ int err;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = selfview_alloc(&st->selfview, ctx);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode_win(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ struct selfview_enc *enc = (struct selfview_enc *)st;
+ int err;
+
+ if (!frame)
+ return 0;
+
+ if (!enc->disp) {
+
+ err = vidisp_alloc(&enc->disp, baresip_vidispl(),
+ NULL, NULL, NULL, NULL, NULL);
+ if (err)
+ return err;
+ }
+
+ return vidisp_display(enc->disp, "Selfview", frame);
+}
+
+
+static int encode_pip(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ struct selfview_enc *enc = (struct selfview_enc *)st;
+ struct selfview *selfview = enc->selfview;
+ int err = 0;
+
+ if (!frame)
+ return 0;
+
+ lock_write_get(selfview->lock);
+ if (!selfview->frame) {
+ struct vidsz sz;
+
+ /* Use size if configured, or else 20% of main window */
+ if (selfview_size.w && selfview_size.h) {
+ sz = selfview_size;
+ }
+ else {
+ sz.w = frame->size.w / 5;
+ sz.h = frame->size.h / 5;
+ }
+
+ err = vidframe_alloc(&selfview->frame, VID_FMT_YUV420P, &sz);
+ }
+ if (!err)
+ vidconv(selfview->frame, frame, NULL);
+ lock_rel(selfview->lock);
+
+ return err;
+}
+
+
+static int decode_pip(struct vidfilt_dec_st *st, struct vidframe *frame)
+{
+ struct selfview_dec *dec = (struct selfview_dec *)st;
+ struct selfview *sv = dec->selfview;
+
+ if (!frame)
+ return 0;
+
+ lock_read_get(sv->lock);
+ if (sv->frame) {
+ struct vidrect rect;
+
+ rect.w = min(sv->frame->size.w, frame->size.w/2);
+ rect.h = min(sv->frame->size.h, frame->size.h/2);
+ if (rect.w <= (frame->size.w - 10))
+ rect.x = frame->size.w - rect.w - 10;
+ else
+ rect.x = frame->size.w/2;
+ if (rect.h <= (frame->size.h - 10))
+ rect.y = frame->size.h - rect.h - 10;
+ else
+ rect.y = frame->size.h/2;
+
+ vidconv(frame, sv->frame, &rect);
+
+ vidframe_draw_rect(frame, rect.x, rect.y, rect.w, rect.h,
+ 127, 127, 127);
+ }
+ lock_rel(sv->lock);
+
+ return 0;
+}
+
+
+static struct vidfilt selfview_win = {
+ LE_INIT, "selfview_window", encode_update, encode_win, NULL, NULL
+};
+static struct vidfilt selfview_pip = {
+ LE_INIT, "selfview_pip",
+ encode_update, encode_pip, decode_update, decode_pip
+};
+
+
+static int module_init(void)
+{
+ struct pl pl = PL("pip");
+
+ (void)conf_get(conf_cur(), "video_selfview", &pl);
+
+ if (0 == pl_strcasecmp(&pl, "window"))
+ vidfilt_register(baresip_vidfiltl(), &selfview_win);
+ else if (0 == pl_strcasecmp(&pl, "pip"))
+ vidfilt_register(baresip_vidfiltl(), &selfview_pip);
+
+ (void)conf_get_vidsz(conf_cur(), "selfview_size", &selfview_size);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&selfview_win);
+ vidfilt_unregister(&selfview_pip);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(selfview) = {
+ "selfview",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/silk/module.mk b/modules/silk/module.mk
new file mode 100644
index 0000000..ac8ebb1
--- /dev/null
+++ b/modules/silk/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := silk
+$(MOD)_SRCS += silk.c
+$(MOD)_LFLAGS += -lSKP_SILK_SDK
+
+include mk/mod.mk
diff --git a/modules/silk/silk.c b/modules/silk/silk.c
new file mode 100644
index 0000000..dee296c
--- /dev/null
+++ b/modules/silk/silk.c
@@ -0,0 +1,262 @@
+/**
+ * @file silk.c Skype SILK audio codec
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <silk/SKP_Silk_SDK_API.h>
+
+
+/**
+ * @defgroup silk silk
+ *
+ * The Skype SILK audio codec
+ *
+ * References: https://developer.skype.com/silk
+ */
+
+
+enum {
+ MAX_BYTES_PER_FRAME = 250,
+ MAX_FRAME_SIZE = 2*480,
+};
+
+
+struct auenc_state {
+ void *enc;
+ SKP_SILK_SDK_EncControlStruct encControl;
+};
+
+struct audec_state {
+ void *dec;
+ SKP_SILK_SDK_DecControlStruct decControl;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ mem_deref(st->enc);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ mem_deref(st->dec);
+}
+
+
+static int encode_update(struct auenc_state **aesp,
+ const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int ret, err = 0;
+ int32_t enc_size;
+ (void)fmtp;
+
+ if (!aesp || !ac || !prm)
+ return EINVAL;
+ if (*aesp)
+ return 0;
+
+ ret = SKP_Silk_SDK_Get_Encoder_Size(&enc_size);
+ if (ret || enc_size <= 0)
+ return EINVAL;
+
+ st = mem_alloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->enc = mem_alloc(enc_size, NULL);
+ if (!st->enc) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ret = SKP_Silk_SDK_InitEncoder(st->enc, &st->encControl);
+ if (ret) {
+ err = EPROTO;
+ goto out;
+ }
+
+ st->encControl.API_sampleRate = ac->srate;
+ st->encControl.maxInternalSampleRate = ac->srate;
+ st->encControl.packetSize = prm->ptime * ac->srate / 1000;
+ st->encControl.bitRate = 64000;
+ st->encControl.complexity = 2;
+ st->encControl.useInBandFEC = 0;
+ st->encControl.useDTX = 0;
+
+ info("silk: encoder: %dHz, psize=%d, bitrate=%d, complex=%d,"
+ " fec=%d, dtx=%d\n",
+ st->encControl.API_sampleRate,
+ st->encControl.packetSize,
+ st->encControl.bitRate,
+ st->encControl.complexity,
+ st->encControl.useInBandFEC,
+ st->encControl.useDTX);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int ret, err = 0;
+ int32_t dec_size;
+ (void)fmtp;
+
+ if (*adsp)
+ return 0;
+
+ ret = SKP_Silk_SDK_Get_Decoder_Size(&dec_size);
+ if (ret || dec_size <= 0)
+ return EINVAL;
+
+ st = mem_alloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->dec = mem_alloc(dec_size, NULL);
+ if (!st->dec) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ret = SKP_Silk_SDK_InitDecoder(st->dec);
+ if (ret) {
+ err = EPROTO;
+ goto out;
+ }
+
+ st->decControl.API_sampleRate = ac->srate;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int ret;
+ int16_t nBytesOut;
+
+ if (*len < MAX_BYTES_PER_FRAME)
+ return ENOMEM;
+
+ nBytesOut = *len;
+ ret = SKP_Silk_SDK_Encode(st->enc,
+ &st->encControl,
+ sampv,
+ (int)sampc,
+ buf,
+ &nBytesOut);
+ if (ret) {
+ warning("silk: SKP_Silk_SDK_Encode: ret=%d\n", ret);
+ }
+
+ *len = nBytesOut;
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ int16_t nsamp = *sampc;
+ int ret;
+
+ ret = SKP_Silk_SDK_Decode(st->dec,
+ &st->decControl,
+ 0,
+ buf,
+ (int)len,
+ sampv,
+ &nsamp);
+ if (ret) {
+ warning("silk: SKP_Silk_SDK_Decode: ret=%d\n", ret);
+ }
+
+ *sampc = nsamp;
+
+ return 0;
+}
+
+
+static int plc(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ int16_t nsamp = *sampc;
+ int ret;
+
+ ret = SKP_Silk_SDK_Decode(st->dec,
+ &st->decControl,
+ 1,
+ NULL,
+ 0,
+ sampv,
+ &nsamp);
+ if (ret)
+ return EPROTO;
+
+ *sampc = nsamp;
+
+ return 0;
+}
+
+
+static struct aucodec silk[] = {
+ {
+ LE_INIT, 0, "SILK", 24000, 24000, 1, NULL,
+ encode_update, encode, decode_update, decode, plc, 0, 0
+ },
+
+};
+
+
+static int module_init(void)
+{
+ debug("silk: SILK %s\n", SKP_Silk_SDK_get_version());
+
+ aucodec_register(baresip_aucodecl(), &silk[0]);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ int i = ARRAY_SIZE(silk);
+
+ while (i--)
+ aucodec_unregister(&silk[i]);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(silk) = {
+ "silk",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/snapshot/module.mk b/modules/snapshot/module.mk
new file mode 100644
index 0000000..1be53bd
--- /dev/null
+++ b/modules/snapshot/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := snapshot
+$(MOD)_SRCS += snapshot.c png_vf.c
+$(MOD)_LFLAGS += -lpng
+
+include mk/mod.mk
diff --git a/modules/snapshot/png_vf.c b/modules/snapshot/png_vf.c
new file mode 100644
index 0000000..6c5c18a
--- /dev/null
+++ b/modules/snapshot/png_vf.c
@@ -0,0 +1,189 @@
+/**
+ * @file png_vf.c Write vidframe to a PNG-file
+ *
+ * Author: Doug Blewett
+ * Review: Alfred E. Heggestad
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <time.h>
+#include <png.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "png_vf.h"
+
+
+static char *png_filename(const struct tm *tmx, const char *name,
+ char *buf, unsigned int length);
+static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers,
+ int png_height);
+
+
+int png_save_vidframe(const struct vidframe *vf, const char *path)
+{
+ png_byte **png_row_pointers = NULL;
+ png_byte *row;
+ const png_byte *p;
+ png_byte red, green, blue;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ FILE *fp = NULL;
+ size_t x, y;
+ unsigned int width = vf->size.w & ~1;
+ unsigned int height = vf->size.h & ~1;
+ unsigned int bytes_per_pixel = 3; /* RGB format */
+ time_t tnow;
+ struct tm *tmx;
+ char filename_buf[64];
+ struct vidframe *f2 = NULL;
+ int err = 0;
+
+ tnow = time(NULL);
+ tmx = localtime(&tnow);
+
+ if (vf->fmt != VID_FMT_RGB32) {
+
+ err = vidframe_alloc(&f2, VID_FMT_RGB32, &vf->size);
+ if (err)
+ goto out;
+
+ vidconv(f2, vf, NULL);
+ vf = f2;
+ }
+
+ /* Initialize the write struct. */
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL);
+ if (png_ptr == NULL) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Initialize the info struct. */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Set up error handling. */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Set image attributes. */
+ png_set_IHDR(png_ptr,
+ info_ptr,
+ width,
+ height,
+ 8,
+ PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ /* Initialize rows of PNG
+ * bytes_per_row = width * bytes_per_pixel;
+ */
+ png_row_pointers = png_malloc(png_ptr,
+ height * sizeof(png_byte *));
+
+ for (y = 0; y < height; ++y) {
+ png_row_pointers[y] =
+ (png_byte *) png_malloc(png_ptr,
+ width * sizeof(uint8_t) *
+ bytes_per_pixel);
+ }
+
+ p = vf->data[0];
+ for (y = 0; y < height; ++y) {
+
+ row = png_row_pointers[y];
+
+ for (x = 0; x < width; ++x) {
+
+ red = *p++;
+ green = *p++;
+ blue = *p++;
+
+ *row++ = blue;
+ *row++ = green;
+ *row++ = red;
+
+ ++p; /* skip alpha */
+ }
+ }
+
+ /* Write the image data. */
+ fp = fopen(png_filename(tmx, path,
+ filename_buf, sizeof(filename_buf)), "wb");
+ if (fp == NULL) {
+ err = errno;
+ goto out;
+ }
+
+ png_init_io(png_ptr, fp);
+ png_set_rows(png_ptr, info_ptr, png_row_pointers);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+
+ info("png: wrote %s\n", filename_buf);
+
+ out:
+ /* Finish writing. */
+ mem_deref(f2);
+ png_save_free(png_ptr, png_row_pointers, height);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (fp)
+ fclose(fp);
+
+ return 0;
+}
+
+
+static void png_save_free(png_structp png_ptr, png_byte **png_row_pointers,
+ int png_height)
+{
+ int y;
+
+ /* Cleanup. */
+ if (png_height == 0 || png_row_pointers == NULL)
+ return;
+
+ for (y = 0; y < png_height; y++) {
+ png_free(png_ptr, png_row_pointers[y]);
+ }
+ png_free(png_ptr, png_row_pointers);
+}
+
+
+static char *png_filename(const struct tm *tmx, const char *name,
+ char *buf, unsigned int length)
+{
+ /*
+ * -2013-03-03-15-22-56.png - 24 chars
+ */
+ if (strlen(name) + 24 >= length) {
+ buf[0] = '\0';
+ return buf;
+ }
+
+ sprintf(buf, (tmx->tm_mon < 9 ? "%s-%d-0%d" : "%s-%d-%d"), name,
+ 1900 + tmx->tm_year, tmx->tm_mon + 1);
+
+ sprintf(buf + strlen(buf), (tmx->tm_mday < 10 ? "-0%d" : "-%d"),
+ tmx->tm_mday);
+
+ sprintf(buf + strlen(buf), (tmx->tm_hour < 10 ? "-0%d" : "-%d"),
+ tmx->tm_hour);
+
+ sprintf(buf + strlen(buf), (tmx->tm_min < 10 ? "-0%d" : "-%d"),
+ tmx->tm_min);
+
+ sprintf(buf + strlen(buf), (tmx->tm_sec < 10 ? "-0%d.png" : "-%d.png"),
+ tmx->tm_sec);
+
+ return buf;
+}
diff --git a/modules/snapshot/png_vf.h b/modules/snapshot/png_vf.h
new file mode 100644
index 0000000..17660cf
--- /dev/null
+++ b/modules/snapshot/png_vf.h
@@ -0,0 +1,6 @@
+/**
+ * @file png_vf.h
+ */
+
+
+int png_save_vidframe(const struct vidframe *vf, const char *path);
diff --git a/modules/snapshot/snapshot.c b/modules/snapshot/snapshot.c
new file mode 100644
index 0000000..4dd5735
--- /dev/null
+++ b/modules/snapshot/snapshot.c
@@ -0,0 +1,104 @@
+/**
+ * @file snapshot.c Snapshot Video-Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "png_vf.h"
+
+
+/**
+ * @defgroup snapshot snapshot
+ *
+ * Take snapshot of the video stream and save it as PNG-files
+ *
+ *
+ * Commands:
+ *
+ \verbatim
+ snapshot Take video snapshot
+ \endverbatim
+ */
+
+
+static bool flag_enc, flag_dec;
+
+
+static int encode(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ (void)st;
+
+ if (!frame)
+ return 0;
+
+ if (flag_enc) {
+ flag_enc = false;
+ png_save_vidframe(frame, "snapshot-send");
+ }
+
+ return 0;
+}
+
+
+static int decode(struct vidfilt_dec_st *st, struct vidframe *frame)
+{
+ (void)st;
+
+ if (!frame)
+ return 0;
+
+ if (flag_dec) {
+ flag_dec = false;
+ png_save_vidframe(frame, "snapshot-recv");
+ }
+
+ return 0;
+}
+
+
+static int do_snapshot(struct re_printf *pf, void *arg)
+{
+ (void)pf;
+ (void)arg;
+
+ /* NOTE: not re-entrant */
+ flag_enc = flag_dec = true;
+
+ return 0;
+}
+
+
+static struct vidfilt snapshot = {
+ LE_INIT, "snapshot", NULL, encode, NULL, decode,
+};
+
+
+static const struct cmd cmdv[] = {
+ {"snapshot", 0, 0, "Take video snapshot", do_snapshot },
+};
+
+
+static int module_init(void)
+{
+ vidfilt_register(baresip_vidfiltl(), &snapshot);
+ return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&snapshot);
+ cmd_unregister(baresip_commands(), cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(snapshot) = {
+ "snapshot",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/sndfile/module.mk b/modules/sndfile/module.mk
new file mode 100644
index 0000000..7fed4de
--- /dev/null
+++ b/modules/sndfile/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := sndfile
+$(MOD)_SRCS += sndfile.c
+$(MOD)_LFLAGS += -lsndfile
+
+include mk/mod.mk
diff --git a/modules/sndfile/sndfile.c b/modules/sndfile/sndfile.c
new file mode 100644
index 0000000..ac31bbb
--- /dev/null
+++ b/modules/sndfile/sndfile.c
@@ -0,0 +1,200 @@
+/**
+ * @file sndfile.c Audio dumper using libsndfile
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <sndfile.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup sndfile sndfile
+ *
+ * Audio filter that writes audio samples to WAV-file
+ *
+ * Example Configuration:
+ \verbatim
+ snd_path /tmp/
+ \endverbatim
+ */
+
+
+struct sndfile_enc {
+ struct aufilt_enc_st af; /* base class */
+ SNDFILE *enc;
+};
+
+struct sndfile_dec {
+ struct aufilt_dec_st af; /* base class */
+ SNDFILE *dec;
+};
+
+static char file_path[256] = ".";
+
+
+static int timestamp_print(struct re_printf *pf, const struct tm *tm)
+{
+ if (!tm)
+ return 0;
+
+ return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d",
+ 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+
+static void enc_destructor(void *arg)
+{
+ struct sndfile_enc *st = arg;
+
+ if (st->enc)
+ sf_close(st->enc);
+
+ list_unlink(&st->af.le);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct sndfile_dec *st = arg;
+
+ if (st->dec)
+ sf_close(st->dec);
+
+ list_unlink(&st->af.le);
+}
+
+
+static SNDFILE *openfile(const struct aufilt_prm *prm, bool enc)
+{
+ char filename[128];
+ SF_INFO sfinfo;
+ time_t tnow = time(0);
+ struct tm *tm = localtime(&tnow);
+ SNDFILE *sf;
+
+ (void)re_snprintf(filename, sizeof(filename),
+ "%s/dump-%H-%s.wav",
+ file_path,
+ timestamp_print, tm, enc ? "enc" : "dec");
+
+ sfinfo.samplerate = prm->srate;
+ sfinfo.channels = prm->ch;
+ sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+
+ sf = sf_open(filename, SFM_WRITE, &sfinfo);
+ if (!sf) {
+ warning("sndfile: could not open: %s\n", filename);
+ puts(sf_strerror(NULL));
+ return NULL;
+ }
+
+ info("sndfile: dumping %s audio to %s\n",
+ enc ? "encode" : "decode", filename);
+
+ return sf;
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct sndfile_enc *st;
+ int err = 0;
+ (void)ctx;
+ (void)af;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return EINVAL;
+
+ st->enc = openfile(prm, true);
+ if (!st->enc)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct sndfile_dec *st;
+ int err = 0;
+ (void)ctx;
+ (void)af;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return EINVAL;
+
+ st->dec = openfile(prm, false);
+ if (!st->dec)
+ err = ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct sndfile_enc *sf = (struct sndfile_enc *)st;
+
+ sf_write_short(sf->enc, sampv, *sampc);
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct sndfile_dec *sf = (struct sndfile_dec *)st;
+
+ sf_write_short(sf->dec, sampv, *sampc);
+
+ return 0;
+}
+
+
+static struct aufilt sndfile = {
+ LE_INIT, "sndfile", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ aufilt_register(baresip_aufiltl(), &sndfile);
+
+ conf_get_str(conf_cur(), "snd_path", file_path, sizeof(file_path));
+
+ info("sndfile: saving files in %s\n", file_path);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&sndfile);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sndfile) = {
+ "sndfile",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/sndio/module.mk b/modules/sndio/module.mk
new file mode 100644
index 0000000..79ace67
--- /dev/null
+++ b/modules/sndio/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2014 Creytiv.com
+#
+
+MOD := sndio
+$(MOD)_SRCS += sndio.c
+$(MOD)_LFLAGS += -lsndio
+
+include mk/mod.mk
diff --git a/modules/sndio/sndio.c b/modules/sndio/sndio.c
new file mode 100644
index 0000000..6ac4b67
--- /dev/null
+++ b/modules/sndio/sndio.c
@@ -0,0 +1,319 @@
+/**
+ * @file sndio.c SndIO sound driver
+ *
+ * Copyright (C) 2014 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <sndio.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup sndio sndio
+ *
+ * This module implements audio driver for OpenBSD sndio
+ */
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* pointer to base-class */
+ struct sio_hdl *hdl;
+ pthread_t thread;
+ int16_t *sampv;
+ size_t sampc;
+ int run;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+struct auplay_st {
+ const struct auplay *ap; /* pointer to base-class */
+ struct sio_hdl *hdl;
+ pthread_t thread;
+ int16_t *sampv;
+ size_t sampc;
+ int run;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+
+static struct sio_par *sndio_initpar(uint32_t srate, uint8_t ch)
+{
+ struct sio_par *par = NULL;
+
+ if ((par = mem_zalloc(sizeof(*par), NULL)) == NULL)
+ return NULL;
+
+ sio_initpar(par);
+
+ /* sndio doesn't support a-low and u-low */
+ par->bits = 16;
+ par->bps = SIO_BPS(par->bits);
+ par->sig = 1;
+ par->le = SIO_LE_NATIVE;
+
+ par->rchan = ch;
+ par->pchan = ch;
+ par->rate = srate;
+
+ return par;
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (!sio_start(st->hdl)) {
+ warning("sndio: could not start record\n");
+ goto out;
+ }
+
+ while (st->run) {
+ size_t n = sio_read(st->hdl, st->sampv, st->sampc*2);
+ st->rh(st->sampv, n/2, st->arg);
+ }
+
+ out:
+ return NULL;
+}
+
+
+static void *write_thread(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ if (!sio_start(st->hdl)) {
+ warning("sndio: could not start playback\n");
+ goto out;
+ }
+
+ while (st->run) {
+ st->wh(st->sampv, st->sampc, st->arg);
+ sio_write(st->hdl, st->sampv, st->sampc*2);
+ }
+
+ out:
+ return NULL;
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->hdl)
+ sio_close(st->hdl);
+
+ mem_deref(st->sampv);
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ (void)pthread_join(st->thread, NULL);
+ }
+
+ if (st->hdl)
+ sio_close(st->hdl);
+
+ mem_deref(st->sampv);
+}
+
+
+static int src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ struct sio_par *par = NULL;
+ int err;
+ const char *name;
+
+ (void)ctx;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("sndio: source: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ name = (str_isset(device)) ? device : SIO_DEVANY;
+
+ if ((st = mem_zalloc(sizeof(*st), ausrc_destructor)) == NULL)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+ st->hdl = sio_open(name, SIO_REC, 0);
+
+ if (!st->hdl) {
+ warning("sndio: could not open ausrc device '%s'\n", name);
+ err = EINVAL;
+ goto out;
+ }
+
+ par = sndio_initpar(prm->srate, prm->ch);
+ if (!par) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (!sio_setpar(st->hdl, par)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ if (!sio_getpar(st->hdl, par)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ st->sampc = par->bufsz / 2;
+
+ st->sampv = mem_alloc(2 * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err)
+ st->run = false;
+
+ out:
+ mem_deref(par);
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ struct sio_par *par = NULL;
+ int err;
+ const char *name;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("sndio: playback: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ name = (str_isset(device)) ? device : SIO_DEVANY;
+
+ if ((st = mem_zalloc(sizeof(*st), auplay_destructor)) == NULL)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+ st->hdl = sio_open(name, SIO_PLAY, 0);
+
+ if (!st->hdl) {
+ warning("sndio: could not open auplay device '%s'\n", name);
+ err = EINVAL;
+ goto out;
+ }
+
+ par = sndio_initpar(prm->srate, prm->ch);
+ if (!par) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (!sio_setpar(st->hdl, par)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ if (!sio_getpar(st->hdl, par)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->sampv = mem_alloc(2 * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, write_thread, st);
+ if (err)
+ st->run = false;
+
+ out:
+ mem_deref(par);
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int sndio_init(void)
+{
+ int err = 0;
+
+ err |= ausrc_register(&ausrc, baresip_ausrcl(), "sndio", src_alloc);
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "sndio", play_alloc);
+
+ return err;
+}
+
+
+static int sndio_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(sndio) = {
+ "sndio",
+ "sound",
+ sndio_init,
+ sndio_close
+};
diff --git a/modules/speex/module.mk b/modules/speex/module.mk
new file mode 100644
index 0000000..81c8b18
--- /dev/null
+++ b/modules/speex/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex
+$(MOD)_SRCS += speex.c
+$(MOD)_LFLAGS += -lspeex
+$(MOD)_CFLAGS += -Wno-strict-prototypes
+
+include mk/mod.mk
diff --git a/modules/speex/speex.c b/modules/speex/speex.c
new file mode 100644
index 0000000..33b52e2
--- /dev/null
+++ b/modules/speex/speex.c
@@ -0,0 +1,519 @@
+/**
+ * @file speex.c Speex audio codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <speex/speex.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup speex speex
+ *
+ * The Speex audio codec
+ *
+ * NOTE: The Speex codec has been obsoleted by Opus.
+ */
+
+
+enum {
+ MIN_FRAME_SIZE = 43,
+ SPEEX_PTIME = 20,
+};
+
+
+struct auenc_state {
+ void *enc;
+ SpeexBits bits;
+
+ uint32_t frame_size; /* Number of sample-frames */
+ uint8_t channels;
+};
+
+
+struct audec_state {
+ void *dec;
+ SpeexBits bits;
+ SpeexStereoState stereo;
+ SpeexCallback callback;
+
+ uint32_t frame_size; /* Number of sample-frames */
+ uint8_t channels;
+};
+
+
+static char speex_fmtp_nb[128];
+static char speex_fmtp_wb[128];
+
+
+/** Speex configuration */
+static struct {
+ int quality;
+ int complexity;
+ int enhancement;
+ int mode_nb;
+ int mode_wb;
+ int vbr;
+ int vad;
+} sconf = {
+ 3, /* 0-10 */
+ 2, /* 0-10 */
+ 0, /* 0 or 1 */
+ 3, /* 1-6 */
+ 6, /* 1-6 */
+ 0, /* 0 or 1 */
+ 0 /* 0 or 1 */
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct auenc_state *st = arg;
+
+ speex_bits_destroy(&st->bits);
+ speex_encoder_destroy(st->enc);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct audec_state *st = arg;
+
+ speex_bits_destroy(&st->bits);
+ speex_decoder_destroy(st->dec);
+}
+
+
+static void encoder_config(void *st)
+{
+ int ret;
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_QUALITY, &sconf.quality);
+ if (ret) {
+ warning("speex: SPEEX_SET_QUALITY: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &sconf.complexity);
+ if (ret) {
+ warning("speex: SPEEX_SET_COMPLEXITY: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_VBR, &sconf.vbr);
+ if (ret) {
+ warning("speex: SPEEX_SET_VBR: %d\n", ret);
+ }
+
+ ret = speex_encoder_ctl(st, SPEEX_SET_VAD, &sconf.vad);
+ if (ret) {
+ warning("speex: SPEEX_SET_VAD: %d\n", ret);
+ }
+}
+
+
+static void decoder_config(void *st)
+{
+ int ret;
+
+ ret = speex_decoder_ctl(st, SPEEX_SET_ENH, &sconf.enhancement);
+ if (ret) {
+ warning("speex: SPEEX_SET_ENH: %d\n", ret);
+ }
+}
+
+
+static int decode_param(struct auenc_state *st, const struct pl *name,
+ const struct pl *val)
+{
+ int ret;
+
+ /* mode: List supported Speex decoding modes. The valid modes are
+ different for narrowband and wideband, and are defined as follows:
+
+ {1,2,3,4,5,6,any}
+ */
+ if (0 == pl_strcasecmp(name, "mode")) {
+ struct pl v;
+ int mode;
+
+ /* parameter is quoted */
+ if (re_regex(val->p, val->l, "\"[^\"]+\"", &v))
+ v = *val;
+
+ if (0 == pl_strcasecmp(&v, "any"))
+ return 0;
+
+ mode = pl_u32(&v);
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_MODE, &mode);
+ if (ret) {
+ warning("speex: SPEEX_SET_MODE: ret=%d\n", ret);
+ }
+ }
+ /* vbr: variable bit rate - either 'on' 'off' or 'vad' */
+ else if (0 == pl_strcasecmp(name, "vbr")) {
+ int vbr = 0, vad = 0;
+
+ if (0 == pl_strcasecmp(val, "on"))
+ vbr = 1;
+ else if (0 == pl_strcasecmp(val, "off"))
+ vbr = 0;
+ else if (0 == pl_strcasecmp(val, "vad"))
+ vad = 1;
+ else {
+ warning("speex: invalid vbr value %r\n", val);
+ }
+
+ debug("speex: setting VBR=%d VAD=%d\n", vbr, vad);
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_VBR, &vbr);
+ if (ret) {
+ warning("speex: SPEEX_SET_VBR: ret=%d\n", ret);
+ }
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_VAD, &vad);
+ if (ret) {
+ warning("speex: SPEEX_SET_VAD: ret=%d\n", ret);
+ }
+ }
+ else if (0 == pl_strcasecmp(name, "cng")) {
+ int dtx = 0;
+
+ if (0 == pl_strcasecmp(val, "on"))
+ dtx = 0;
+ else if (0 == pl_strcasecmp(val, "off"))
+ dtx = 1;
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_SET_DTX, &dtx);
+ if (ret) {
+ warning("speex: SPEEX_SET_DTX: ret=%d\n", ret);
+ }
+ }
+ else {
+ debug("speex: unknown Speex param: %r=%r\n", name, val);
+ }
+
+ return 0;
+}
+
+
+static void param_handler(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct auenc_state *st = arg;
+
+ decode_param(st, name, val);
+}
+
+
+static const SpeexMode *resolve_mode(uint32_t srate)
+{
+ switch (srate) {
+
+ default:
+ case 8000: return &speex_nb_mode;
+ case 16000: return &speex_wb_mode;
+ case 32000: return &speex_uwb_mode;
+ }
+}
+
+
+static int encode_update(struct auenc_state **aesp, const struct aucodec *ac,
+ struct auenc_param *prm, const char *fmtp)
+{
+ struct auenc_state *st;
+ int ret, err = 0;
+
+ if (!aesp || !ac || !prm)
+ return EINVAL;
+ if (prm->ptime != SPEEX_PTIME)
+ return EPROTO;
+ if (*aesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->frame_size = ac->srate * SPEEX_PTIME / 1000;
+ st->channels = ac->ch;
+
+ /* Encoder */
+ st->enc = speex_encoder_init(resolve_mode(ac->srate));
+ if (!st->enc) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ speex_bits_init(&st->bits);
+
+ encoder_config(st->enc);
+
+ ret = speex_encoder_ctl(st->enc, SPEEX_GET_FRAME_SIZE,
+ &st->frame_size);
+ if (ret) {
+ warning("speex: SPEEX_GET_FRAME_SIZE: %d\n", ret);
+ }
+
+ if (str_isset(fmtp)) {
+ struct pl params;
+
+ pl_set_str(&params, fmtp);
+
+ fmt_param_apply(&params, param_handler, st);
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+static int decode_update(struct audec_state **adsp,
+ const struct aucodec *ac, const char *fmtp)
+{
+ struct audec_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!adsp || !ac)
+ return EINVAL;
+ if (*adsp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->frame_size = ac->srate * SPEEX_PTIME / 1000;
+ st->channels = ac->ch;
+
+ /* Decoder */
+ st->dec = speex_decoder_init(resolve_mode(ac->srate));
+ if (!st->dec) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ speex_bits_init(&st->bits);
+
+ if (2 == st->channels) {
+
+ /* Stereo. */
+ st->stereo.balance = 1;
+ st->stereo.e_ratio = .5f;
+ st->stereo.smooth_left = 1;
+ st->stereo.smooth_right = 1;
+
+ st->callback.callback_id = SPEEX_INBAND_STEREO;
+ st->callback.func = speex_std_stereo_request_handler;
+ st->callback.data = &st->stereo;
+ speex_decoder_ctl(st->dec, SPEEX_SET_HANDLER,
+ &st->callback);
+ }
+
+ decoder_config(st->dec);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *adsp = st;
+
+ return err;
+}
+
+
+static int encode(struct auenc_state *st, uint8_t *buf,
+ size_t *len, const int16_t *sampv, size_t sampc)
+{
+ const size_t n = st->channels * st->frame_size;
+ int ret, r;
+
+ if (*len < 128)
+ return ENOMEM;
+
+ /* VAD */
+ if (!sampv || !sampc) {
+ /* 5 zeros interpreted by Speex as silence (submode 0) */
+ speex_bits_pack(&st->bits, 0, 5);
+ goto out;
+ }
+
+ /* Handle multiple Speex frames in one RTP packet */
+ while (sampc > 0) {
+
+ /* Assume stereo */
+ if (2 == st->channels) {
+ speex_encode_stereo_int((int16_t *)sampv,
+ st->frame_size, &st->bits);
+ }
+
+ ret = speex_encode_int(st->enc, (int16_t *)sampv, &st->bits);
+ if (1 != ret) {
+ warning("speex: speex_encode_int: ret=%d\n", ret);
+ }
+
+ sampc -= n;
+ sampv += n;
+ }
+
+ out:
+ /* Terminate bit stream */
+ speex_bits_pack(&st->bits, 15, 5);
+
+ r = speex_bits_write(&st->bits, (char *)buf, (int)*len);
+ *len = r;
+
+ speex_bits_reset(&st->bits);
+
+ return 0;
+}
+
+
+static int decode(struct audec_state *st, int16_t *sampv,
+ size_t *sampc, const uint8_t *buf, size_t len)
+{
+ const size_t n = st->channels * st->frame_size;
+ size_t i = 0;
+
+ /* Read into bit-stream */
+ speex_bits_read_from(&st->bits, (char *)buf, (int)len);
+
+ /* Handle multiple Speex frames in one RTP packet */
+ while (speex_bits_remaining(&st->bits) >= MIN_FRAME_SIZE) {
+ int ret;
+
+ if (*sampc < n)
+ return ENOMEM;
+
+ ret = speex_decode_int(st->dec, &st->bits,
+ (int16_t *)&sampv[i]);
+ if (ret < 0) {
+ if (-1 == ret) {
+ }
+ else if (-2 == ret) {
+ warning("speex: decode: corrupt stream\n");
+ }
+ else {
+ warning("speex: decode: speex_decode_int:"
+ " ret=%d\n", ret);
+ }
+ break;
+ }
+
+ /* Transforms a mono frame into a stereo frame
+ using intensity stereo info */
+ if (2 == st->channels) {
+ speex_decode_stereo_int((int16_t *)&sampv[i],
+ st->frame_size,
+ &st->stereo);
+ }
+
+ i += n;
+ *sampc -= n;
+ }
+
+ *sampc = i;
+
+ return 0;
+}
+
+
+static int pkloss(struct audec_state *st, int16_t *sampv, size_t *sampc)
+{
+ const size_t n = st->channels * st->frame_size;
+
+ if (*sampc < n)
+ return ENOMEM;
+
+ /* Silence */
+ speex_decode_int(st->dec, NULL, sampv);
+ *sampc = n;
+
+ return 0;
+}
+
+
+static void config_parse(struct conf *conf)
+{
+ uint32_t v;
+
+ if (0 == conf_get_u32(conf, "speex_quality", &v))
+ sconf.quality = v;
+ if (0 == conf_get_u32(conf, "speex_complexity", &v))
+ sconf.complexity = v;
+ if (0 == conf_get_u32(conf, "speex_enhancement", &v))
+ sconf.enhancement = v;
+ if (0 == conf_get_u32(conf, "speex_mode_nb", &v))
+ sconf.mode_nb = v;
+ if (0 == conf_get_u32(conf, "speex_mode_wb", &v))
+ sconf.mode_wb = v;
+ if (0 == conf_get_u32(conf, "speex_vbr", &v))
+ sconf.vbr = v;
+ if (0 == conf_get_u32(conf, "speex_vad", &v))
+ sconf.vad = v;
+}
+
+
+static struct aucodec speexv[] = {
+
+ /* Stereo Speex */
+ {LE_INIT, 0, "speex", 32000, 32000, 2, speex_fmtp_wb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 16000, 16000, 2, speex_fmtp_wb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 8000, 8000, 2, speex_fmtp_nb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+
+ /* Standard Speex */
+ {LE_INIT, 0, "speex", 32000, 32000, 1, speex_fmtp_wb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 16000, 16000, 1, speex_fmtp_wb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+ {LE_INIT, 0, "speex", 8000, 8000, 1, speex_fmtp_nb,
+ encode_update, encode, decode_update, decode, pkloss, 0, 0},
+};
+
+
+static int speex_init(void)
+{
+ size_t i;
+
+ config_parse(conf_cur());
+
+ (void)re_snprintf(speex_fmtp_nb, sizeof(speex_fmtp_nb),
+ "mode=\"%d\";vbr=%s;cng=on", sconf.mode_nb,
+ sconf.vad ? "vad" : (sconf.vbr ? "on" : "off"));
+
+ (void)re_snprintf(speex_fmtp_wb, sizeof(speex_fmtp_wb),
+ "mode=\"%d\";vbr=%s;cng=on", sconf.mode_wb,
+ sconf.vad ? "vad" : (sconf.vbr ? "on" : "off"));
+
+ for (i=0; i<ARRAY_SIZE(speexv); i++)
+ aucodec_register(baresip_aucodecl(), &speexv[i]);
+
+ return 0;
+}
+
+
+static int speex_close(void)
+{
+ size_t i;
+ for (i=0; i<ARRAY_SIZE(speexv); i++)
+ aucodec_unregister(&speexv[i]);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex) = {
+ "speex",
+ "codec",
+ speex_init,
+ speex_close
+};
diff --git a/modules/speex_aec/module.mk b/modules/speex_aec/module.mk
new file mode 100644
index 0000000..9e29696
--- /dev/null
+++ b/modules/speex_aec/module.mk
@@ -0,0 +1,15 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex_aec
+$(MOD)_SRCS += speex_aec.c
+ifneq ($(HAVE_SPEEXDSP),)
+$(MOD)_LFLAGS += "-lspeexdsp"
+else
+$(MOD)_LFLAGS += "-lspeex"
+endif
+
+include mk/mod.mk
diff --git a/modules/speex_aec/speex_aec.c b/modules/speex_aec/speex_aec.c
new file mode 100644
index 0000000..5377a06
--- /dev/null
+++ b/modules/speex_aec/speex_aec.c
@@ -0,0 +1,229 @@
+/**
+ * @file speex_aec.c Speex Acoustic Echo Cancellation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <speex/speex.h>
+#include <speex/speex_echo.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup speex_aec speex_aec
+ *
+ * Acoustic Echo Cancellation (AEC) from libspeexdsp
+ */
+
+
+struct speex_st {
+ int16_t *out;
+ SpeexEchoState *state;
+};
+
+struct enc_st {
+ struct aufilt_enc_st af; /* base class */
+ struct speex_st *st;
+};
+
+struct dec_st {
+ struct aufilt_dec_st af; /* base class */
+ struct speex_st *st;
+};
+
+
+static void enc_destructor(void *arg)
+{
+ struct enc_st *st = arg;
+
+ list_unlink(&st->af.le);
+ mem_deref(st->st);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct dec_st *st = arg;
+
+ list_unlink(&st->af.le);
+ mem_deref(st->st);
+}
+
+
+#ifdef SPEEX_SET_VBR_MAX_BITRATE
+static void speex_aec_destructor(void *arg)
+{
+ struct speex_st *st = arg;
+
+ if (st->state)
+ speex_echo_state_destroy(st->state);
+
+ mem_deref(st->out);
+}
+
+
+static int aec_alloc(struct speex_st **stp, void **ctx, struct aufilt_prm *prm)
+{
+ struct speex_st *st;
+ uint32_t sampc;
+ int err, tmp, fl;
+
+ if (!stp || !ctx || !prm)
+ return EINVAL;
+
+ if (*ctx) {
+ *stp = mem_ref(*ctx);
+ return 0;
+ }
+
+ st = mem_zalloc(sizeof(*st), speex_aec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->out = mem_alloc(2 * sampc, NULL);
+ if (!st->out) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ /* Echo canceller with 200 ms tail length */
+ fl = 10 * sampc;
+ st->state = speex_echo_state_init(sampc, fl);
+ if (!st->state) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ tmp = prm->srate;
+ err = speex_echo_ctl(st->state, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp);
+ if (err < 0) {
+ warning("speex_aec: speex_echo_ctl: err=%d\n", err);
+ }
+
+ info("speex_aec: Speex AEC loaded: srate = %uHz\n", prm->srate);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *ctx = *stp = st;
+
+ return err;
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct enc_st *st;
+ int err;
+
+ if (!stp || !ctx || !af || !prm)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = aec_alloc(&st->st, ctx, prm);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct dec_st *st;
+ int err;
+
+ if (!stp || !ctx || !af || !prm)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = aec_alloc(&st->st, ctx, prm);
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct aufilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct enc_st *est = (struct enc_st *)st;
+ struct speex_st *sp = est->st;
+
+ if (*sampc) {
+ speex_echo_capture(sp->state, sampv, sp->out);
+ memcpy(sampv, sp->out, *sampc * 2);
+ }
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct dec_st *dst = (struct dec_st *)st;
+ struct speex_st *sp = dst->st;
+
+ if (*sampc)
+ speex_echo_playback(sp->state, sampv);
+
+ return 0;
+}
+#endif
+
+
+static struct aufilt speex_aec = {
+ LE_INIT, "speex_aec", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ /* Note: Hack to check libspeex version */
+#ifdef SPEEX_SET_VBR_MAX_BITRATE
+ aufilt_register(baresip_aufiltl(), &speex_aec);
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&speex_aec);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_aec) = {
+ "speex_aec",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/speex_pp/module.mk b/modules/speex_pp/module.mk
new file mode 100644
index 0000000..fad5f88
--- /dev/null
+++ b/modules/speex_pp/module.mk
@@ -0,0 +1,15 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := speex_pp
+$(MOD)_SRCS += speex_pp.c
+ifneq ($(HAVE_SPEEXDSP),)
+$(MOD)_LFLAGS += "-lspeexdsp"
+else
+$(MOD)_LFLAGS += "-lspeex"
+endif
+
+include mk/mod.mk
diff --git a/modules/speex_pp/speex_pp.c b/modules/speex_pp/speex_pp.c
new file mode 100644
index 0000000..d82e575
--- /dev/null
+++ b/modules/speex_pp/speex_pp.c
@@ -0,0 +1,161 @@
+/**
+ * @file speex_pp.c Speex Pre-processor
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <speex/speex.h>
+#include <speex/speex_preprocess.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup speex_pp speex_pp
+ *
+ * Audio pre-processor from libspeexdsp
+ */
+
+
+struct preproc {
+ struct aufilt_enc_st af; /* base class */
+ SpeexPreprocessState *state;
+};
+
+
+/** Speex configuration */
+static struct {
+ int denoise_enabled;
+ int agc_enabled;
+ int vad_enabled;
+ int dereverb_enabled;
+ spx_int32_t agc_level;
+} pp_conf = {
+ 1,
+ 1,
+ 1,
+ 1,
+ 8000
+};
+
+
+static void speexpp_destructor(void *arg)
+{
+ struct preproc *st = arg;
+
+ if (st->state)
+ speex_preprocess_state_destroy(st->state);
+
+ list_unlink(&st->af.le);
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct preproc *st;
+ unsigned sampc;
+ (void)ctx;
+
+ if (!stp || !af || !prm || prm->ch != 1)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), speexpp_destructor);
+ if (!st)
+ return ENOMEM;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->state = speex_preprocess_state_init(sampc, prm->srate);
+ if (!st->state)
+ goto error;
+
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DENOISE,
+ &pp_conf.denoise_enabled);
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_AGC,
+ &pp_conf.agc_enabled);
+
+#ifdef SPEEX_PREPROCESS_SET_AGC_TARGET
+ if (pp_conf.agc_enabled) {
+ speex_preprocess_ctl(st->state,
+ SPEEX_PREPROCESS_SET_AGC_TARGET,
+ &pp_conf.agc_level);
+ }
+#endif
+
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_VAD,
+ &pp_conf.vad_enabled);
+ speex_preprocess_ctl(st->state, SPEEX_PREPROCESS_SET_DEREVERB,
+ &pp_conf.dereverb_enabled);
+
+ info("speex_pp: Speex preprocessor loaded: srate = %uHz\n",
+ prm->srate);
+
+ *stp = (struct aufilt_enc_st *)st;
+ return 0;
+
+ error:
+ mem_deref(st);
+ return ENOMEM;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct preproc *pp = (struct preproc *)st;
+ int is_speech = 1;
+
+ if (!*sampc)
+ return 0;
+
+ /* NOTE: Using this macro to check libspeex version */
+#ifdef SPEEX_PREPROCESS_SET_NOISE_SUPPRESS
+ /* New API */
+ is_speech = speex_preprocess_run(pp->state, sampv);
+#else
+ /* Old API - not tested! */
+ is_speech = speex_preprocess(pp->state, sampv, NULL);
+#endif
+
+ /* XXX: Handle is_speech and VAD */
+ (void)is_speech;
+
+ return 0;
+}
+
+
+static void config_parse(struct conf *conf)
+{
+ uint32_t v;
+
+ if (0 == conf_get_u32(conf, "speex_agc_level", &v))
+ pp_conf.agc_level = v;
+}
+
+
+static struct aufilt preproc = {
+ LE_INIT, "speex_pp", encode_update, encode, NULL, NULL
+};
+
+static int module_init(void)
+{
+ config_parse(conf_cur());
+ aufilt_register(baresip_aufiltl(), &preproc);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&preproc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(speex_pp) = {
+ "speex_pp",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/srtp/module.mk b/modules/srtp/module.mk
new file mode 100644
index 0000000..285f4ed
--- /dev/null
+++ b/modules/srtp/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := srtp
+$(MOD)_SRCS += srtp.c sdes.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/srtp/sdes.c b/modules/srtp/sdes.c
new file mode 100644
index 0000000..49b32aa
--- /dev/null
+++ b/modules/srtp/sdes.c
@@ -0,0 +1,45 @@
+/**
+ * @file /srtp/sdes.c SDP Security Descriptions for Media Streams (RFC 4568)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+const char sdp_attr_crypto[] = "crypto";
+
+
+int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite,
+ const char *key, size_t key_len)
+{
+ return sdp_media_set_lattr(m, true, sdp_attr_crypto, "%u %s inline:%b",
+ tag, suite, key, key_len);
+}
+
+
+/* http://tools.ietf.org/html/rfc4568
+ * a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+ */
+int sdes_decode_crypto(struct crypto *c, const char *val)
+{
+ struct pl tag, key_prms;
+ int err;
+
+ err = re_regex(val, str_len(val), "[0-9]+ [^ ]+ [^ ]+[]*[^]*",
+ &tag, &c->suite, &key_prms, NULL, &c->sess_prms);
+ if (err)
+ return err;
+
+ c->tag = pl_u32(&tag);
+
+ c->lifetime = c->mki = pl_null;
+ err = re_regex(key_prms.p, key_prms.l, "[^:]+:[^|]+[|]*[^|]*[|]*[^|]*",
+ &c->key_method, &c->key_info,
+ NULL, &c->lifetime, NULL, &c->mki);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/modules/srtp/sdes.h b/modules/srtp/sdes.h
new file mode 100644
index 0000000..a5637bc
--- /dev/null
+++ b/modules/srtp/sdes.h
@@ -0,0 +1,22 @@
+/**
+ * @file /srtp/sdes.h SDP Security Descriptions for Media Streams API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct crypto {
+ uint32_t tag;
+ struct pl suite;
+ struct pl key_method;
+ struct pl key_info;
+ struct pl lifetime; /* optional */
+ struct pl mki; /* optional */
+ struct pl sess_prms; /* optional */
+};
+
+extern const char sdp_attr_crypto[];
+
+int sdes_encode_crypto(struct sdp_media *m, uint32_t tag, const char *suite,
+ const char *key, size_t key_len);
+int sdes_decode_crypto(struct crypto *c, const char *val);
diff --git a/modules/srtp/srtp.c b/modules/srtp/srtp.c
new file mode 100644
index 0000000..217e5bf
--- /dev/null
+++ b/modules/srtp/srtp.c
@@ -0,0 +1,420 @@
+/**
+ * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "sdes.h"
+
+
+/**
+ * @defgroup srtp srtp
+ *
+ * Secure Real-time Transport Protocol module
+ *
+ * This module implements media encryption using SRTP and SDES.
+ *
+ * SRTP can be enabled in ~/.baresip/accounts:
+ *
+ \verbatim
+ <sip:user@domain.com>;mediaenc=srtp
+ <sip:user@domain.com>;mediaenc=srtp-mand
+ \endverbatim
+ *
+ */
+
+
+#define SRTP_MASTER_KEY_LEN 30
+
+
+struct menc_st {
+ /* one SRTP session per media line */
+ uint8_t key_tx[32];
+ uint8_t key_rx[32];
+ struct srtp *srtp_tx, *srtp_rx;
+ bool use_srtp;
+ bool got_sdp;
+ char *crypto_suite;
+
+ void *rtpsock;
+ void *rtcpsock;
+ struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */
+ struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */
+ struct sdp_media *sdpm;
+};
+
+
+static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80";
+
+static const char *preferred_suite = aes_cm_128_hmac_sha1_80;
+
+
+static void destructor(void *arg)
+{
+ struct menc_st *st = arg;
+
+ mem_deref(st->sdpm);
+ mem_deref(st->crypto_suite);
+
+ /* note: must be done before freeing socket */
+ mem_deref(st->uh_rtp);
+ mem_deref(st->uh_rtcp);
+ mem_deref(st->rtpsock);
+ mem_deref(st->rtcpsock);
+
+ mem_deref(st->srtp_tx);
+ mem_deref(st->srtp_rx);
+}
+
+
+static bool cryptosuite_issupported(const struct pl *suite)
+{
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true;
+ if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true;
+
+ return false;
+}
+
+
+/*
+ * See RFC 5764 figure 3:
+ *
+ * +----------------+
+ * | 127 < B < 192 -+--> forward to RTP
+ * | |
+ * packet --> | 19 < B < 64 -+--> forward to DTLS
+ * | |
+ * | B < 2 -+--> forward to STUN
+ * +----------------+
+ *
+ */
+static bool is_rtp_or_rtcp(const struct mbuf *mb)
+{
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mbuf_buf(mb)[0];
+
+ return 127 < b && b < 192;
+}
+
+
+static bool is_rtcp_packet(const struct mbuf *mb)
+{
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return false;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ return 64 <= pt && pt <= 95;
+}
+
+
+static enum srtp_suite resolve_suite(const char *suite)
+{
+ if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32))
+ return SRTP_AES_CM_128_HMAC_SHA1_32;
+ if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80))
+ return SRTP_AES_CM_128_HMAC_SHA1_80;
+
+ return -1;
+}
+
+
+static int start_srtp(struct menc_st *st, const char *suite_name)
+{
+ enum srtp_suite suite;
+ int err;
+
+ suite = resolve_suite(suite_name);
+
+ /* allocate and initialize the SRTP session */
+ if (!st->srtp_tx) {
+ err = srtp_alloc(&st->srtp_tx, suite, st->key_tx, 30, 0);
+ if (err) {
+ warning("srtp: srtp_alloc TX failed (%m)\n", err);
+ return err;
+ }
+ }
+
+ if (!st->srtp_rx) {
+ err = srtp_alloc(&st->srtp_rx, suite, st->key_rx, 30, 0);
+ if (err) {
+ warning("srtp: srtp_alloc RX failed (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* use SRTP for this stream/session */
+ st->use_srtp = true;
+
+ return 0;
+}
+
+
+static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ size_t len = mbuf_get_left(mb);
+ int lerr = 0;
+ (void)dst;
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ if (is_rtcp_packet(mb)) {
+ lerr = srtcp_encrypt(st->srtp_tx, mb);
+ }
+ else {
+ lerr = srtp_encrypt(st->srtp_tx, mb);
+ }
+
+ if (lerr) {
+ warning("srtp: failed to encrypt %s-packet"
+ " with %zu bytes (%m)\n",
+ is_rtcp_packet(mb) ? "RTCP" : "RTP",
+ len, lerr);
+ *err = lerr;
+ return false;
+ }
+
+ return false; /* continue processing */
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct menc_st *st = arg;
+ size_t len = mbuf_get_left(mb);
+ int err = 0;
+ (void)src;
+
+ if (!st->got_sdp)
+ return true; /* drop the packet */
+
+ if (!st->use_srtp || !is_rtp_or_rtcp(mb))
+ return false;
+
+ if (is_rtcp_packet(mb)) {
+ err = srtcp_decrypt(st->srtp_rx, mb);
+ if (err) {
+ warning("srtp: failed to decrypt RTCP packet"
+ " with %zu bytes (%m)\n", len, err);
+ }
+ }
+ else {
+ err = srtp_decrypt(st->srtp_rx, mb);
+ if (err) {
+ warning("srtp: failed to decrypt RTP packet"
+ " with %zu bytes (%m)\n", len, err);
+ }
+ }
+
+ return err ? true : false;
+}
+
+
+/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */
+static int sdp_enc(struct menc_st *st, struct sdp_media *m,
+ uint32_t tag, const char *suite)
+{
+ char key[128] = "";
+ size_t olen;
+ int err;
+
+ olen = sizeof(key);
+ err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen);
+ if (err)
+ return err;
+
+ return sdes_encode_crypto(m, tag, suite, key, olen);
+}
+
+
+static int start_crypto(struct menc_st *st, const struct pl *key_info)
+{
+ size_t olen;
+ int err;
+
+ /* key-info is BASE64 encoded */
+
+ olen = sizeof(st->key_rx);
+ err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen);
+ if (err)
+ return err;
+
+ if (SRTP_MASTER_KEY_LEN != olen) {
+ warning("srtp: srtp keylen is %u (should be 30)\n", olen);
+ }
+
+ err = start_srtp(st, st->crypto_suite);
+ if (err)
+ return err;
+
+ info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n",
+ sdp_media_name(st->sdpm), st->crypto_suite);
+
+ return 0;
+}
+
+
+static bool sdp_attr_handler(const char *name, const char *value, void *arg)
+{
+ struct menc_st *st = arg;
+ struct crypto c;
+ (void)name;
+
+ if (sdes_decode_crypto(&c, value))
+ return false;
+
+ if (0 != pl_strcmp(&c.key_method, "inline"))
+ return false;
+
+ if (!cryptosuite_issupported(&c.suite))
+ return false;
+
+ st->crypto_suite = mem_deref(st->crypto_suite);
+ pl_strdup(&st->crypto_suite, &c.suite);
+
+ if (start_crypto(st, &c.key_info))
+ return false;
+
+ sdp_enc(st, st->sdpm, c.tag, st->crypto_suite);
+
+ return true;
+}
+
+
+static int alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_st *st;
+ const char *rattr = NULL;
+ int layer = 10; /* above zero */
+ int err = 0;
+ bool mux = (rtpsock == rtcpsock);
+ (void)sess;
+ (void)rtp;
+
+ if (!stp || !sdpm)
+ return EINVAL;
+ if (proto != IPPROTO_UDP)
+ return EPROTONOSUPPORT;
+
+ st = (struct menc_st *)*stp;
+ if (!st) {
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sdpm = mem_ref(sdpm);
+
+ err = sdp_media_set_alt_protos(st->sdpm, 4,
+ "RTP/AVP",
+ "RTP/AVPF",
+ "RTP/SAVP",
+ "RTP/SAVPF");
+ if (err)
+ goto out;
+
+ if (rtpsock) {
+ st->rtpsock = mem_ref(rtpsock);
+ err |= udp_register_helper(&st->uh_rtp, rtpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (rtcpsock && !mux) {
+ st->rtcpsock = mem_ref(rtcpsock);
+ err |= udp_register_helper(&st->uh_rtcp, rtcpsock,
+ layer, send_handler,
+ recv_handler, st);
+ }
+ if (err)
+ goto out;
+
+ /* set our preferred crypto-suite */
+ err |= str_dup(&st->crypto_suite, preferred_suite);
+ if (err)
+ goto out;
+
+ rand_bytes(st->key_tx, SRTP_MASTER_KEY_LEN);
+ }
+
+ /* SDP handling */
+
+ if (sdp_media_rport(sdpm))
+ st->got_sdp = true;
+
+ if (sdp_media_rattr(st->sdpm, "crypto")) {
+
+ rattr = sdp_media_rattr_apply(st->sdpm, "crypto",
+ sdp_attr_handler, st);
+ if (!rattr) {
+ warning("srtp: no valid a=crypto attribute from"
+ " remote peer\n");
+ }
+ }
+
+ if (!rattr)
+ err = sdp_enc(st, sdpm, 1, st->crypto_suite);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct menc_media *)st;
+
+ return err;
+}
+
+
+static struct menc menc_srtp_opt = {
+ LE_INIT, "srtp", "RTP/AVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mand = {
+ LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc
+};
+
+static struct menc menc_srtp_mandf = {
+ LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc
+};
+
+
+static int mod_srtp_init(void)
+{
+ struct list *mencl = baresip_mencl();
+
+ menc_register(mencl, &menc_srtp_opt);
+ menc_register(mencl, &menc_srtp_mand);
+ menc_register(mencl, &menc_srtp_mandf);
+
+ return 0;
+}
+
+
+static int mod_srtp_close(void)
+{
+ menc_unregister(&menc_srtp_mandf);
+ menc_unregister(&menc_srtp_mand);
+ menc_unregister(&menc_srtp_opt);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(srtp) = {
+ "srtp",
+ "menc",
+ mod_srtp_init,
+ mod_srtp_close
+};
diff --git a/modules/stdio/module.mk b/modules/stdio/module.mk
new file mode 100644
index 0000000..4c52b28
--- /dev/null
+++ b/modules/stdio/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := stdio
+$(MOD)_SRCS += stdio.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/stdio/stdio.c b/modules/stdio/stdio.c
new file mode 100644
index 0000000..179484e
--- /dev/null
+++ b/modules/stdio/stdio.c
@@ -0,0 +1,193 @@
+/**
+ * @file stdio.c Standard Input/Output UI module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup stdio stdio
+ *
+ * User-Interface (UI) module for standard input/output
+ *
+ * This module sets up the terminal in raw mode, and reads characters from the
+ * input to the UI subsystem. The module is indented for Unix-based systems.
+ */
+
+
+/** Local constants */
+enum {
+ RELEASE_VAL = 250 /**< Key release value in [ms] */
+};
+
+struct ui_st {
+ struct tmr tmr;
+ struct termios term;
+ bool term_set;
+};
+
+
+/* We only allow one instance */
+static struct ui_st *ui_state;
+
+
+static void ui_destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ fd_close(STDIN_FILENO);
+
+ if (st->term_set)
+ tcsetattr(STDIN_FILENO, TCSANOW, &st->term);
+
+ tmr_cancel(&st->tmr);
+}
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM;
+}
+
+
+static void report_key(struct ui_st *ui, char key)
+{
+ static struct re_printf pf_stderr = {print_handler, NULL};
+ (void)ui;
+
+ ui_input_key(baresip_uis(), key, &pf_stderr);
+}
+
+
+static void timeout(void *arg)
+{
+ struct ui_st *st = arg;
+
+ /* Emulate key-release */
+ report_key(st, KEYCODE_REL);
+}
+
+
+static void ui_fd_handler(int flags, void *arg)
+{
+ struct ui_st *st = arg;
+ char key;
+ (void)flags;
+
+ if (1 != read(STDIN_FILENO, &key, 1)) {
+ return;
+ }
+
+ tmr_start(&st->tmr, RELEASE_VAL, timeout, st);
+ report_key(st, key);
+}
+
+
+static int term_setup(struct ui_st *st)
+{
+ struct termios now;
+
+ if (tcgetattr(STDIN_FILENO, &st->term) < 0)
+ return errno;
+
+ now = st->term;
+
+ now.c_lflag |= ISIG;
+ now.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
+
+ /* required on Solaris */
+ now.c_cc[VMIN] = 1;
+ now.c_cc[VTIME] = 0;
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &now) < 0)
+ return errno;
+
+ st->term_set = true;
+
+ return 0;
+}
+
+
+static int ui_alloc(struct ui_st **stp)
+{
+ struct ui_st *st;
+ int err;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ui_destructor);
+ if (!st)
+ return ENOMEM;
+
+ tmr_init(&st->tmr);
+
+ err = fd_listen(STDIN_FILENO, FD_READ, ui_fd_handler, st);
+ if (err)
+ goto out;
+
+ err = term_setup(st);
+ if (err) {
+ info("stdio: could not setup terminal: %m\n", err);
+ err = 0;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int output_handler(const char *str)
+{
+ return print_handler(str, str_len(str), NULL);
+}
+
+
+static struct ui ui_stdio = {
+ .name = "stdio",
+ .outputh = output_handler
+};
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = ui_alloc(&ui_state);
+ if (err)
+ return err;
+
+ ui_register(baresip_uis(), &ui_stdio);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ ui_unregister(&ui_stdio);
+ ui_state = mem_deref(ui_state);
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(stdio) = {
+ "stdio",
+ "ui",
+ module_init,
+ module_close
+};
diff --git a/modules/stun/module.mk b/modules/stun/module.mk
new file mode 100644
index 0000000..6ae88b5
--- /dev/null
+++ b/modules/stun/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := stun
+$(MOD)_SRCS += stun.c
+
+include mk/mod.mk
diff --git a/modules/stun/stun.c b/modules/stun/stun.c
new file mode 100644
index 0000000..48dffe7
--- /dev/null
+++ b/modules/stun/stun.c
@@ -0,0 +1,252 @@
+/**
+ * @file stun.c STUN Module for Media NAT-traversal
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup stun stun
+ *
+ * Session Traversal Utilities for NAT (STUN) for media NAT traversal
+ */
+
+
+enum {LAYER = 0, INTERVAL = 30};
+
+struct mnat_sess {
+ struct list medial;
+ struct sa srv;
+ struct stun_dns *dnsq;
+ mnat_estab_h *estabh;
+ void *arg;
+ int mediac;
+};
+
+
+struct mnat_media {
+ struct le le;
+ struct sa addr1;
+ struct sa addr2;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct stun_keepalive *ska1;
+ struct stun_keepalive *ska2;
+ void *sock1;
+ void *sock2;
+ int proto;
+};
+
+
+static struct mnat *mnat;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+ mem_deref(sess->dnsq);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ list_unlink(&m->le);
+ mem_deref(m->sdpm);
+ mem_deref(m->ska1);
+ mem_deref(m->ska2);
+ mem_deref(m->sock1);
+ mem_deref(m->sock2);
+}
+
+
+static void mapped_handler1(int err, const struct sa *map_addr, void *arg)
+{
+ struct mnat_media *m = arg;
+
+ if (!err) {
+
+ sdp_media_set_laddr(m->sdpm, map_addr);
+
+ m->addr1 = *map_addr;
+
+ if (m->ska2 && !sa_isset(&m->addr2, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, 0, NULL, m->sess->arg);
+}
+
+
+static void mapped_handler2(int err, const struct sa *map_addr, void *arg)
+{
+ struct mnat_media *m = arg;
+
+ if (!err) {
+
+ sdp_media_set_laddr_rtcp(m->sdpm, map_addr);
+
+ m->addr2 = *map_addr;
+
+ if (m->ska1 && !sa_isset(&m->addr1, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, 0, NULL, m->sess->arg);
+}
+
+
+static int media_start(struct mnat_sess *sess, struct mnat_media *m)
+{
+ int err = 0;
+
+ if (m->sock1) {
+ err |= stun_keepalive_alloc(&m->ska1, m->proto,
+ m->sock1, LAYER, &sess->srv, NULL,
+ mapped_handler1, m);
+ }
+ if (m->sock2) {
+ err |= stun_keepalive_alloc(&m->ska2, m->proto,
+ m->sock2, LAYER, &sess->srv, NULL,
+ mapped_handler2, m);
+ }
+ if (err)
+ return err;
+
+ stun_keepalive_enable(m->ska1, INTERVAL);
+ stun_keepalive_enable(m->ska2, INTERVAL);
+
+ return 0;
+}
+
+
+static void dns_handler(int err, const struct sa *srv, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ if (err)
+ goto out;
+
+ sess->srv = *srv;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+
+ err = media_start(sess, m);
+ if (err)
+ goto out;
+ }
+
+ return;
+
+ out:
+ sess->estabh(err, 0, NULL, sess->arg);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err;
+ (void)user;
+ (void)pass;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ err = stun_server_discover(&sess->dnsq, dnsc,
+ stun_usage_binding, stun_proto_udp,
+ af, srv, port, dns_handler, sess);
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sdpm = mem_ref(sdpm);
+ m->sess = sess;
+ m->sock1 = mem_ref(sock1);
+ m->sock2 = mem_ref(sock2);
+ m->proto = proto;
+
+ if (sa_isset(&sess->srv, SA_ALL))
+ err = media_start(sess, m);
+
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ ++sess->mediac;
+ }
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return mnat_register(&mnat, baresip_mnatl(),
+ "stun", NULL, session_alloc, media_alloc,
+ NULL);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(stun) = {
+ "stun",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/swscale/module.mk b/modules/swscale/module.mk
new file mode 100644
index 0000000..73b49e0
--- /dev/null
+++ b/modules/swscale/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := swscale
+$(MOD)_SRCS += swscale.c
+$(MOD)_LFLAGS += -lswscale
+
+include mk/mod.mk
diff --git a/modules/swscale/swscale.c b/modules/swscale/swscale.c
new file mode 100644
index 0000000..5cd8905
--- /dev/null
+++ b/modules/swscale/swscale.c
@@ -0,0 +1,197 @@
+/**
+ * @file swscale.c Video filter for scaling and pixel conversion
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <libswscale/swscale.h>
+
+
+struct swscale_enc {
+ struct vidfilt_enc_st vf; /**< Inheritance */
+
+ struct SwsContext *sws;
+ struct vidframe *frame;
+ struct vidsz dst_size;
+};
+
+
+static enum vidfmt swscale_format = VID_FMT_YUV420P; /* XXX: configurable */
+
+
+static enum AVPixelFormat vidfmt_to_avpixfmt(enum vidfmt fmt)
+{
+ switch (fmt) {
+
+ case VID_FMT_YUV420P: return AV_PIX_FMT_YUV420P;
+ case VID_FMT_NV12: return AV_PIX_FMT_NV12;
+ case VID_FMT_NV21: return AV_PIX_FMT_NV21;
+ default: return AV_PIX_FMT_NONE;
+ }
+}
+
+
+static void encode_destructor(void *arg)
+{
+ struct swscale_enc *st = arg;
+
+ list_unlink(&st->vf.le);
+
+ mem_deref(st->frame);
+ sws_freeContext(st->sws);
+}
+
+
+static int encode_update(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct swscale_enc *st;
+ struct config *config = conf_config();
+ int err = 0;
+
+ if (!config) {
+ warning("swscale: no config\n");
+ return EINVAL;
+ }
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->dst_size.w = config->video.width;
+ st->dst_size.h = config->video.height;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int encode_process(struct vidfilt_enc_st *st, struct vidframe *frame)
+{
+ struct swscale_enc *enc = (struct swscale_enc *)st;
+ enum AVPixelFormat avpixfmt, avpixfmt_dst;
+ const uint8_t *srcSlice[4];
+ uint8_t *dst[4];
+ int srcStride[4], dstStride[4];
+ int width, height, i, h;
+ int err = 0;
+
+ if (!st)
+ return EINVAL;
+
+ if (!frame)
+ return 0;
+
+ width = frame->size.w;
+ height = frame->size.h;
+
+ avpixfmt = vidfmt_to_avpixfmt(frame->fmt);
+ if (avpixfmt == AV_PIX_FMT_NONE) {
+ warning("swscale: unknown pixel-format (%s)\n",
+ vidfmt_name(frame->fmt));
+ return EINVAL;
+ }
+
+ avpixfmt_dst = vidfmt_to_avpixfmt(swscale_format);
+ if (avpixfmt_dst == AV_PIX_FMT_NONE) {
+ warning("swscale: unknown pixel-format (%s)\n",
+ vidfmt_name(swscale_format));
+ return EINVAL;
+ }
+
+ if (!enc->sws) {
+
+ struct SwsContext *sws;
+ int flags = 0;
+
+ sws = sws_getContext(width, height, avpixfmt,
+ enc->dst_size.w, enc->dst_size.h,
+ avpixfmt_dst,
+ flags, NULL, NULL, NULL);
+ if (!sws) {
+ warning("swscale: sws_getContext error\n");
+ return ENOMEM;
+ }
+
+ enc->sws = sws;
+
+ info("swscale: created SwsContext:"
+ " `%s' %d x %d --> `%s' %u x %u\n",
+ vidfmt_name(frame->fmt), width, height,
+ vidfmt_name(swscale_format),
+ enc->dst_size.w, enc->dst_size.h);
+ }
+
+ if (!enc->frame) {
+
+ err = vidframe_alloc(&enc->frame, swscale_format,
+ &enc->dst_size);
+ if (err) {
+ warning("swscale: vidframe_alloc error (%m)\n", err);
+ return err;
+ }
+ }
+
+ for (i=0; i<4; i++) {
+ srcSlice[i] = frame->data[i];
+ srcStride[i] = frame->linesize[i];
+ dst[i] = enc->frame->data[i];
+ dstStride[i] = enc->frame->linesize[i];
+ }
+
+ h = sws_scale(enc->sws, srcSlice, srcStride,
+ 0, height, dst, dstStride);
+ if (h <= 0) {
+ warning("swscale: sws_scale error (%d)\n", h);
+ return EPROTO;
+ }
+
+ /* Copy the converted frame back to the input frame */
+ for (i=0; i<4; i++) {
+ frame->data[i] = enc->frame->data[i];
+ frame->linesize[i] = enc->frame->linesize[i];
+ }
+ frame->size = enc->frame->size;
+ frame->fmt = enc->frame->fmt;
+
+ return 0;
+}
+
+
+static struct vidfilt vf_swscale = {
+ LE_INIT, "swscale", encode_update, encode_process, NULL, NULL
+};
+
+
+static int module_init(void)
+{
+ vidfilt_register(baresip_vidfiltl(), &vf_swscale);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&vf_swscale);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(swscale) = {
+ "swscale",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/syslog/module.mk b/modules/syslog/module.mk
new file mode 100644
index 0000000..94fd185
--- /dev/null
+++ b/modules/syslog/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := syslog
+$(MOD)_SRCS += syslog.c
+
+include mk/mod.mk
diff --git a/modules/syslog/syslog.c b/modules/syslog/syslog.c
new file mode 100644
index 0000000..7c9b054
--- /dev/null
+++ b/modules/syslog/syslog.c
@@ -0,0 +1,76 @@
+/**
+ * @file syslog.c Syslog module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <syslog.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup syslog syslog
+ *
+ * This module implements a logging handler for output to syslog
+ */
+
+
+#define DEBUG_MODULE ""
+#define DEBUG_LEVEL 0
+#include <re_dbg.h>
+
+
+static const int lmap[] = { LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR };
+
+
+static void log_handler(uint32_t level, const char *msg)
+{
+ syslog(lmap[MIN(level, ARRAY_SIZE(lmap)-1)], "%s", msg);
+}
+
+
+static struct log lg = {
+ .h = log_handler,
+};
+
+
+static void syslog_handler(int level, const char *p, size_t len, void *arg)
+{
+ (void)arg;
+
+ syslog(level, "%.*s", (int)len, p);
+}
+
+
+static int module_init(void)
+{
+ openlog("baresip", LOG_NDELAY | LOG_PID, LOG_LOCAL0);
+
+ dbg_init(DBG_INFO, DBG_NONE);
+ dbg_handler_set(syslog_handler, NULL);
+
+ log_register_handler(&lg);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ log_unregister_handler(&lg);
+
+ dbg_handler_set(NULL, NULL);
+
+ closelog();
+
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(syslog) = {
+ "syslog",
+ "application",
+ module_init,
+ module_close
+};
diff --git a/modules/turn/module.mk b/modules/turn/module.mk
new file mode 100644
index 0000000..876308d
--- /dev/null
+++ b/modules/turn/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := turn
+$(MOD)_SRCS += turn.c
+
+include mk/mod.mk
diff --git a/modules/turn/turn.c b/modules/turn/turn.c
new file mode 100644
index 0000000..85fe84b
--- /dev/null
+++ b/modules/turn/turn.c
@@ -0,0 +1,301 @@
+/**
+ * @file turn.c TURN Module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup turn turn
+ *
+ * Traversal Using Relays around NAT (TURN) for media NAT traversal
+ *
+ * XXX: use turn RSV_TOKEN for RTP/RTCP even/odd pair ?
+ */
+
+
+enum {LAYER = 0};
+
+
+struct mnat_sess {
+ struct list medial;
+ struct sa srv;
+ struct stun_dns *dnsq;
+ char *user;
+ char *pass;
+ mnat_estab_h *estabh;
+ void *arg;
+ int mediac;
+};
+
+
+struct mnat_media {
+ struct le le;
+ struct sa addr1;
+ struct sa addr2;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct turnc *turnc1;
+ struct turnc *turnc2;
+ void *sock1;
+ void *sock2;
+ int proto;
+};
+
+
+static struct mnat *mnat;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+ mem_deref(sess->dnsq);
+ mem_deref(sess->user);
+ mem_deref(sess->pass);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ list_unlink(&m->le);
+ mem_deref(m->sdpm);
+ mem_deref(m->turnc1);
+ mem_deref(m->turnc2);
+ mem_deref(m->sock1);
+ mem_deref(m->sock2);
+}
+
+
+static void turn_handler1(int err, uint16_t scode, const char *reason,
+ const struct sa *relay_addr,
+ const struct sa *mapped_addr,
+ const struct stun_msg *msg,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ (void)mapped_addr;
+ (void)msg;
+
+ if (!err && !scode) {
+
+ sdp_media_set_laddr(m->sdpm, relay_addr);
+
+ m->addr1 = *relay_addr;
+
+ if (m->turnc2 && !sa_isset(&m->addr2, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, scode, reason, m->sess->arg);
+}
+
+
+static void turn_handler2(int err, uint16_t scode, const char *reason,
+ const struct sa *relay_addr,
+ const struct sa *mapped_addr,
+ const struct stun_msg *msg,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ (void)mapped_addr;
+ (void)msg;
+
+ if (!err && !scode) {
+
+ sdp_media_set_laddr_rtcp(m->sdpm, relay_addr);
+
+ m->addr2 = *relay_addr;
+
+ if (m->turnc1 && !sa_isset(&m->addr1, SA_ALL))
+ return;
+
+ if (--m->sess->mediac)
+ return;
+ }
+
+ m->sess->estabh(err, scode, reason, m->sess->arg);
+}
+
+
+static int media_start(struct mnat_sess *sess, struct mnat_media *m)
+{
+ int err = 0;
+
+ if (m->sock1) {
+ err |= turnc_alloc(&m->turnc1, NULL,
+ m->proto, m->sock1, LAYER,
+ &sess->srv, sess->user, sess->pass,
+ TURN_DEFAULT_LIFETIME,
+ turn_handler1, m);
+ }
+ if (m->sock2) {
+ err |= turnc_alloc(&m->turnc2, NULL,
+ m->proto, m->sock2, LAYER,
+ &sess->srv, sess->user, sess->pass,
+ TURN_DEFAULT_LIFETIME,
+ turn_handler2, m);
+ }
+
+ return err;
+}
+
+
+static void dns_handler(int err, const struct sa *srv, void *arg)
+{
+ struct mnat_sess *sess = arg;
+ struct le *le;
+
+ if (err)
+ goto out;
+
+ sess->srv = *srv;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+
+ err = media_start(sess, m);
+ if (err)
+ goto out;
+ }
+
+ return;
+
+ out:
+ sess->estabh(err, 0, NULL, sess->arg);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !user || !pass || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ err = str_dup(&sess->user, user);
+ err |= str_dup(&sess->pass, pass);
+ if (err)
+ goto out;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ err = stun_server_discover(&sess->dnsq, dnsc,
+ stun_usage_relay, stun_proto_udp,
+ af, srv, port, dns_handler, sess);
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sdpm = mem_ref(sdpm);
+ m->sess = sess;
+ m->sock1 = mem_ref(sock1);
+ m->sock2 = mem_ref(sock2);
+ m->proto = proto;
+
+ if (sa_isset(&sess->srv, SA_ALL))
+ err = media_start(sess, m);
+
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ ++sess->mediac;
+ }
+
+ return err;
+}
+
+
+static int update(struct mnat_sess *sess)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!sess)
+ return EINVAL;
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct mnat_media *m = le->data;
+ struct sa raddr1, raddr2;
+
+ raddr1 = *sdp_media_raddr(m->sdpm);
+ sdp_media_raddr_rtcp(m->sdpm, &raddr2);
+
+ if (m->turnc1 && sa_isset(&raddr1, SA_ALL))
+ err |= turnc_add_chan(m->turnc1, &raddr1, NULL, NULL);
+
+ if (m->turnc2 && sa_isset(&raddr2, SA_ALL))
+ err |= turnc_add_chan(m->turnc2, &raddr2, NULL, NULL);
+ }
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ return mnat_register(&mnat, baresip_mnatl(),
+ "turn", NULL, session_alloc, media_alloc,
+ update);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(turn) = {
+ "turn",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/uuid/module.mk b/modules/uuid/module.mk
new file mode 100644
index 0000000..d82836b
--- /dev/null
+++ b/modules/uuid/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := uuid
+$(MOD)_SRCS += uuid.c
+
+include mk/mod.mk
diff --git a/modules/uuid/uuid.c b/modules/uuid/uuid.c
new file mode 100644
index 0000000..ba1daa4
--- /dev/null
+++ b/modules/uuid/uuid.c
@@ -0,0 +1,117 @@
+/**
+ * @file modules/uuid/uuid.c Generate and load UUID
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup uuid uuid
+ *
+ * UUID generator and loader
+ */
+
+
+enum { UUID_LEN = 36 };
+
+
+static int generate_random_uuid(FILE *f)
+{
+ if (re_fprintf(f, "%08x-%04x-%04x-%04x-%08x%04x",
+ rand_u32(), rand_u16(), rand_u16(), rand_u16(),
+ rand_u32(), rand_u16()) != UUID_LEN)
+ return ENOMEM;
+
+ return 0;
+}
+
+
+static int uuid_init(const char *file)
+{
+ FILE *f = NULL;
+ int err = 0;
+
+ f = fopen(file, "r");
+ if (f) {
+ err = 0;
+ goto out;
+ }
+
+ f = fopen(file, "w");
+ if (!f) {
+ err = errno;
+ warning("uuid: fopen() %s (%m)\n", file, err);
+ goto out;
+ }
+
+ err = generate_random_uuid(f);
+ if (err) {
+ warning("uuid: generate random UUID failed (%m)\n", err);
+ goto out;
+ }
+
+ info("uuid: generated new UUID in %s\n", file);
+
+ out:
+ if (f)
+ fclose(f);
+
+ return err;
+}
+
+
+static int uuid_load(const char *file, char *uuid, size_t sz)
+{
+ FILE *f = NULL;
+ int err = 0;
+
+ f = fopen(file, "r");
+ if (!f)
+ return errno;
+
+ if (!fgets(uuid, (int)sz, f))
+ err = errno;
+
+ (void)fclose(f);
+
+ debug("uuid: loaded UUID %s from file %s\n", uuid, file);
+
+ return err;
+}
+
+
+static int module_init(void)
+{
+ struct config *cfg = conf_config();
+ char path[256];
+ int err = 0;
+
+ err = conf_path_get(path, sizeof(path));
+ if (err)
+ return err;
+
+ strncat(path, "/uuid", sizeof(path) - strlen(path) - 1);
+
+ err = uuid_init(path);
+ if (err)
+ return err;
+
+ err = uuid_load(path, cfg->sip.uuid, sizeof(cfg->sip.uuid));
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(uuid) = {
+ "uuid",
+ NULL,
+ module_init,
+ NULL
+};
diff --git a/modules/v4l/module.mk b/modules/v4l/module.mk
new file mode 100644
index 0000000..05ff278
--- /dev/null
+++ b/modules/v4l/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := v4l
+$(MOD)_SRCS += v4l.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/v4l/v4l.c b/modules/v4l/v4l.c
new file mode 100644
index 0000000..a171021
--- /dev/null
+++ b/modules/v4l/v4l.c
@@ -0,0 +1,270 @@
+/**
+ * @file v4l.c Video4Linux video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */
+#include <libv4l1-videodev.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup v4l v4l
+ *
+ * Video4Linux video-source module
+ */
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ int fd;
+ pthread_t thread;
+ bool run;
+ struct vidsz size;
+ struct mbuf *mb;
+ enum vidfmt fmt;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void v4l_get_caps(struct vidsrc_st *st)
+{
+ struct video_capability caps;
+
+ if (-1 == ioctl(st->fd, VIDIOCGCAP, &caps)) {
+ warning("v4l: VIDIOCGCAP: %m\n", errno);
+ return;
+ }
+
+ info("v4l: video: \"%s\" (%ux%u) - (%ux%u)\n", caps.name,
+ caps.minwidth, caps.minheight,
+ caps.maxwidth, caps.maxheight);
+
+ if (VID_TYPE_CAPTURE != caps.type) {
+ warning("v4l: not a capture device (%d)\n", caps.type);
+ }
+}
+
+
+static int v4l_check_palette(struct vidsrc_st *st)
+{
+ struct video_picture pic;
+
+ if (-1 == ioctl(st->fd, VIDIOCGPICT, &pic)) {
+ warning("v4l: VIDIOCGPICT: %m\n", errno);
+ return errno;
+ }
+
+ switch (pic.palette) {
+
+ case VIDEO_PALETTE_RGB24:
+ st->fmt = VID_FMT_RGB32;
+ break;
+
+ case VIDEO_PALETTE_YUYV:
+ st->fmt = VID_FMT_YUYV422;
+ break;
+
+ default:
+ warning("v4l: unsupported palette %d\n", pic.palette);
+ return ENODEV;
+ }
+
+ info("v4l: pixel format is %s\n", vidfmt_name(st->fmt));
+
+ return 0;
+}
+
+
+static int v4l_get_win(int fd, int width, int height)
+{
+ struct video_window win;
+
+ if (-1 == ioctl(fd, VIDIOCGWIN, &win)) {
+ warning("v4l: VIDIOCGWIN: %m\n", errno);
+ return errno;
+ }
+
+ info("v4l: video window: x,y=%u,%u (%u x %u)\n",
+ win.x, win.y, win.width, win.height);
+
+ win.width = width;
+ win.height = height;
+
+ if (-1 == ioctl(fd, VIDIOCSWIN, &win)) {
+ warning("v4l: VIDIOCSWIN: %m\n", errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, st->fmt, &st->size, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ while (st->run) {
+ ssize_t n;
+
+ n = read(st->fd, st->mb->buf, st->mb->size);
+ if ((ssize_t)st->mb->size != n) {
+ warning("v4l: video read: %d -> %d bytes\n",
+ st->mb->size, n);
+ continue;
+ }
+
+ call_frame_handler(st, st->mb->buf);
+ }
+
+ return NULL;
+}
+
+
+static int vd_open(struct vidsrc_st *v4l, const char *device)
+{
+ /* NOTE: with kernel 2.6.26 it takes ~2 seconds to open
+ * the video device.
+ */
+ v4l->fd = open(device, O_RDWR);
+ if (v4l->fd < 0) {
+ warning("v4l: open %s: %m\n", device, errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->fd >= 0)
+ close(st->fd);
+
+ mem_deref(st->mb);
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ if (!str_isset(dev))
+ dev = "/dev/video0";
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->fd = -1;
+ st->size = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ info("v4l: open: %s (%u x %u)\n", dev, size->w, size->h);
+
+ err = vd_open(st, dev);
+ if (err)
+ goto out;
+
+ v4l_get_caps(st);
+
+ err = v4l_check_palette(st);
+ if (err)
+ goto out;
+
+ err = v4l_get_win(st->fd, st->size.w, st->size.h);
+ if (err)
+ goto out;
+
+ /* allocate buffer for the picture */
+ st->mb = mbuf_alloc(vidframe_size(st->fmt, &st->size));
+ if (!st->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int v4l_init(void)
+{
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(), "v4l", alloc, NULL);
+}
+
+
+static int v4l_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l) = {
+ "v4l",
+ "vidsrc",
+ v4l_init,
+ v4l_close
+};
diff --git a/modules/v4l2/module.mk b/modules/v4l2/module.mk
new file mode 100644
index 0000000..8fefae4
--- /dev/null
+++ b/modules/v4l2/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := v4l2
+$(MOD)_SRCS += v4l2.c
+ifneq ($(HAVE_LIBV4L2),)
+$(MOD)_LFLAGS += -lv4l2
+$(MOD)_CFLAGS += -DHAVE_LIBV4L2
+endif
+
+include mk/mod.mk
diff --git a/modules/v4l2/v4l2.c b/modules/v4l2/v4l2.c
new file mode 100644
index 0000000..983ad53
--- /dev/null
+++ b/modules/v4l2/v4l2.c
@@ -0,0 +1,513 @@
+/**
+ * @file v4l2.c Video4Linux2 video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#undef __STRICT_ANSI__ /* needed for RHEL4 kernel 2.6.9 */
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#if defined (OPENBSD) || defined (NETBSD)
+#include <sys/videoio.h>
+#else
+#include <linux/videodev2.h>
+#endif
+
+#ifdef HAVE_LIBV4L2
+#include <libv4l2.h>
+#else
+#define v4l2_open open
+#define v4l2_read read
+#define v4l2_ioctl ioctl
+#define v4l2_mmap mmap
+#define v4l2_munmap munmap
+#define v4l2_close close
+#endif
+
+
+/**
+ * @defgroup v4l2 v4l2
+ *
+ * V4L2 (Video for Linux 2) video-source module
+ */
+
+
+struct buffer {
+ void *start;
+ size_t length;
+};
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ int fd;
+ pthread_t thread;
+ bool run;
+ struct vidsz sz;
+ u_int32_t pixfmt;
+ struct buffer *buffers;
+ unsigned int n_buffers;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static enum vidfmt match_fmt(u_int32_t fmt)
+{
+ switch (fmt) {
+
+ case V4L2_PIX_FMT_YUV420: return VID_FMT_YUV420P;
+ case V4L2_PIX_FMT_YUYV: return VID_FMT_YUYV422;
+ case V4L2_PIX_FMT_UYVY: return VID_FMT_UYVY422;
+ case V4L2_PIX_FMT_RGB32: return VID_FMT_RGB32;
+ case V4L2_PIX_FMT_RGB565: return VID_FMT_RGB565;
+ case V4L2_PIX_FMT_RGB555: return VID_FMT_RGB555;
+ case V4L2_PIX_FMT_NV12: return VID_FMT_NV12;
+ case V4L2_PIX_FMT_NV21: return VID_FMT_NV21;
+ default: return VID_FMT_N;
+ }
+}
+
+
+static void print_video_input(const struct vidsrc_st *st)
+{
+ struct v4l2_input input;
+
+ memset(&input, 0, sizeof(input));
+
+#ifndef OPENBSD
+ if (-1 == v4l2_ioctl(st->fd, VIDIOC_G_INPUT, &input.index)) {
+ warning("v4l2: VIDIOC_G_INPUT: %m\n", errno);
+ return;
+ }
+#endif
+
+ if (-1 == v4l2_ioctl(st->fd, VIDIOC_ENUMINPUT, &input)) {
+ warning("v4l2: VIDIOC_ENUMINPUT: %m\n", errno);
+ return;
+ }
+
+ info("v4l2: Current input: \"%s\"\n", input.name);
+}
+
+
+static int xioctl(int fd, unsigned long int request, void *arg)
+{
+ int r;
+
+ do {
+ r = v4l2_ioctl(fd, request, arg);
+ }
+ while (-1 == r && EINTR == errno);
+
+ return r;
+}
+
+
+static int init_mmap(struct vidsrc_st *st, const char *dev_name)
+{
+ struct v4l2_requestbuffers req;
+
+ memset(&req, 0, sizeof(req));
+
+ req.count = 4;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+
+ if (-1 == xioctl(st->fd, VIDIOC_REQBUFS, &req)) {
+ if (EINVAL == errno) {
+ warning("v4l2: %s does not support "
+ "memory mapping\n", dev_name);
+ return errno;
+ }
+ else {
+ return errno;
+ }
+ }
+
+ if (req.count < 2) {
+ warning("v4l2: Insufficient buffer memory on %s\n", dev_name);
+ return ENOMEM;
+ }
+
+ st->buffers = mem_zalloc(req.count * sizeof(*st->buffers), NULL);
+ if (!st->buffers)
+ return ENOMEM;
+
+ for (st->n_buffers = 0; st->n_buffers<req.count; ++st->n_buffers) {
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = st->n_buffers;
+
+ if (-1 == xioctl(st->fd, VIDIOC_QUERYBUF, &buf)) {
+ warning("v4l2: VIDIOC_QUERYBUF\n");
+ return errno;
+ }
+
+ st->buffers[st->n_buffers].length = buf.length;
+ st->buffers[st->n_buffers].start =
+ v4l2_mmap(NULL /* start anywhere */,
+ buf.length,
+ PROT_READ | PROT_WRITE /* required */,
+ MAP_SHARED /* recommended */,
+ st->fd, buf.m.offset);
+
+ if (MAP_FAILED == st->buffers[st->n_buffers].start) {
+ warning("v4l2: mmap failed\n");
+ return ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+
+static int v4l2_init_device(struct vidsrc_st *st, const char *dev_name,
+ int width, int height)
+{
+ struct v4l2_capability cap;
+ struct v4l2_format fmt;
+ struct v4l2_fmtdesc fmts;
+ unsigned int min;
+ const char *pix;
+ int err;
+
+ if (-1 == xioctl(st->fd, VIDIOC_QUERYCAP, &cap)) {
+ if (EINVAL == errno) {
+ warning("v4l2: %s is no V4L2 device\n", dev_name);
+ return ENODEV;
+ }
+ else {
+ warning("v4l2: VIDIOC_QUERYCAP: %m\n", errno);
+ return errno;
+ }
+ }
+
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
+ warning("v4l2: %s is no video capture device\n", dev_name);
+ return ENODEV;
+ }
+
+ if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
+ warning("v4l2: %s does not support streaming i/o\n",
+ dev_name);
+ return ENOSYS;
+ }
+
+ /* Negotiate video format */
+ memset(&fmts, 0, sizeof(fmts));
+
+ fmts.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ for (fmts.index=0; !v4l2_ioctl(st->fd, VIDIOC_ENUM_FMT, &fmts);
+ fmts.index++) {
+ if (match_fmt(fmts.pixelformat) != VID_FMT_N) {
+ st->pixfmt = fmts.pixelformat;
+ break;
+ }
+ }
+
+ if (!st->pixfmt) {
+ warning("v4l2: format negotiation failed: %m\n", errno);
+ return errno;
+ }
+
+ /* Select video input, video standard and tune here. */
+
+ memset(&fmt, 0, sizeof(fmt));
+
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt.fmt.pix.width = width;
+ fmt.fmt.pix.height = height;
+ fmt.fmt.pix.pixelformat = st->pixfmt;
+ fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+ if (-1 == xioctl(st->fd, VIDIOC_S_FMT, &fmt)) {
+ warning("v4l2: VIDIOC_S_FMT: %m\n", errno);
+ return errno;
+ }
+
+ /* Note VIDIOC_S_FMT may change width and height. */
+
+ /* Buggy driver paranoia. */
+ min = fmt.fmt.pix.width * 2;
+ if (fmt.fmt.pix.bytesperline < min)
+ fmt.fmt.pix.bytesperline = min;
+ min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
+ if (fmt.fmt.pix.sizeimage < min)
+ fmt.fmt.pix.sizeimage = min;
+
+ st->sz.w = fmt.fmt.pix.width;
+ st->sz.h = fmt.fmt.pix.height;
+
+ err = init_mmap(st, dev_name);
+ if (err)
+ return err;
+
+ pix = (char *)&fmt.fmt.pix.pixelformat;
+
+ if (st->pixfmt != fmt.fmt.pix.pixelformat) {
+ warning("v4l2: %s: unexpectedly got %c%c%c%c\n", dev_name,
+ pix[0], pix[1], pix[2], pix[3]);
+ return ENODEV;
+ }
+
+ info("v4l2: %s: found valid V4L2 device (%u x %u) pixfmt=%c%c%c%c\n",
+ dev_name, fmt.fmt.pix.width, fmt.fmt.pix.height,
+ pix[0], pix[1], pix[2], pix[3]);
+
+ return 0;
+}
+
+
+static void stop_capturing(struct vidsrc_st *st)
+{
+ enum v4l2_buf_type type;
+
+ if (st->fd < 0)
+ return;
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ xioctl(st->fd, VIDIOC_STREAMOFF, &type);
+}
+
+
+static void uninit_device(struct vidsrc_st *st)
+{
+ unsigned int i;
+
+ for (i=0; i<st->n_buffers; ++i) {
+ v4l2_munmap(st->buffers[i].start, st->buffers[i].length);
+ }
+
+ st->buffers = mem_deref(st->buffers);
+ st->n_buffers = 0;
+}
+
+
+static int start_capturing(struct vidsrc_st *st)
+{
+ unsigned int i;
+ enum v4l2_buf_type type;
+
+ for (i = 0; i < st->n_buffers; ++i) {
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ if (-1 == xioctl (st->fd, VIDIOC_QBUF, &buf))
+ return errno;
+ }
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ if (-1 == xioctl (st->fd, VIDIOC_STREAMON, &type))
+ return errno;
+
+ return 0;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, match_fmt(st->pixfmt), &st->sz, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static int read_frame(struct vidsrc_st *st)
+{
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+
+ if (-1 == xioctl (st->fd, VIDIOC_DQBUF, &buf)) {
+ switch (errno) {
+
+ case EAGAIN:
+ return 0;
+
+ case EIO:
+ /* Could ignore EIO, see spec. */
+
+ /* fall through */
+
+ default:
+ warning("v4l2: VIDIOC_DQBUF: %m\n", errno);
+ return errno;
+ }
+ }
+
+ if (buf.index >= st->n_buffers) {
+ warning("v4l2: index >= n_buffers\n");
+ }
+
+ call_frame_handler(st, st->buffers[buf.index].start);
+
+ if (-1 == xioctl (st->fd, VIDIOC_QBUF, &buf)) {
+ warning("v4l2: VIDIOC_QBUF\n");
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static int vd_open(struct vidsrc_st *st, const char *device)
+{
+ st->fd = v4l2_open(device, O_RDWR);
+ if (st->fd < 0) {
+ warning("v4l2: open %s: %m\n", device, errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ debug("v4l2: stopping video source..\n");
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ stop_capturing(st);
+ uninit_device(st);
+
+ if (st->fd >= 0)
+ v4l2_close(st->fd);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ int err;
+
+ while (st->run) {
+ err = read_frame(st);
+ if (err) {
+ warning("v4l2: read_frame: %m\n", err);
+ }
+ }
+
+ return NULL;
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ if (!str_isset(dev))
+ dev = "/dev/video0";
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->fd = -1;
+ st->sz = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+ st->pixfmt = 0;
+
+ err = vd_open(st, dev);
+ if (err)
+ goto out;
+
+ err = v4l2_init_device(st, dev, size->w, size->h);
+ if (err)
+ goto out;
+
+ print_video_input(st);
+
+ err = start_capturing(st);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int v4l_init(void)
+{
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "v4l2", alloc, NULL);
+}
+
+
+static int v4l_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2) = {
+ "v4l2",
+ "vidsrc",
+ v4l_init,
+ v4l_close
+};
diff --git a/modules/v4l2_codec/README b/modules/v4l2_codec/README
new file mode 100644
index 0000000..501647c
--- /dev/null
+++ b/modules/v4l2_codec/README
@@ -0,0 +1,41 @@
+README
+------
+
+This module is using V4L2 (Video for Linux 2) as a codec module
+for devices that supports compressed formats such as H.264
+The module implements both the vidsrc API and the vidcodec API.
+
+
+- encoder/decoder: Encoder only
+- codec formats: H.264
+- keyframe refresh: Not supported
+
+
+
+
+EXAMPLE CONFIG
+--------------
+
+# Video
+video_source v4l2_codec,/dev/video0
+video_size 640x480
+
+
+# Video codec Modules (in order)
+module v4l2_codec.so
+
+
+
+
+SUPPORTED DEVICES
+-----------------
+
+This webcam supports H.264 hardware acceleration:
+
+HD Pro Webcam C920 (usb-0000:00:1a.0-1.5):
+ /dev/video0
+
+ELP-USB100W04H-L36 ARC International - (Product ID : 05a3:9420)
+ This device provide 2 /dev/video sub-devices.
+ The first is a YUV and MJPEG device (/dev/video0), the second is for the H264 stream (/dev/video1).
+ You must use the second the second (/dev/video1).
diff --git a/modules/v4l2_codec/module.mk b/modules/v4l2_codec/module.mk
new file mode 100644
index 0000000..b0b7f3e
--- /dev/null
+++ b/modules/v4l2_codec/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+MOD := v4l2_codec
+$(MOD)_SRCS += v4l2_codec.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/v4l2_codec/v4l2_codec.c b/modules/v4l2_codec/v4l2_codec.c
new file mode 100644
index 0000000..814a477
--- /dev/null
+++ b/modules/v4l2_codec/v4l2_codec.c
@@ -0,0 +1,603 @@
+/**
+ * @file v4l2_codec.c Video4Linux2 video-source and video-codec
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#if defined (OPENBSD) || defined (NETBSD)
+#include <sys/videoio.h>
+#else
+#include <linux/videodev2.h>
+#endif
+
+
+/**
+ * @defgroup v4l2_codec v4l2_codec
+ *
+ * V4L2 (Video for Linux 2) video-codec and source hybrid module
+ *
+ * This module is using V4L2 (Video for Linux 2) as a codec module
+ * for devices that supports compressed formats such as H.264.
+ * The module implements both the vidsrc API and the vidcodec API.
+ *
+ *
+ * TODO:
+ *
+ * - timestamp syncronization
+ * - how to configure the wanted bitrate and framerate
+ * - how to handle Key-frame requests
+ */
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ uint8_t *buffer;
+ size_t buffer_len;
+ int fd;
+ struct {
+ unsigned n_key;
+ unsigned n_delta;
+ } stats;
+};
+
+struct videnc_state {
+ struct le le;
+ struct videnc_param encprm;
+ videnc_packet_h *pkth;
+ void *arg;
+};
+
+
+/* TODO: global data, move to per vidsrc instance */
+static struct {
+ /* List of encoder-states (struct videnc_state) */
+ struct list encoderl;
+} v4l2;
+
+
+static struct vidsrc *vidsrc;
+
+
+static int xioctl(int fd, unsigned long int request, void *arg)
+{
+ int r;
+
+ do r = ioctl (fd, request, arg);
+ while (-1 == r && EINTR == errno);
+
+ return r;
+}
+
+
+static int print_caps(int fd, unsigned width, unsigned height)
+{
+ struct v4l2_capability caps;
+ struct v4l2_fmtdesc fmtdesc;
+ struct v4l2_format fmt;
+ bool support_h264 = false;
+ char fourcc[5] = {0};
+ char c;
+ int err;
+
+ memset(&caps, 0, sizeof(caps));
+ memset(&fmtdesc, 0, sizeof(fmtdesc));
+ memset(&fmt, 0, sizeof(fmt));
+
+ if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &caps)) {
+ err = errno;
+ warning("v4l2_codec: error Querying Capabilities (%m)\n", err);
+ return err;
+ }
+
+ info("v4l2_codec: Driver Caps:\n"
+ " Driver: \"%s\"\n"
+ " Card: \"%s\"\n"
+ " Bus: \"%s\"\n"
+ " Version: %d.%d\n"
+ " Capabilities: 0x%08x\n",
+ caps.driver,
+ caps.card,
+ caps.bus_info,
+ (caps.version>>16) & 0xff,
+ (caps.version>>24) & 0xff,
+ caps.capabilities);
+
+
+ fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ info(" Formats:\n");
+
+ while (0 == xioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) {
+ bool selected = false;
+
+ strncpy(fourcc, (char *)&fmtdesc.pixelformat, 4);
+
+#ifdef V4L2_PIX_FMT_H264
+ if (fmtdesc.pixelformat == V4L2_PIX_FMT_H264) {
+ support_h264 = true;
+ selected = true;
+ }
+#endif
+
+ c = fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED ? 'C' : ' ';
+
+ info(" %c %s: %c '%s'\n",
+ selected ? '>' : ' ',
+ fourcc, c, fmtdesc.description);
+
+ fmtdesc.index++;
+ }
+
+ info("\n");
+
+ if (!support_h264) {
+ warning("v4l2_codec: Doesn't support H264.\n");
+ return ENODEV;
+ }
+
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt.fmt.pix.width = width;
+ fmt.fmt.pix.height = height;
+#ifdef V4L2_PIX_FMT_H264
+ fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_H264;
+#endif
+ fmt.fmt.pix.field = V4L2_FIELD_NONE;
+
+ if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) {
+ err = errno;
+ warning("v4l2_codec: Setting Pixel Format (%m)\n", err);
+ return err;
+ }
+
+ strncpy(fourcc, (char *)&fmt.fmt.pix.pixelformat, 4);
+ info("v4l2_codec: Selected Camera Mode:\n"
+ " Width: %d\n"
+ " Height: %d\n"
+ " PixFmt: %s\n"
+ " Field: %d\n",
+ fmt.fmt.pix.width,
+ fmt.fmt.pix.height,
+ fourcc,
+ fmt.fmt.pix.field);
+
+ return 0;
+}
+
+
+static int init_mmap(struct vidsrc_st *st, int fd)
+{
+ struct v4l2_requestbuffers req;
+ struct v4l2_buffer buf;
+ int err;
+
+ memset(&req, 0, sizeof(req));
+ memset(&buf, 0, sizeof(buf));
+
+ req.count = 1;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+
+ if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
+ err = errno;
+ warning("v4l2_codec: Requesting Buffer (%m)\n", err);
+ return err;
+ }
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = 0;
+ if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) {
+ err = errno;
+ warning("v4l2_codec: Querying Buffer (%m)\n", err);
+ return err;
+ }
+
+ st->buffer = mmap(NULL, buf.length,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ fd, buf.m.offset);
+ if (st->buffer == MAP_FAILED) {
+ err = errno;
+ warning("v4l2_codec: mmap failed (%m)\n", err);
+ return err;
+ }
+ st->buffer_len = buf.length;
+
+ return 0;
+}
+
+
+static int query_buffer(int fd)
+{
+ struct v4l2_buffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = 0;
+
+ if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
+ return errno;
+
+ return 0;
+}
+
+
+static int start_streaming(int fd)
+{
+ struct v4l2_buffer buf;
+ int err;
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = 0;
+
+ if (-1 == xioctl(fd, VIDIOC_STREAMON, &buf.type)) {
+ err = errno;
+ warning("v4l2_codec: Start Capture (%m)\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+
+static void stop_capturing(int fd)
+{
+ enum v4l2_buf_type type;
+
+ if (fd < 0)
+ return;
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ xioctl(fd, VIDIOC_STREAMOFF, &type);
+}
+
+
+static void enc_destructor(void *arg)
+{
+ struct videnc_state *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+static void encoders_read(uint32_t rtp_ts, const uint8_t *buf, size_t sz)
+{
+ struct le *le;
+ int err;
+
+ for (le = v4l2.encoderl.head; le; le = le->next) {
+ struct videnc_state *st = le->data;
+
+ err = h264_packetize(rtp_ts, buf, sz,
+ st->encprm.pktsize,
+ st->pkth, st->arg);
+ if (err) {
+ warning("h264_packetize error (%m)\n", err);
+ }
+ }
+}
+
+
+static void read_handler(int flags, void *arg)
+{
+ struct vidsrc_st *st = arg;
+ struct v4l2_buffer buf;
+ bool keyframe = false;
+ struct timeval ts;
+ uint32_t rtp_ts;
+ int err;
+
+ if (flags & FD_EXCEPT) {
+ warning("v4l2_codec: device error\n");
+ return;
+ }
+
+ memset(&buf, 0, sizeof(buf));
+
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = 0;
+
+ if (-1 == xioctl(st->fd, VIDIOC_DQBUF, &buf)) {
+ err = errno;
+ warning("v4l2_codec: Retrieving Frame (%m)\n", err);
+ return;
+ }
+
+
+ {
+ struct mbuf mb = {0,0,0,0};
+ struct h264_hdr hdr;
+
+ mb.buf = st->buffer;
+ mb.pos = 4;
+ mb.end = buf.bytesused - 4;
+ mb.size = buf.bytesused;
+
+ err = h264_hdr_decode(&hdr, &mb);
+ if (err) {
+ warning("could not decode H.264 header\n");
+ }
+ else {
+ keyframe = h264_is_keyframe(hdr.type);
+ if (keyframe) {
+ ++st->stats.n_key;
+ }
+ else
+ ++st->stats.n_delta;
+ }
+ }
+
+ ts = buf.timestamp;
+ rtp_ts = (90000ULL * (1000000*ts.tv_sec + ts.tv_usec)) / 1000000;
+
+#if 0
+ debug("v4l2_codec: %s frame captured at %ldsec, %ldusec (%zu bytes)\n",
+ keyframe ? "KEY" : " ",
+ buf.timestamp.tv_sec, buf.timestamp.tv_usec,
+ (size_t)buf.bytesused);
+#endif
+
+ /* pass the frame to the encoders */
+ encoders_read(rtp_ts, st->buffer, buf.bytesused);
+
+ err = query_buffer(st->fd);
+ if (err) {
+ warning("v4l2_codec: query_buffer failed (%m)\n", err);
+ }
+}
+
+
+static int open_encoder(struct vidsrc_st *st, const char *device,
+ unsigned width, unsigned height)
+{
+ int err;
+
+ debug("v4l2_codec: opening video-encoder device (device=%s)\n",
+ device);
+
+ st->fd = open(device, O_RDWR);
+ if (st->fd == -1) {
+ err = errno;
+ warning("Opening video device (%m)\n", err);
+ goto out;
+ }
+
+ err = print_caps(st->fd, width, height);
+ if (err)
+ goto out;
+
+ err = init_mmap(st, st->fd);
+ if (err)
+ goto out;
+
+ err = query_buffer(st->fd);
+ if (err)
+ goto out;
+
+ err = start_streaming(st->fd);
+ if (err)
+ goto out;
+
+ err = fd_listen(st->fd, FD_READ, read_handler, st);
+ if (err)
+ goto out;
+
+out:
+ return err;
+}
+
+
+static int encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *st;
+ int err = 0;
+ (void)fmtp;
+
+ if (!vesp || !vc || !prm || !pkth)
+ return EINVAL;
+
+ if (*vesp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->encprm = *prm;
+ st->pkth = pkth;
+ st->arg = arg;
+
+ list_append(&v4l2.encoderl, &st->le, st);
+
+ info("v4l2_codec: video encoder %s: %d fps, %d bit/s, pktsize=%u\n",
+ vc->name, prm->fps, prm->bitrate, prm->pktsize);
+
+ if (err)
+ mem_deref(st);
+ else
+ *vesp = st;
+
+ return err;
+}
+
+
+/* note: dummy function, the input is unused */
+static int encode_packet(struct videnc_state *st, bool update,
+ const struct vidframe *frame)
+{
+ (void)st;
+ (void)update;
+ (void)frame;
+ return 0;
+}
+
+
+static uint32_t packetization_mode(const char *fmtp)
+{
+ struct pl pl, mode;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "packetization-mode", &mode))
+ return pl_u32(&mode);
+
+ return 0;
+}
+
+
+static int h264_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ struct vidcodec *vc = arg;
+ const uint8_t profile_idc = 0x42; /* baseline profile */
+ const uint8_t profile_iop = 0x80;
+ static const uint8_t h264_level_idc = 0x0c;
+ (void)offer;
+
+ if (!mb || !fmt || !vc)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s"
+ " packetization-mode=0"
+ ";profile-level-id=%02x%02x%02x"
+ "\r\n",
+ fmt->id, profile_idc, profile_iop, h264_level_idc);
+}
+
+
+static bool h264_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data)
+{
+ (void)data;
+
+ return packetization_mode(fmtp1) == packetization_mode(fmtp2);
+}
+
+
+static void src_destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->fd >=0 ) {
+ info("v4l2_codec: encoder stats"
+ " (keyframes:%u, deltaframes:%u)\n",
+ st->stats.n_key, st->stats.n_delta);
+ }
+
+ stop_capturing(st->fd);
+
+ if (st->buffer)
+ munmap(st->buffer, st->buffer_len);
+
+ if (st->fd >= 0) {
+ fd_close(st->fd);
+ close(st->fd);
+ }
+}
+
+
+static int src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err = 0;
+
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+ (void)arg;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ if (!str_isset(dev))
+ dev = "/dev/video0";
+
+ debug("v4l2_codec: video-source alloc (device=%s)\n", dev);
+
+ st = mem_zalloc(sizeof(*st), src_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+
+ err = open_encoder(st, dev, size->w, size->h);
+ if (err)
+ goto out;
+
+out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static struct vidcodec h264 = {
+ LE_INIT,
+ NULL,
+ "H264",
+ "packetization-mode=0",
+ NULL,
+ encode_update,
+ encode_packet,
+ NULL,
+ NULL,
+ h264_fmtp_enc,
+ h264_fmtp_cmp,
+};
+
+
+static int module_init(void)
+{
+ info("v4l2_codec inited\n");
+
+ vidcodec_register(baresip_vidcodecl(), &h264);
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "v4l2_codec", src_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ vidcodec_unregister(&h264);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(v4l2_codec) = {
+ "v4l2_codec",
+ "vidcodec",
+ module_init,
+ module_close
+};
diff --git a/modules/vidbridge/disp.c b/modules/vidbridge/disp.c
new file mode 100644
index 0000000..be1e8e5
--- /dev/null
+++ b/modules/vidbridge/disp.c
@@ -0,0 +1,94 @@
+/**
+ * @file vidbridge/disp.c Video bridge -- display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ if (st->vidsrc)
+ st->vidsrc->vidisp = NULL;
+
+ list_unlink(&st->le);
+ mem_deref(st->device);
+}
+
+
+int vidbridge_disp_alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+ (void)prm;
+ (void)resizeh;
+ (void)arg;
+
+ if (!stp || !vd || !dev)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ err = str_dup(&st->device, dev);
+ if (err)
+ goto out;
+
+ /* find the vidsrc with the same device-name */
+ st->vidsrc = vidbridge_src_find(dev);
+ if (st->vidsrc) {
+ st->vidsrc->vidisp = st;
+ }
+
+ hash_append(ht_disp, hash_joaat_str(dev), &st->le, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static bool list_apply_handler(struct le *le, void *arg)
+{
+ struct vidisp_st *st = le->data;
+
+ return 0 == str_cmp(st->device, arg);
+}
+
+
+int vidbridge_disp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ int err = 0;
+ (void)title;
+
+ if (st->vidsrc)
+ vidbridge_src_input(st->vidsrc, frame);
+ else {
+ debug("vidbridge: display: dropping frame (%u x %u)\n",
+ frame->size.w, frame->size.h);
+ }
+
+ return err;
+}
+
+
+struct vidisp_st *vidbridge_disp_find(const char *device)
+{
+ return list_ledata(hash_lookup(ht_disp, hash_joaat_str(device),
+ list_apply_handler, (void *)device));
+}
diff --git a/modules/vidbridge/module.mk b/modules/vidbridge/module.mk
new file mode 100644
index 0000000..13000b6
--- /dev/null
+++ b/modules/vidbridge/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vidbridge
+$(MOD)_SRCS += vidbridge.c src.c disp.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/vidbridge/src.c b/modules/vidbridge/src.c
new file mode 100644
index 0000000..5e7d08a
--- /dev/null
+++ b/modules/vidbridge/src.c
@@ -0,0 +1,93 @@
+/**
+ * @file vidbridge/src.c Video bridge -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->vidisp)
+ st->vidisp->vidsrc = NULL;
+
+ list_unlink(&st->le);
+ mem_deref(st->device);
+}
+
+
+int vidbridge_src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+ (void)ctx;
+ (void)prm;
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = str_dup(&st->device, dev);
+ if (err)
+ goto out;
+
+ /* find a vidisp device with same name */
+ st->vidisp = vidbridge_disp_find(dev);
+ if (st->vidisp) {
+ st->vidisp->vidsrc = st;
+ }
+
+ hash_append(ht_src, hash_joaat_str(dev), &st->le, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static bool list_apply_handler(struct le *le, void *arg)
+{
+ struct vidsrc_st *st = le->data;
+
+ return 0 == str_cmp(st->device, arg);
+}
+
+
+struct vidsrc_st *vidbridge_src_find(const char *device)
+{
+ return list_ledata(hash_lookup(ht_src, hash_joaat_str(device),
+ list_apply_handler, (void *)device));
+}
+
+
+void vidbridge_src_input(const struct vidsrc_st *st,
+ const struct vidframe *frame)
+{
+ if (!st || !frame)
+ return;
+
+ if (st->frameh)
+ st->frameh((struct vidframe *)frame, st->arg);
+}
diff --git a/modules/vidbridge/vidbridge.c b/modules/vidbridge/vidbridge.c
new file mode 100644
index 0000000..557e175
--- /dev/null
+++ b/modules/vidbridge/vidbridge.c
@@ -0,0 +1,78 @@
+/**
+ * @file vidbridge.c Video bridge
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidbridge.h"
+
+
+/**
+ * @defgroup vidbridge vidbridge
+ *
+ * Video bridge module
+ *
+ * This module can be used to connect two video devices together,
+ * so that all output to VIDISP device is bridged as the input to
+ * a VIDSRC device.
+ *
+ * Sample config:
+ *
+ \verbatim
+ video_display vidbridge,pseudo0
+ video_source vidbridge,pseudo0
+ \endverbatim
+ */
+
+
+static struct vidisp *vidisp;
+static struct vidsrc *vidsrc;
+
+struct hash *ht_src;
+struct hash *ht_disp;
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = hash_alloc(&ht_src, 32);
+ err |= hash_alloc(&ht_disp, 32);
+ if (err)
+ return err;
+
+ err = vidisp_register(&vidisp, baresip_vidispl(),
+ "vidbridge", vidbridge_disp_alloc,
+ NULL, vidbridge_disp_display, 0);
+ if (err)
+ return err;
+
+ err = vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "vidbridge", vidbridge_src_alloc, NULL);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ vidisp = mem_deref(vidisp);
+
+ ht_src = mem_deref(ht_src);
+ ht_disp = mem_deref(ht_disp);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vidbridge) = {
+ "vidbridge",
+ "video",
+ module_init,
+ module_close,
+};
diff --git a/modules/vidbridge/vidbridge.h b/modules/vidbridge/vidbridge.h
new file mode 100644
index 0000000..0fbf68e
--- /dev/null
+++ b/modules/vidbridge/vidbridge.h
@@ -0,0 +1,47 @@
+/**
+ * @file vidbridge.h Video bridge -- internal interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance (1st) */
+
+ struct le le;
+ struct vidisp_st *vidisp;
+ char *device;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance (1st) */
+
+ struct le le;
+ struct vidsrc_st *vidsrc;
+ char *device;
+};
+
+
+extern struct hash *ht_src;
+extern struct hash *ht_disp;
+
+
+int vidbridge_disp_alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg);
+int vidbridge_disp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+struct vidisp_st *vidbridge_disp_find(const char *device);
+
+
+int vidbridge_src_alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg);
+struct vidsrc_st *vidbridge_src_find(const char *device);
+void vidbridge_src_input(const struct vidsrc_st *st,
+ const struct vidframe *frame);
diff --git a/modules/vidinfo/module.mk b/modules/vidinfo/module.mk
new file mode 100644
index 0000000..a9cb4b0
--- /dev/null
+++ b/modules/vidinfo/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vidinfo
+$(MOD)_SRCS += vidinfo.c panel.c
+$(MOD)_LFLAGS += $(shell pkg-config --libs cairo)
+$(MOD)_CFLAGS += $(shell pkg-config --cflags cairo)
+
+include mk/mod.mk
diff --git a/modules/vidinfo/panel.c b/modules/vidinfo/panel.c
new file mode 100644
index 0000000..93c475b
--- /dev/null
+++ b/modules/vidinfo/panel.c
@@ -0,0 +1,289 @@
+/**
+ * @file panel.c Video-info filter -- panel
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidinfo.h"
+
+
+static void rrd_append(struct panel *panel, uint64_t val)
+{
+ if (!panel)
+ return;
+
+ panel->rrdv[panel->rrdc++] = val;
+ panel->rrd_sum += val;
+
+ if (panel->rrdc >= panel->rrdsz) {
+ panel->rrdc = 0;
+ panel->rrd_sum = 0;
+ }
+}
+
+
+static int rrd_get_average(struct panel *panel, uint64_t *average)
+{
+ if (!panel->rrdc)
+ return ENOENT;
+
+ *average = panel->rrd_sum / panel->rrdc;
+
+ return 0;
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct panel *panel = arg;
+ uint64_t now = tmr_jiffies();
+
+ tmr_start(&panel->tmr, 2000, tmr_handler, panel);
+
+ if (panel->ts) {
+ panel->fps = 1000.0 * panel->nframes / (now - panel->ts);
+ }
+ panel->nframes = 0;
+
+ panel->ts = now;
+}
+
+
+static void destructor(void *arg)
+{
+ struct panel *panel = arg;
+
+ tmr_cancel(&panel->tmr);
+ mem_deref(panel->label);
+ mem_deref(panel->rrdv);
+
+ if (panel->cr)
+ cairo_destroy(panel->cr);
+ if (panel->surface)
+ cairo_surface_destroy(panel->surface);
+}
+
+
+int panel_alloc(struct panel **panelp, const char *label,
+ unsigned yoffs, int width, int height)
+{
+ struct panel *panel;
+ int err;
+
+ if (!panelp || !width || !height)
+ return EINVAL;
+
+ panel = mem_zalloc(sizeof(*panel), destructor);
+ if (!panel)
+ return ENOMEM;
+
+ err = str_dup(&panel->label, label);
+ if (err)
+ goto out;
+
+ panel->size.w = width;
+ panel->size.h = height;
+ panel->yoffs = yoffs;
+ panel->xoffs = TEXT_WIDTH;
+
+ panel->size_text.w = TEXT_WIDTH;
+ panel->size_text.h = height;
+
+ panel->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ panel->size_text.w,
+ panel->size_text.h);
+ panel->cr = cairo_create(panel->surface);
+ if (!panel->surface || !panel->cr) {
+ warning("vidinfo: cairo error\n");
+ return ENOMEM;
+ }
+
+ cairo_select_font_face (panel->cr, "Hyperfont",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size (panel->cr, height-2);
+
+ panel->rrdc = 0;
+ panel->rrdsz = (width - TEXT_WIDTH) / 2;
+ panel->rrdv = mem_reallocarray(NULL, panel->rrdsz,
+ sizeof(*panel->rrdv), NULL);
+ if (!panel->rrdv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ tmr_start(&panel->tmr, 0, tmr_handler, panel);
+
+ info("new panel '%s' (%u x %u) with RRD size %u\n",
+ label, width, height, panel->rrdsz);
+
+ out:
+ if (err)
+ mem_deref(panel);
+ else
+ *panelp = panel;
+
+ return err;
+}
+
+
+static void overlay(struct vidframe *dst, unsigned yoffs, struct vidframe *src)
+{
+ uint8_t *pdst, *psrc;
+ unsigned x, y;
+
+ pdst = dst->data[0] + yoffs * dst->linesize[0];
+ psrc = src->data[0];
+
+ for (y=0; y<src->size.h; y++) {
+
+ for (x=0; x<src->size.w; x++) {
+
+ /* copy the luma component if visible */
+ if (psrc[x] > 16)
+ pdst[x] = psrc[x];
+ }
+
+ pdst += dst->linesize[0];
+ psrc += src->linesize[0];
+ }
+}
+
+
+static int draw_text(struct panel *panel, struct vidframe *frame)
+{
+ char buf[256];
+ int width = panel->size_text.w;
+ int height = panel->size_text.h;
+ struct vidframe f;
+ struct vidframe *f2 = NULL;
+ cairo_t *cr = panel->cr;
+ double tx, ty;
+ int err;
+
+ tx = 1;
+ ty = height - 3;
+
+ /* draw background */
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+ cairo_fill (cr);
+
+ /* Draw text */
+ if (re_snprintf(buf, sizeof(buf), "%s %2.2f fps",
+ panel->label, panel->fps) < 0)
+ return ENOMEM;
+
+ cairo_move_to (cr, tx, ty);
+ cairo_text_path (cr, buf);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_fill_preserve (cr);
+ cairo_set_line_width (cr, 0.6);
+ cairo_stroke (cr);
+
+ vidframe_init_buf(&f, VID_FMT_RGB32, &panel->size_text,
+ cairo_image_surface_get_data(panel->surface));
+
+ err = vidframe_alloc(&f2, frame->fmt, &panel->size_text);
+ if (err)
+ goto out;
+
+ vidconv(f2, &f, 0);
+
+ overlay(frame, panel->yoffs, f2);
+
+ out:
+ mem_deref(f2);
+ return err;
+}
+
+
+static void dim_frame(struct vidframe *frame, unsigned yoffs, unsigned height)
+{
+ unsigned x, y;
+ uint8_t *p;
+ bool lower = (yoffs > 0);
+ double grade = lower ? 1.00 : (1.00 - PANEL_HEIGHT/100.0);
+
+ p = frame->data[0] + yoffs * frame->linesize[0];
+
+ /* first dim the background */
+ for (y = 0; y < height; y++) {
+
+ for (x = 0; x < frame->size.w; x++) {
+ p[x] = p[x] * grade;
+ }
+
+ p += frame->linesize[0];
+
+ if (lower)
+ grade -= 0.01;
+ else
+ grade += 0.01;
+ }
+}
+
+
+static void draw_graph(struct panel *panel, struct vidframe *frame)
+{
+ uint64_t avg;
+ unsigned y0 = panel->yoffs;
+ size_t i;
+
+ if (rrd_get_average(panel, &avg))
+ return;
+
+ for (i=0; i<panel->rrdc; i++) {
+
+ uint64_t value;
+ double ratio;
+ unsigned pixels;
+ unsigned x = panel->xoffs + (unsigned)i * 2;
+ unsigned y;
+ value = panel->rrdv[i];
+
+ ratio = (double)value / (double)avg;
+
+ pixels = (unsigned)((double)panel->size.h * ratio * 0.5f);
+
+ pixels = min(pixels, panel->size.h);
+
+ y = y0 + panel->size.h - pixels;
+
+ vidframe_draw_vline(frame, x, y, pixels, 220, 220, 220);
+ }
+}
+
+
+int panel_draw(struct panel *panel, struct vidframe *frame)
+{
+ int err;
+
+ if (!panel || !frame)
+ return EINVAL;
+
+ dim_frame(frame, panel->yoffs, panel->size.h);
+
+ err = draw_text(panel, frame);
+ if (err)
+ return err;
+ draw_graph(panel, frame);
+
+ return 0;
+}
+
+
+void panel_add_frame(struct panel *panel, uint64_t pts)
+{
+ if (!panel)
+ return;
+
+ if (panel->pts_prev) {
+ rrd_append(panel, pts - panel->pts_prev);
+ }
+
+ panel->nframes++;
+ panel->pts_prev = pts;
+}
diff --git a/modules/vidinfo/vidinfo.c b/modules/vidinfo/vidinfo.c
new file mode 100644
index 0000000..f460a2d
--- /dev/null
+++ b/modules/vidinfo/vidinfo.c
@@ -0,0 +1,177 @@
+/**
+ * @file vidinfo.c Video-info filter
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vidinfo.h"
+
+
+/**
+ * @defgroup vidinfo vidinfo
+ *
+ * Display video-info overlay on the encode/decode streams
+ *
+ * Displays info like framerate and packet timing, this is mainly
+ * for development and debugging.
+ */
+
+
+struct vidinfo_enc {
+ struct vidfilt_enc_st vf; /* base member (inheritance) */
+
+ struct panel *panel;
+};
+
+
+struct vidinfo_dec {
+ struct vidfilt_dec_st vf; /* base member (inheritance) */
+
+ struct panel *panel;
+};
+
+
+static void encode_destructor(void *arg)
+{
+ struct vidinfo_enc *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->panel);
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct vidinfo_dec *st = arg;
+
+ list_unlink(&st->vf.le);
+ mem_deref(st->panel);
+}
+
+
+static int encode_update(struct vidfilt_enc_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidinfo_enc *st;
+ int err = 0;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), encode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_enc_st *)st;
+
+ return err;
+}
+
+
+static int decode_update(struct vidfilt_dec_st **stp, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidinfo_dec *st;
+ int err = 0;
+
+ if (!stp || !ctx || !vf)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), decode_destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = (struct vidfilt_dec_st *)st;
+
+ return err;
+}
+
+
+static int encode(struct vidfilt_enc_st *_st, struct vidframe *frame)
+{
+ struct vidinfo_enc *st = (struct vidinfo_enc *)_st;
+ int err = 0;
+
+ if (!st->panel) {
+
+ unsigned width = frame->size.w;
+ unsigned height = MIN(PANEL_HEIGHT, frame->size.h);
+
+ err = panel_alloc(&st->panel, "encode", 0, width, height);
+ if (err)
+ return err;
+ }
+
+ panel_add_frame(st->panel, tmr_jiffies());
+
+ panel_draw(st->panel, frame);
+
+ return 0;
+}
+
+
+static int decode(struct vidfilt_dec_st *_st, struct vidframe *frame)
+{
+ struct vidinfo_dec *st = (struct vidinfo_dec *)_st;
+ int err = 0;
+
+ if (!st->panel) {
+
+ unsigned width = frame->size.w;
+ unsigned height = MIN(PANEL_HEIGHT, frame->size.h);
+ unsigned yoffs = frame->size.h - PANEL_HEIGHT;
+
+ err = panel_alloc(&st->panel, "decode", yoffs, width, height);
+ if (err)
+ return err;
+ }
+
+ panel_add_frame(st->panel, tmr_jiffies());
+
+ panel_draw(st->panel, frame);
+
+ return 0;
+}
+
+
+static struct vidfilt vidinfo = {
+ LE_INIT, "vidinfo", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ vidfilt_register(baresip_vidfiltl(), &vidinfo);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidfilt_unregister(&vidinfo);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vidinfo) = {
+ "vidinfo",
+ "vidfilt",
+ module_init,
+ module_close
+};
diff --git a/modules/vidinfo/vidinfo.h b/modules/vidinfo/vidinfo.h
new file mode 100644
index 0000000..7f540e4
--- /dev/null
+++ b/modules/vidinfo/vidinfo.h
@@ -0,0 +1,42 @@
+/**
+ * @file vidinfo.h Video-info filter
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+
+#include <cairo/cairo.h>
+
+
+#define PANEL_HEIGHT 24
+#define TEXT_WIDTH 220
+
+
+struct panel {
+ struct vidsz size;
+ struct vidsz size_text;
+ unsigned yoffs;
+ unsigned xoffs;
+ char *label;
+
+ uint64_t *rrdv;
+ size_t rrdsz;
+ size_t rrdc;
+ uint64_t rrd_sum;
+
+ unsigned nframes;
+ uint64_t ts;
+ double fps;
+ struct tmr tmr;
+
+ uint64_t pts_prev;
+
+ /* cairo backend: */
+ cairo_surface_t *surface;
+ cairo_t *cr;
+};
+
+int panel_alloc(struct panel **panelp, const char *label,
+ unsigned yoffs, int width, int height);
+void panel_add_frame(struct panel *panel, uint64_t pts);
+int panel_draw(struct panel *panel, struct vidframe *frame);
diff --git a/modules/vidloop/module.mk b/modules/vidloop/module.mk
new file mode 100644
index 0000000..4775ef1
--- /dev/null
+++ b/modules/vidloop/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vidloop
+$(MOD)_SRCS += vidloop.c
+
+include mk/mod.mk
diff --git a/modules/vidloop/vidloop.c b/modules/vidloop/vidloop.c
new file mode 100644
index 0000000..d99fa49
--- /dev/null
+++ b/modules/vidloop/vidloop.c
@@ -0,0 +1,505 @@
+/**
+ * @file vidloop.c Video loop
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup vidloop vidloop
+ *
+ * A video-loop module for testing
+ *
+ * Simple test module that loops back the video frames from a
+ * video-source to a video-display, optionally via a video codec.
+ *
+ * Example usage without codec:
+ \verbatim
+ baresip -e/vidloop
+ \endverbatim
+ *
+ * Example usage with codec:
+ \verbatim
+ baresip -e"/vidloop h264"
+ \endverbatim
+ */
+
+
+/** Internal pixel-format */
+#ifndef VIDLOOP_INTERNAL_FMT
+#define VIDLOOP_INTERNAL_FMT (VID_FMT_YUV420P)
+#endif
+
+
+/** Video Statistics */
+struct vstat {
+ uint64_t tsamp;
+ uint32_t frames;
+ size_t bytes;
+ uint32_t bitrate;
+ double efps;
+ size_t n_intra;
+};
+
+
+/** Video loop */
+struct video_loop {
+ const struct vidcodec *vc_enc;
+ const struct vidcodec *vc_dec;
+ struct config_video cfg;
+ struct videnc_state *enc;
+ struct viddec_state *dec;
+ struct vidisp_st *vidisp;
+ struct vidsrc_st *vsrc;
+ struct list filtencl;
+ struct list filtdecl;
+ struct vstat stat;
+ struct tmr tmr_bw;
+ uint16_t seq;
+ bool need_conv;
+ int err;
+};
+
+
+static struct video_loop *gvl;
+
+
+static int display(struct video_loop *vl, struct vidframe *frame)
+{
+ struct vidframe *frame_filt = NULL;
+ struct le *le;
+ int err = 0;
+
+ if (!vidframe_isvalid(frame))
+ return 0;
+
+ /* Process video frame through all Video Filters */
+ for (le = vl->filtdecl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ /* Some video decoders keeps the displayed video frame
+ * in memory and we should not write to that frame.
+ */
+ if (!frame_filt) {
+
+ err = vidframe_alloc(&frame_filt, frame->fmt,
+ &frame->size);
+ if (err)
+ return err;
+
+ vidframe_copy(frame_filt, frame);
+
+ frame = frame_filt;
+ }
+
+ if (st->vf->dech)
+ err |= st->vf->dech(st, frame);
+ }
+
+ if (err) {
+ warning("vidloop: error in video-filters (%m)\n", err);
+ }
+
+ /* display frame */
+ err = vidisp_display(vl->vidisp, "Video Loop", frame);
+ if (err == ENODEV) {
+ info("vidloop: video-display was closed\n");
+ vl->vidisp = mem_deref(vl->vidisp);
+ vl->err = err;
+ }
+
+ mem_deref(frame_filt);
+
+ return err;
+}
+
+
+static int packet_handler(bool marker, uint32_t rtp_ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len,
+ void *arg)
+{
+ struct video_loop *vl = arg;
+ struct vidframe frame;
+ struct mbuf *mb;
+ bool intra;
+ int err = 0;
+ (void)rtp_ts;
+
+ mb = mbuf_alloc(hdr_len + pld_len);
+ if (!mb)
+ return ENOMEM;
+
+ if (hdr_len)
+ mbuf_write_mem(mb, hdr, hdr_len);
+ mbuf_write_mem(mb, pld, pld_len);
+
+ mb->pos = 0;
+
+ vl->stat.bytes += mbuf_get_left(mb);
+
+ /* decode */
+ frame.data[0] = NULL;
+ if (vl->vc_dec && vl->dec) {
+ err = vl->vc_dec->dech(vl->dec, &frame, &intra,
+ marker, vl->seq++, mb);
+ if (err) {
+ warning("vidloop: codec decode: %m\n", err);
+ goto out;
+ }
+
+ if (intra)
+ ++vl->stat.n_intra;
+ }
+
+ if (vidframe_isvalid(&frame)) {
+
+ display(vl, &frame);
+ }
+
+ out:
+ mem_deref(mb);
+
+ return 0;
+}
+
+
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct video_loop *vl = arg;
+ struct vidframe *f2 = NULL;
+ struct le *le;
+ int err = 0;
+
+ ++vl->stat.frames;
+
+ if (frame->fmt != VIDLOOP_INTERNAL_FMT) {
+
+ if (!vl->need_conv) {
+ info("vidloop: NOTE: pixel-format conversion"
+ " needed: %s --> %s\n",
+ vidfmt_name(frame->fmt),
+ vidfmt_name(VIDLOOP_INTERNAL_FMT));
+ vl->need_conv = true;
+ }
+
+ if (vidframe_alloc(&f2, VIDLOOP_INTERNAL_FMT, &frame->size))
+ return;
+
+ vidconv(f2, frame, 0);
+
+ frame = f2;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vl->filtencl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ if (vl->vc_enc && vl->enc) {
+ (void)vl->vc_enc->ench(vl->enc, false, frame);
+ }
+ else {
+ vl->stat.bytes += vidframe_size(frame->fmt, &frame->size);
+ (void)display(vl, frame);
+ }
+
+ mem_deref(f2);
+}
+
+
+static void vidloop_destructor(void *arg)
+{
+ struct video_loop *vl = arg;
+
+ tmr_cancel(&vl->tmr_bw);
+ mem_deref(vl->vsrc);
+ mem_deref(vl->enc);
+ mem_deref(vl->dec);
+ mem_deref(vl->vidisp);
+ list_flush(&vl->filtencl);
+ list_flush(&vl->filtdecl);
+}
+
+
+static int enable_codec(struct video_loop *vl, const char *name)
+{
+ struct list *vidcodecl = baresip_vidcodecl();
+ struct videnc_param prm;
+ int err;
+
+ prm.fps = vl->cfg.fps;
+ prm.pktsize = 1480;
+ prm.bitrate = vl->cfg.bitrate;
+ prm.max_fs = -1;
+
+ /* Use the first video codec */
+
+ vl->vc_enc = vidcodec_find_encoder(vidcodecl, name);
+ if (!vl->vc_enc) {
+ warning("vidloop: could not find encoder (%s)\n", name);
+ return ENOENT;
+ }
+
+ info("vidloop: enabled encoder %s (%u fps, %u bit/s)\n",
+ vl->vc_enc->name, prm.fps, prm.bitrate);
+
+ vl->vc_dec = vidcodec_find_decoder(vidcodecl, name);
+ if (!vl->vc_dec) {
+ warning("vidloop: could not find decoder (%s)\n", name);
+ return ENOENT;
+ }
+
+ info("vidloop: enabled decoder %s\n", vl->vc_dec->name);
+
+ err = vl->vc_enc->encupdh(&vl->enc, vl->vc_enc, &prm, NULL,
+ packet_handler, vl);
+ if (err) {
+ warning("vidloop: update encoder failed: %m\n", err);
+ return err;
+ }
+
+ if (vl->vc_dec->decupdh) {
+ err = vl->vc_dec->decupdh(&vl->dec, vl->vc_dec, NULL);
+ if (err) {
+ warning("vidloop: update decoder failed: %m\n", err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+static void print_status(struct video_loop *vl)
+{
+ (void)re_fprintf(stdout,
+ "\rstatus:"
+ " [%s] [%s] intra=%zu "
+ " EFPS=%.1f %u kbit/s \r",
+ vl->vc_enc ? vl->vc_enc->name : "",
+ vl->vc_dec ? vl->vc_dec->name : "",
+ vl->stat.n_intra,
+ vl->stat.efps, vl->stat.bitrate);
+ fflush(stdout);
+}
+
+
+static void calc_bitrate(struct video_loop *vl)
+{
+ const uint64_t now = tmr_jiffies();
+
+ if (now > vl->stat.tsamp) {
+
+ const uint32_t dur = (uint32_t)(now - vl->stat.tsamp);
+
+ vl->stat.efps = 1000.0f * vl->stat.frames / dur;
+
+ vl->stat.bitrate = (uint32_t) (8 * vl->stat.bytes / dur);
+ }
+
+ vl->stat.frames = 0;
+ vl->stat.bytes = 0;
+ vl->stat.tsamp = now;
+}
+
+
+static void timeout_bw(void *arg)
+{
+ struct video_loop *vl = arg;
+
+ if (vl->err) {
+ info("error in video-loop -- closing (%m)\n", vl->err);
+ gvl = mem_deref(gvl);
+ return;
+ }
+
+ tmr_start(&vl->tmr_bw, 2000, timeout_bw, vl);
+
+ calc_bitrate(vl);
+ print_status(vl);
+}
+
+
+static int vsrc_reopen(struct video_loop *vl, const struct vidsz *sz)
+{
+ struct vidsrc_prm prm;
+ int err;
+
+ info("vidloop: %s,%s: open video source: %u x %u at %u fps\n",
+ vl->cfg.src_mod, vl->cfg.src_dev,
+ sz->w, sz->h, vl->cfg.fps);
+
+ prm.orient = VIDORIENT_PORTRAIT;
+ prm.fps = vl->cfg.fps;
+
+ vl->vsrc = mem_deref(vl->vsrc);
+ err = vidsrc_alloc(&vl->vsrc, baresip_vidsrcl(),
+ vl->cfg.src_mod, NULL, &prm, sz,
+ NULL, vl->cfg.src_dev, vidsrc_frame_handler,
+ NULL, vl);
+ if (err) {
+ warning("vidloop: vidsrc '%s' failed: %m\n",
+ vl->cfg.src_dev, err);
+ }
+
+ return err;
+}
+
+
+static int video_loop_alloc(struct video_loop **vlp)
+{
+ struct video_loop *vl;
+ struct config *cfg;
+ struct le *le;
+ int err = 0;
+
+ cfg = conf_config();
+ if (!cfg)
+ return EINVAL;
+
+ vl = mem_zalloc(sizeof(*vl), vidloop_destructor);
+ if (!vl)
+ return ENOMEM;
+
+ vl->cfg = cfg->video;
+ tmr_init(&vl->tmr_bw);
+
+ /* Video filters */
+ for (le = list_head(baresip_vidfiltl()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ info("vidloop: added video-filter `%s'\n", vf->name);
+
+ err |= vidfilt_enc_append(&vl->filtencl, &ctx, vf);
+ err |= vidfilt_dec_append(&vl->filtdecl, &ctx, vf);
+ if (err) {
+ warning("vidloop: vidfilt error: %m\n", err);
+ }
+ }
+
+ info("vidloop: open video display (%s.%s)\n",
+ vl->cfg.disp_mod, vl->cfg.disp_dev);
+
+ err = vidisp_alloc(&vl->vidisp, baresip_vidispl(),
+ vl->cfg.disp_mod, NULL,
+ vl->cfg.disp_dev, NULL, vl);
+ if (err) {
+ warning("vidloop: video display failed: %m\n", err);
+ goto out;
+ }
+
+ tmr_start(&vl->tmr_bw, 1000, timeout_bw, vl);
+
+ out:
+ if (err)
+ mem_deref(vl);
+ else
+ *vlp = vl;
+
+ return err;
+}
+
+
+/**
+ * Start the video loop (for testing)
+ */
+static int vidloop_start(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ struct vidsz size;
+ struct config *cfg = conf_config();
+ const char *codec_name = carg->prm;
+ int err = 0;
+
+ size.w = cfg->video.width;
+ size.h = cfg->video.height;
+
+ if (gvl) {
+ return re_hprintf(pf, "video-loop already running.\n");
+ }
+
+ (void)re_hprintf(pf, "Enable video-loop on %s,%s: %u x %u\n",
+ cfg->video.src_mod, cfg->video.src_dev,
+ size.w, size.h);
+
+ err = video_loop_alloc(&gvl);
+ if (err) {
+ warning("vidloop: alloc: %m\n", err);
+ return err;
+ }
+
+ if (str_isset(codec_name)) {
+
+ err = enable_codec(gvl, codec_name);
+ if (err) {
+ gvl = mem_deref(gvl);
+ return err;
+ }
+
+ (void)re_hprintf(pf, "%sabled codec: %s\n",
+ gvl->vc_enc ? "En" : "Dis",
+ gvl->vc_enc ? gvl->vc_enc->name : "");
+ }
+
+ /* Start video source, after codecs are created */
+ err = vsrc_reopen(gvl, &size);
+ if (err) {
+ gvl = mem_deref(gvl);
+ return err;
+ }
+
+ return err;
+}
+
+
+static int vidloop_stop(struct re_printf *pf, void *arg)
+{
+ (void)arg;
+
+ if (gvl)
+ (void)re_hprintf(pf, "Disable video-loop\n");
+ gvl = mem_deref(gvl);
+ return 0;
+}
+
+
+static const struct cmd cmdv[] = {
+ {"vidloop", 0, CMD_PRM, "Start video-loop <codec>", vidloop_start},
+ {"vidloop_stop",0, 0, "Stop video-loop", vidloop_stop },
+};
+
+
+static int module_init(void)
+{
+ return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ gvl = mem_deref(gvl);
+ cmd_unregister(baresip_commands(), cmdv);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vidloop) = {
+ "vidloop",
+ "application",
+ module_init,
+ module_close,
+};
diff --git a/modules/vp8/decode.c b/modules/vp8/decode.c
new file mode 100644
index 0000000..4399c0b
--- /dev/null
+++ b/modules/vp8/decode.c
@@ -0,0 +1,290 @@
+/**
+ * @file vp8/decode.c VP8 Decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_decoder.h>
+#include <vpx/vp8dx.h>
+#include "vp8.h"
+
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+struct hdr {
+ unsigned x:1;
+ unsigned noref:1;
+ unsigned start:1;
+ unsigned partid:4;
+ /* extension fields */
+ unsigned i:1;
+ unsigned l:1;
+ unsigned t:1;
+ unsigned k:1;
+ uint16_t picid;
+ uint8_t tl0picidx;
+ unsigned tid:2;
+ unsigned y:1;
+ unsigned keyidx:5;
+};
+
+struct viddec_state {
+ vpx_codec_ctx_t ctx;
+ struct mbuf *mb;
+ bool ctxup;
+ bool started;
+ uint16_t seq;
+};
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->ctxup)
+ vpx_codec_destroy(&vds->ctx);
+
+ mem_deref(vds->mb);
+}
+
+
+int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ vpx_codec_err_t res;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ vds->mb = mbuf_alloc(1024);
+ if (!vds->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp8_dx_algo, NULL, 0);
+ if (res) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->ctxup = true;
+
+ out:
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->x = v>>7 & 0x1;
+ hdr->noref = v>>5 & 0x1;
+ hdr->start = v>>4 & 0x1;
+ hdr->partid = v & 0x07;
+
+ if (hdr->x) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->i = v>>7 & 0x1;
+ hdr->l = v>>6 & 0x1;
+ hdr->t = v>>5 & 0x1;
+ hdr->k = v>>4 & 0x1;
+ }
+
+ if (hdr->i) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ if (v>>7 & 0x1) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->picid = (v & 0x7f)<<8;
+ hdr->picid += mbuf_read_u8(mb);
+ }
+ else {
+ hdr->picid = v & 0x7f;
+ }
+ }
+
+ if (hdr->l) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->tl0picidx = mbuf_read_u8(mb);
+ }
+
+ if (hdr->t || hdr->k) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->tid = v>>6 & 0x3;
+ hdr->y = v>>5 & 0x1;
+ hdr->keyidx = v & 0x1f;
+ }
+
+ return 0;
+}
+
+
+static inline bool is_keyframe(struct mbuf *mb)
+{
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ if (mb->buf[mb->pos] & 0x01)
+ return false;
+
+ return true;
+}
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+int vp8_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t *img;
+ struct hdr hdr;
+ int err, i;
+
+ if (!vds || !frame || !intra || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ err = hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+#if 0
+ debug("vp8: header: x=%u noref=%u start=%u partid=%u "
+ "i=%u l=%u t=%u k=%u "
+ "picid=%u tl0picidx=%u tid=%u y=%u keyidx=%u\n",
+ hdr.x, hdr.noref, hdr.start, hdr.partid,
+ hdr.i, hdr.l, hdr.t, hdr.k,
+ hdr.picid, hdr.tl0picidx, hdr.tid, hdr.y, hdr.keyidx);
+#endif
+
+ if (hdr.start && hdr.partid == 0) {
+
+ if (is_keyframe(mb))
+ *intra = true;
+
+ mbuf_rewind(vds->mb);
+ vds->started = true;
+ }
+ else {
+ if (!vds->started)
+ return 0;
+
+ if (seq_diff(vds->seq, seq) != 1) {
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+ return 0;
+ }
+ }
+
+ vds->seq = seq;
+
+ err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (vds->mb->end > DECODE_MAXSZ) {
+ warning("vp8: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ res = vpx_codec_decode(&vds->ctx, vds->mb->buf,
+ (unsigned int)vds->mb->end, NULL, 1);
+ if (res) {
+ debug("vp8: decode error: %s\n", vpx_codec_err_to_string(res));
+ err = EPROTO;
+ goto out;
+ }
+
+ img = vpx_codec_get_frame(&vds->ctx, &iter);
+ if (!img) {
+ debug("vp8: no picture\n");
+ goto out;
+ }
+
+ if (img->fmt != VPX_IMG_FMT_I420) {
+ warning("vp8: bad pixel format (%i)\n", img->fmt);
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = img->planes[i];
+ frame->linesize[i] = img->stride[i];
+ }
+
+ frame->size.w = img->d_w;
+ frame->size.h = img->d_h;
+ frame->fmt = VID_FMT_YUV420P;
+
+ out:
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+
+ return err;
+}
diff --git a/modules/vp8/encode.c b/modules/vp8/encode.c
new file mode 100644
index 0000000..83f136b
--- /dev/null
+++ b/modules/vp8/encode.c
@@ -0,0 +1,272 @@
+/**
+ * @file vp8/encode.c VP8 Encode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_encoder.h>
+#include <vpx/vp8cx.h>
+#include "vp8.h"
+
+
+enum {
+ HDR_SIZE = 4,
+};
+
+
+struct videnc_state {
+ vpx_codec_ctx_t ctx;
+ struct vidsz size;
+ vpx_codec_pts_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ bool ctxup;
+ uint16_t picid;
+ videnc_packet_h *pkth;
+ void *arg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *ves = arg;
+
+ if (ves->ctxup)
+ vpx_codec_destroy(&ves->ctx);
+}
+
+
+int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ const struct vp8_vidcodec *vp8 = (struct vp8_vidcodec *)vc;
+ struct videnc_state *ves;
+ uint32_t max_fs;
+ (void)vp8;
+
+ if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1))
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ ves->picid = rand_u16();
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->ctxup && (ves->bitrate != prm->bitrate ||
+ ves->fps != prm->fps)) {
+
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ max_fs = vp8_max_fs(fmtp);
+ if (max_fs > 0)
+ prm->max_fs = max_fs * 256;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *ves, const struct vidsz *size)
+{
+ vpx_codec_enc_cfg_t cfg;
+ vpx_codec_err_t res;
+ vpx_codec_flags_t flags = 0;
+
+ res = vpx_codec_enc_config_default(&vpx_codec_vp8_cx_algo, &cfg, 0);
+ if (res)
+ return EPROTO;
+
+ cfg.g_profile = 2;
+ cfg.g_w = size->w;
+ cfg.g_h = size->h;
+ cfg.g_timebase.num = 1;
+ cfg.g_timebase.den = ves->fps;
+#ifdef VPX_ERROR_RESILIENT_DEFAULT
+ cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
+#endif
+ cfg.g_pass = VPX_RC_ONE_PASS;
+ cfg.g_lag_in_frames = 0;
+ cfg.rc_end_usage = VPX_VBR;
+ cfg.rc_target_bitrate = ves->bitrate;
+ cfg.kf_mode = VPX_KF_AUTO;
+
+ if (ves->ctxup) {
+ debug("vp8: re-opening encoder\n");
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+
+#ifdef VPX_CODEC_USE_OUTPUT_PARTITION
+ flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
+#endif
+
+ res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp8_cx_algo, &cfg,
+ flags);
+ if (res) {
+ warning("vp8: enc init: %s\n", vpx_codec_err_to_string(res));
+ return EPROTO;
+ }
+
+ ves->ctxup = true;
+
+ res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 16);
+ if (res) {
+ warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+
+ res = vpx_codec_control(&ves->ctx, VP8E_SET_NOISE_SENSITIVITY, 0);
+ if (res) {
+ warning("vp8: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+
+ return 0;
+}
+
+
+static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool noref, bool start,
+ uint8_t partid, uint16_t picid)
+{
+ hdr[0] = 1<<7 | noref<<5 | start<<4 | (partid & 0x7);
+ hdr[1] = 1<<7;
+ hdr[2] = 1<<7 | (picid>>8 & 0x7f);
+ hdr[3] = picid & 0xff;
+}
+
+
+static inline int packetize(bool marker, const uint8_t *buf, size_t len,
+ size_t maxlen, bool noref, uint8_t partid,
+ uint16_t picid, uint32_t rtp_ts,
+ videnc_packet_h *pkth, void *arg)
+{
+ uint8_t hdr[HDR_SIZE];
+ bool start = true;
+ int err = 0;
+
+ maxlen -= sizeof(hdr);
+
+ while (len > maxlen) {
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(false, rtp_ts, hdr, sizeof(hdr), buf, maxlen,
+ arg);
+
+ buf += maxlen;
+ len -= maxlen;
+ start = false;
+ }
+
+ hdr_encode(hdr, noref, start, partid, picid);
+
+ err |= pkth(marker, rtp_ts, hdr, sizeof(hdr), buf, len, arg);
+
+ return err;
+}
+
+
+int vp8_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame)
+{
+ vpx_enc_frame_flags_t flags = 0;
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t img;
+ int err, i;
+
+ if (!ves || !frame || frame->fmt != VID_FMT_YUV420P)
+ return EINVAL;
+
+ if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) {
+
+ err = open_encoder(ves, &frame->size);
+ if (err)
+ return err;
+
+ ves->size = frame->size;
+ }
+
+ if (update) {
+ /* debug("vp8: picture update\n"); */
+ flags |= VPX_EFLAG_FORCE_KF;
+ }
+
+ memset(&img, 0, sizeof(img));
+
+ img.fmt = VPX_IMG_FMT_I420;
+ img.w = img.d_w = frame->size.w;
+ img.h = img.d_h = frame->size.h;
+
+ for (i=0; i<4; i++) {
+ img.stride[i] = frame->linesize[i];
+ img.planes[i] = frame->data[i];
+ }
+
+ res = vpx_codec_encode(&ves->ctx, &img, ves->pts++, 1,
+ flags, VPX_DL_REALTIME);
+ if (res) {
+ warning("vp8: enc error: %s\n", vpx_codec_err_to_string(res));
+ return ENOMEM;
+ }
+
+ ++ves->picid;
+
+ for (;;) {
+ bool keyframe = false, marker = true;
+ const vpx_codec_cx_pkt_t *pkt;
+ uint8_t partid = 0;
+ uint32_t ts;
+
+ pkt = vpx_codec_get_cx_data(&ves->ctx, &iter);
+ if (!pkt)
+ break;
+
+ if (pkt->kind != VPX_CODEC_CX_FRAME_PKT)
+ continue;
+
+ if (pkt->data.frame.flags & VPX_FRAME_IS_KEY)
+ keyframe = true;
+
+#ifdef VPX_FRAME_IS_FRAGMENT
+ if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT)
+ marker = false;
+
+ if (pkt->data.frame.partition_id >= 0)
+ partid = pkt->data.frame.partition_id;
+#endif
+
+ ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps);
+
+ err = packetize(marker,
+ pkt->data.frame.buf,
+ pkt->data.frame.sz,
+ ves->pktsize, !keyframe, partid, ves->picid,
+ ts,
+ ves->pkth, ves->arg);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
diff --git a/modules/vp8/module.mk b/modules/vp8/module.mk
new file mode 100644
index 0000000..4142af5
--- /dev/null
+++ b/modules/vp8/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vp8
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += vp8.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_LFLAGS += -lvpx
+
+include mk/mod.mk
diff --git a/modules/vp8/sdp.c b/modules/vp8/sdp.c
new file mode 100644
index 0000000..8d2ecca
--- /dev/null
+++ b/modules/vp8/sdp.c
@@ -0,0 +1,39 @@
+/**
+ * @file vp8/sdp.c VP8 SDP Functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "vp8.h"
+
+
+uint32_t vp8_max_fs(const char *fmtp)
+{
+ struct pl pl, max_fs;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "max-fs", &max_fs))
+ return pl_u32(&max_fs);
+
+ return 0;
+}
+
+
+int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ const struct vp8_vidcodec *vp8 = arg;
+ (void)offer;
+
+ if (!mb || !fmt || !vp8 || !vp8->max_fs)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n",
+ fmt->id, vp8->max_fs);
+}
diff --git a/modules/vp8/vp8.c b/modules/vp8/vp8.c
new file mode 100644
index 0000000..18937d3
--- /dev/null
+++ b/modules/vp8/vp8.c
@@ -0,0 +1,62 @@
+/**
+ * @file vp8.c VP8 Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vp8.h"
+
+
+/**
+ * @defgroup vp8 vp8
+ *
+ * The VP8 video codec
+ *
+ * This module implements the VP8 video codec that is compatible
+ * with the WebRTC standard.
+ *
+ * References:
+ *
+ * http://www.webmproject.org/
+ *
+ * https://tools.ietf.org/html/rfc7741
+ */
+
+
+static struct vp8_vidcodec vp8 = {
+ .vc = {
+ .name = "VP8",
+ .encupdh = vp8_encode_update,
+ .ench = vp8_encode,
+ .decupdh = vp8_decode_update,
+ .dech = vp8_decode,
+ .fmtp_ench = vp8_fmtp_enc,
+ },
+ .max_fs = 3600,
+};
+
+
+static int module_init(void)
+{
+ vidcodec_register(baresip_vidcodecl(), (struct vidcodec *)&vp8);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister((struct vidcodec *)&vp8);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vp8) = {
+ "vp8",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/vp8/vp8.h b/modules/vp8/vp8.h
new file mode 100644
index 0000000..c0e262a
--- /dev/null
+++ b/modules/vp8/vp8.h
@@ -0,0 +1,30 @@
+/**
+ * @file vp8.h Private VP8 Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct vp8_vidcodec {
+ struct vidcodec vc;
+ uint32_t max_fs;
+};
+
+/* Encode */
+int vp8_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int vp8_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+
+/* Decode */
+int vp8_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int vp8_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb);
+
+
+/* SDP */
+uint32_t vp8_max_fs(const char *fmtp);
+int vp8_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
diff --git a/modules/vp9/decode.c b/modules/vp9/decode.c
new file mode 100644
index 0000000..f69b240
--- /dev/null
+++ b/modules/vp9/decode.c
@@ -0,0 +1,287 @@
+/**
+ * @file vp9/decode.c VP9 Decode
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_decoder.h>
+#include <vpx/vp8dx.h>
+#include "vp9.h"
+
+
+enum {
+ DECODE_MAXSZ = 524288,
+};
+
+
+struct hdr {
+ /* header: */
+ unsigned i:1; /* I: Picture ID (PID) present */
+ unsigned p:1; /* P: Inter-picture predicted layer frame */
+ unsigned l:1; /* L: Layer indices present */
+ unsigned f:1; /* F: Flexible mode */
+ unsigned b:1; /* B: Start of a layer frame */
+ unsigned e:1; /* E: End of a layer frame */
+ unsigned v:1; /* V: Scalability structure (SS) data present */
+
+ /* extension fields */
+ uint16_t picid;
+};
+
+struct viddec_state {
+ vpx_codec_ctx_t ctx;
+ struct mbuf *mb;
+ bool ctxup;
+ bool started;
+ uint16_t seq;
+
+ unsigned n_frames;
+ size_t n_bytes;
+};
+
+
+static void destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ if (vds->ctxup) {
+ debug("vp9: decoder stats: frames=%u, bytes=%zu\n",
+ vds->n_frames, vds->n_bytes);
+
+ vpx_codec_destroy(&vds->ctx);
+ }
+
+ mem_deref(vds->mb);
+}
+
+
+int vp9_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp)
+{
+ struct viddec_state *vds;
+ vpx_codec_err_t res;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), destructor);
+ if (!vds)
+ return ENOMEM;
+
+ vds->mb = mbuf_alloc(1024);
+ if (!vds->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ res = vpx_codec_dec_init(&vds->ctx, &vpx_codec_vp9_dx_algo, NULL, 0);
+ if (res) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ vds->ctxup = true;
+
+ out:
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static inline int hdr_decode(struct hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->i = v>>7 & 0x1;
+ hdr->p = v>>6 & 0x1;
+ hdr->l = v>>5 & 0x1;
+ hdr->f = v>>4 & 0x1;
+ hdr->b = v>>3 & 0x1;
+ hdr->e = v>>2 & 0x1;
+ hdr->v = v>>1 & 0x1;
+
+ if (hdr->p) {
+ warning("vp9: decode: P-bit not supported\n");
+ return EPROTO;
+ }
+ if (hdr->l) {
+ warning("vp9: decode: L-bit not supported\n");
+ return EPROTO;
+ }
+ if (hdr->f) {
+ warning("vp9: decode: F-bit not supported\n");
+ return EPROTO;
+ }
+ if (hdr->v) {
+ warning("vp9: decode: V-bit not supported\n");
+ return EPROTO;
+ }
+
+ if (hdr->i) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ v = mbuf_read_u8(mb);
+
+ if (v>>7 & 0x1) {
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ hdr->picid = (v & 0x7f)<<8;
+ hdr->picid += mbuf_read_u8(mb);
+ }
+ else {
+ hdr->picid = v & 0x7f;
+ }
+ }
+
+ return 0;
+}
+
+
+static inline bool is_keyframe(const struct mbuf *mb)
+{
+ vpx_codec_stream_info_t si;
+ vpx_codec_err_t ret;
+
+ memset(&si, 0, sizeof(si));
+ si.sz = sizeof(si);
+
+ ret = vpx_codec_peek_stream_info(&vpx_codec_vp9_dx_algo,
+ mbuf_buf(mb),
+ (unsigned int)mbuf_get_left(mb), &si);
+ if (ret != VPX_CODEC_OK)
+ return false;
+
+ return si.is_kf;
+}
+
+
+static inline int16_t seq_diff(uint16_t x, uint16_t y)
+{
+ return (int16_t)(y - x);
+}
+
+
+int vp9_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t *img;
+ struct hdr hdr;
+ int err, i;
+
+ if (!vds || !frame || !intra || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ vds->n_bytes += mbuf_get_left(mb);
+
+ err = hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+#if 0
+ debug("vp9: [%c] header: i=%u start=%u end=%u picid=%u \n",
+ marker ? 'M' : ' ', hdr.i, hdr.b, hdr.e, hdr.picid);
+#endif
+
+ if (hdr.b) {
+
+ if (is_keyframe(mb))
+ *intra = true;
+
+ mbuf_rewind(vds->mb);
+ vds->started = true;
+ }
+ else {
+ if (!vds->started)
+ return 0;
+
+ if (seq_diff(vds->seq, seq) != 1) {
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+ return 0;
+ }
+ }
+
+ vds->seq = seq;
+
+ err = mbuf_write_mem(vds->mb, mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ if (!marker) {
+
+ if (vds->mb->end > DECODE_MAXSZ) {
+ warning("vp9: decode buffer size exceeded\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ return 0;
+ }
+
+ res = vpx_codec_decode(&vds->ctx, vds->mb->buf,
+ (unsigned int)vds->mb->end, NULL, 1);
+ if (res) {
+ debug("vp9: decode error: %s\n", vpx_codec_err_to_string(res));
+ err = EPROTO;
+ goto out;
+ }
+
+ img = vpx_codec_get_frame(&vds->ctx, &iter);
+ if (!img) {
+ debug("vp9: no picture\n");
+ goto out;
+ }
+
+ if (img->fmt != VPX_IMG_FMT_I420) {
+ warning("vp9: bad pixel format (%i)\n", img->fmt);
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = img->planes[i];
+ frame->linesize[i] = img->stride[i];
+ }
+
+ frame->size.w = img->d_w;
+ frame->size.h = img->d_h;
+ frame->fmt = VID_FMT_YUV420P;
+
+ ++vds->n_frames;
+
+ out:
+ mbuf_rewind(vds->mb);
+ vds->started = false;
+
+ return err;
+}
diff --git a/modules/vp9/encode.c b/modules/vp9/encode.c
new file mode 100644
index 0000000..9de4d73
--- /dev/null
+++ b/modules/vp9/encode.c
@@ -0,0 +1,317 @@
+/**
+ * @file vp9/encode.c VP9 Encode
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <vpx/vpx_encoder.h>
+#include <vpx/vp8cx.h>
+#include "vp9.h"
+
+
+enum {
+ HDR_SIZE = 3,
+};
+
+
+struct videnc_state {
+ vpx_codec_ctx_t ctx;
+ struct vidsz size;
+ vpx_codec_pts_t pts;
+ unsigned fps;
+ unsigned bitrate;
+ unsigned pktsize;
+ bool ctxup;
+ uint16_t picid;
+ videnc_packet_h *pkth;
+ void *arg;
+
+ unsigned n_frames;
+ unsigned n_key_frames;
+ size_t n_bytes;
+};
+
+
+static void destructor(void *arg)
+{
+ struct videnc_state *ves = arg;
+
+ if (ves->ctxup) {
+
+ debug("vp9: encoder stats:"
+ " frames=%u, key_frames=%u, bytes=%zu\n",
+ ves->n_frames,
+ ves->n_key_frames,
+ ves->n_bytes);
+
+ vpx_codec_destroy(&ves->ctx);
+ }
+}
+
+
+int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ const struct vp9_vidcodec *vp9 = (struct vp9_vidcodec *)vc;
+ struct videnc_state *ves;
+ uint32_t max_fs;
+ (void)vp9;
+
+ if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1))
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), destructor);
+ if (!ves)
+ return ENOMEM;
+
+ ves->picid = rand_u16();
+
+ *vesp = ves;
+ }
+ else {
+ if (ves->ctxup && (ves->bitrate != prm->bitrate ||
+ ves->fps != prm->fps)) {
+
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+ }
+
+ ves->bitrate = prm->bitrate;
+ ves->pktsize = prm->pktsize;
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ max_fs = vp9_max_fs(fmtp);
+ if (max_fs > 0)
+ prm->max_fs = max_fs * 256;
+
+ return 0;
+}
+
+
+static int open_encoder(struct videnc_state *ves, const struct vidsz *size)
+{
+ vpx_codec_enc_cfg_t cfg;
+ vpx_codec_err_t res;
+
+ res = vpx_codec_enc_config_default(&vpx_codec_vp9_cx_algo, &cfg, 0);
+ if (res)
+ return EPROTO;
+
+ /*
+ Profile 0 = 8 bit yuv420p
+ Profile 1 = 8 bit yuv422/440/444p
+ Profile 2 = 10/12 bit yuv420p
+ Profile 3 = 10/12 bit yuv422/440/444p
+ */
+
+ cfg.g_profile = 0;
+ cfg.g_w = size->w;
+ cfg.g_h = size->h;
+ cfg.g_timebase.num = 1;
+ cfg.g_timebase.den = ves->fps;
+ cfg.rc_target_bitrate = ves->bitrate / 1000;
+ cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
+ cfg.g_pass = VPX_RC_ONE_PASS;
+ cfg.g_lag_in_frames = 0;
+ cfg.rc_end_usage = VPX_VBR;
+ cfg.kf_mode = VPX_KF_AUTO;
+
+ if (ves->ctxup) {
+ debug("vp9: re-opening encoder\n");
+ vpx_codec_destroy(&ves->ctx);
+ ves->ctxup = false;
+ }
+
+ res = vpx_codec_enc_init(&ves->ctx, &vpx_codec_vp9_cx_algo, &cfg,
+ 0);
+ if (res) {
+ warning("vp9: enc init: %s\n", vpx_codec_err_to_string(res));
+ return EPROTO;
+ }
+
+ ves->ctxup = true;
+
+ res = vpx_codec_control(&ves->ctx, VP8E_SET_CPUUSED, 8);
+ if (res) {
+ warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+#ifdef VP9E_SET_NOISE_SENSITIVITY
+ res = vpx_codec_control(&ves->ctx, VP9E_SET_NOISE_SENSITIVITY, 0);
+ if (res) {
+ warning("vp9: codec ctrl: %s\n", vpx_codec_err_to_string(res));
+ }
+#endif
+
+ info("vp9: encoder opened, picture size %u x %u\n", size->w, size->h);
+
+ return 0;
+}
+
+
+static inline void hdr_encode(uint8_t hdr[HDR_SIZE], bool start, bool end,
+ uint16_t picid)
+{
+ hdr[0] = 1<<7 | start<<3 | end<<2;
+ hdr[1] = 1<<7 | (picid>>8 & 0x7f);
+ hdr[2] = picid & 0xff;
+}
+
+
+static int send_packet(struct videnc_state *ves, bool marker,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len,
+ uint32_t rtp_ts)
+{
+ ves->n_bytes += (hdr_len + pld_len);
+
+ return ves->pkth(marker, rtp_ts, hdr, hdr_len, pld, pld_len,
+ ves->arg);
+}
+
+
+static inline int packetize(struct videnc_state *ves,
+ bool marker, const uint8_t *buf, size_t len,
+ size_t maxlen, uint16_t picid,
+ uint32_t rtp_ts)
+{
+ uint8_t hdr[HDR_SIZE];
+ bool start = true;
+ int err = 0;
+
+ maxlen -= sizeof(hdr);
+
+ while (len > maxlen) {
+
+ hdr_encode(hdr, start, false, picid);
+
+ err |= send_packet(ves, false, hdr, sizeof(hdr), buf, maxlen,
+ rtp_ts);
+
+ buf += maxlen;
+ len -= maxlen;
+ start = false;
+ }
+
+ hdr_encode(hdr, start, true, picid);
+
+ err |= send_packet(ves, marker, hdr, sizeof(hdr), buf, len,
+ rtp_ts);
+
+ return err;
+}
+
+
+int vp9_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame)
+{
+ vpx_enc_frame_flags_t flags = 0;
+ vpx_codec_iter_t iter = NULL;
+ vpx_codec_err_t res;
+ vpx_image_t *img = NULL;
+ vpx_img_fmt_t img_fmt;
+ int err, i;
+
+ if (!ves || !frame)
+ return EINVAL;
+
+ switch (frame->fmt) {
+
+ case VID_FMT_YUV420P:
+ img_fmt = VPX_IMG_FMT_I420;
+ break;
+
+ default:
+ warning("vp9: pixel format not supported (%s)\n",
+ vidfmt_name(frame->fmt));
+ return EINVAL;
+ }
+
+ if (!ves->ctxup || !vidsz_cmp(&ves->size, &frame->size)) {
+
+ err = open_encoder(ves, &frame->size);
+ if (err)
+ return err;
+
+ ves->size = frame->size;
+ }
+
+ ++ves->n_frames;
+
+ if (update) {
+ /* debug("vp9: picture update\n"); */
+ flags |= VPX_EFLAG_FORCE_KF;
+ }
+
+ img = vpx_img_wrap(NULL, img_fmt, frame->size.w, frame->size.h,
+ 16, NULL);
+ if (!img) {
+ warning("vp9: encoder: could not allocate image\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ img->stride[i] = frame->linesize[i];
+ img->planes[i] = frame->data[i];
+ }
+
+ res = vpx_codec_encode(&ves->ctx, img, ves->pts++, 1,
+ flags, VPX_DL_REALTIME);
+ if (res) {
+ warning("vp9: enc error: %s\n", vpx_codec_err_to_string(res));
+ err = ENOMEM;
+ goto out;
+ }
+
+ ++ves->picid;
+
+ for (;;) {
+ bool marker = true;
+ const vpx_codec_cx_pkt_t *pkt;
+ uint32_t ts;
+
+ pkt = vpx_codec_get_cx_data(&ves->ctx, &iter);
+ if (!pkt)
+ break;
+
+ if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
+ continue;
+ }
+
+ if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) {
+ ++ves->n_key_frames;
+ }
+
+ if (pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT)
+ marker = false;
+
+ ts = video_calc_rtp_timestamp(pkt->data.frame.pts, ves->fps);
+
+ err = packetize(ves,
+ marker,
+ pkt->data.frame.buf,
+ pkt->data.frame.sz,
+ ves->pktsize, ves->picid,
+ ts);
+ if (err)
+ return err;
+ }
+
+ out:
+ if (img)
+ vpx_img_free(img);
+
+ return err;
+}
diff --git a/modules/vp9/module.mk b/modules/vp9/module.mk
new file mode 100644
index 0000000..41879a2
--- /dev/null
+++ b/modules/vp9/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vp9
+$(MOD)_SRCS += decode.c
+$(MOD)_SRCS += encode.c
+$(MOD)_SRCS += vp9.c
+$(MOD)_SRCS += sdp.c
+$(MOD)_LFLAGS += -lvpx
+
+include mk/mod.mk
diff --git a/modules/vp9/sdp.c b/modules/vp9/sdp.c
new file mode 100644
index 0000000..25a3d12
--- /dev/null
+++ b/modules/vp9/sdp.c
@@ -0,0 +1,39 @@
+/**
+ * @file vp9/sdp.c VP9 SDP Functions
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "vp9.h"
+
+
+uint32_t vp9_max_fs(const char *fmtp)
+{
+ struct pl pl, max_fs;
+
+ if (!fmtp)
+ return 0;
+
+ pl_set_str(&pl, fmtp);
+
+ if (fmt_param_get(&pl, "max-fs", &max_fs))
+ return pl_u32(&max_fs);
+
+ return 0;
+}
+
+
+int vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg)
+{
+ const struct vp9_vidcodec *vp9 = arg;
+ (void)offer;
+
+ if (!mb || !fmt || !vp9 || !vp9->max_fs)
+ return 0;
+
+ return mbuf_printf(mb, "a=fmtp:%s max-fs=%u\r\n",
+ fmt->id, vp9->max_fs);
+}
diff --git a/modules/vp9/vp9.c b/modules/vp9/vp9.c
new file mode 100644
index 0000000..44a7481
--- /dev/null
+++ b/modules/vp9/vp9.c
@@ -0,0 +1,64 @@
+/**
+ * @file vp9.c VP9 video codec
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "vp9.h"
+
+
+/**
+ * @defgroup vp9 vp9
+ *
+ * The VP9 video codec
+ *
+ * This module implements the VP9 video codec that is compatible
+ * with the WebRTC standard.
+ *
+ * Libvpx version 1.3.0 or later is required.
+ *
+ *
+ * References:
+ *
+ * http://www.webmproject.org/
+ *
+ * draft-ietf-payload-vp9-02
+ */
+
+
+static struct vp9_vidcodec vp9 = {
+ .vc = {
+ .name = "VP9",
+ .encupdh = vp9_encode_update,
+ .ench = vp9_encode,
+ .decupdh = vp9_decode_update,
+ .dech = vp9_decode,
+ .fmtp_ench = vp9_fmtp_enc,
+ },
+ .max_fs = 3600
+};
+
+
+static int module_init(void)
+{
+ vidcodec_register(baresip_vidcodecl(), (struct vidcodec *)&vp9);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ vidcodec_unregister((struct vidcodec *)&vp9);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vp9) = {
+ "vp9",
+ "codec",
+ module_init,
+ module_close
+};
diff --git a/modules/vp9/vp9.h b/modules/vp9/vp9.h
new file mode 100644
index 0000000..53899a1
--- /dev/null
+++ b/modules/vp9/vp9.h
@@ -0,0 +1,30 @@
+/**
+ * @file vp9.h Private VP9 Interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+struct vp9_vidcodec {
+ struct vidcodec vc;
+ uint32_t max_fs;
+};
+
+/* Encode */
+int vp9_encode_update(struct videnc_state **vesp, const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg);
+int vp9_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame);
+
+
+/* Decode */
+int vp9_decode_update(struct viddec_state **vdsp, const struct vidcodec *vc,
+ const char *fmtp);
+int vp9_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb);
+
+
+/* SDP */
+uint32_t vp9_max_fs(const char *fmtp);
+int vp9_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *arg);
diff --git a/modules/vumeter/module.mk b/modules/vumeter/module.mk
new file mode 100644
index 0000000..caa8605
--- /dev/null
+++ b/modules/vumeter/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := vumeter
+$(MOD)_SRCS += vumeter.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/vumeter/vumeter.c b/modules/vumeter/vumeter.c
new file mode 100644
index 0000000..5b2c33f
--- /dev/null
+++ b/modules/vumeter/vumeter.c
@@ -0,0 +1,203 @@
+/**
+ * @file vumeter.c VU-meter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup vumeter vumeter
+ *
+ * Simple ASCII VU-meter for the audio-signal.
+ *
+ * The Volume unit (VU) meter module takes the audio-signal as input
+ * and prints a simple ASCII-art bar for the recording and playback levels.
+ * It is using the aufilt API to get the audio samples.
+ */
+
+
+struct vumeter_enc {
+ struct aufilt_enc_st af; /* inheritance */
+ struct tmr tmr;
+ double avg_rec;
+ volatile bool started;
+};
+
+struct vumeter_dec {
+ struct aufilt_dec_st af; /* inheritance */
+ struct tmr tmr;
+ double avg_play;
+ volatile bool started;
+};
+
+
+static void enc_destructor(void *arg)
+{
+ struct vumeter_enc *st = arg;
+
+ list_unlink(&st->af.le);
+ tmr_cancel(&st->tmr);
+}
+
+
+static void dec_destructor(void *arg)
+{
+ struct vumeter_dec *st = arg;
+
+ list_unlink(&st->af.le);
+ tmr_cancel(&st->tmr);
+}
+
+
+static int audio_print_vu(struct re_printf *pf, double *level)
+{
+ char buf[16];
+ size_t res;
+ double x;
+
+ x = (*level + -AULEVEL_MIN) / -AULEVEL_MIN;
+
+ res = min(sizeof(buf) * x,
+ sizeof(buf)-1);
+
+ memset(buf, '=', res);
+ buf[res] = '\0';
+
+ return re_hprintf(pf, "[%-16s]", buf);
+}
+
+
+static void print_vumeter(int pos, int color, double value)
+{
+ /* move cursor to a fixed position */
+ re_fprintf(stderr, "\x1b[%dG", pos);
+
+ /* print VU-meter in Nice colors */
+ re_fprintf(stderr, " \x1b[%dm%H\x1b[;m\r",
+ color, audio_print_vu, &value);
+}
+
+
+static void enc_tmr_handler(void *arg)
+{
+ struct vumeter_enc *st = arg;
+
+ tmr_start(&st->tmr, 100, enc_tmr_handler, st);
+
+ if (st->started)
+ print_vumeter(60, 31, st->avg_rec);
+}
+
+
+static void dec_tmr_handler(void *arg)
+{
+ struct vumeter_dec *st = arg;
+
+ tmr_start(&st->tmr, 100, dec_tmr_handler, st);
+
+ if (st->started)
+ print_vumeter(80, 32, st->avg_play);
+}
+
+
+static int encode_update(struct aufilt_enc_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_enc *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), enc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ tmr_start(&st->tmr, 100, enc_tmr_handler, st);
+
+ *stp = (struct aufilt_enc_st *)st;
+
+ return 0;
+}
+
+
+static int decode_update(struct aufilt_dec_st **stp, void **ctx,
+ const struct aufilt *af, struct aufilt_prm *prm)
+{
+ struct vumeter_dec *st;
+ (void)ctx;
+ (void)prm;
+
+ if (!stp || !af)
+ return EINVAL;
+
+ if (*stp)
+ return 0;
+
+ st = mem_zalloc(sizeof(*st), dec_destructor);
+ if (!st)
+ return ENOMEM;
+
+ tmr_start(&st->tmr, 100, dec_tmr_handler, st);
+
+ *stp = (struct aufilt_dec_st *)st;
+
+ return 0;
+}
+
+
+static int encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_enc *vu = (void *)st;
+
+ vu->avg_rec = aulevel_calc_dbov(sampv, *sampc);
+ vu->started = true;
+
+ return 0;
+}
+
+
+static int decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
+{
+ struct vumeter_dec *vu = (void *)st;
+
+ vu->avg_play = aulevel_calc_dbov(sampv, *sampc);
+ vu->started = true;
+
+ return 0;
+}
+
+
+static struct aufilt vumeter = {
+ LE_INIT, "vumeter", encode_update, encode, decode_update, decode
+};
+
+
+static int module_init(void)
+{
+ aufilt_register(baresip_aufiltl(), &vumeter);
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ aufilt_unregister(&vumeter);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(vumeter) = {
+ "vumeter",
+ "filter",
+ module_init,
+ module_close
+};
diff --git a/modules/wincons/module.mk b/modules/wincons/module.mk
new file mode 100644
index 0000000..aa1746e
--- /dev/null
+++ b/modules/wincons/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := wincons
+$(MOD)_SRCS += wincons.c
+$(MOD)_LFLAGS +=
+
+include mk/mod.mk
diff --git a/modules/wincons/wincons.c b/modules/wincons/wincons.c
new file mode 100644
index 0000000..641ceb0
--- /dev/null
+++ b/modules/wincons/wincons.c
@@ -0,0 +1,217 @@
+/**
+ * @file wincons.c Windows console input
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <winsock2.h>
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup wincons wincons
+ *
+ * User-Interface (UI) module for Windows Console
+ */
+
+
+/** Local constants */
+enum {
+ RELEASE_VAL = 250 /**< Key release value in [ms] */
+};
+
+struct ui_st {
+ struct tmr tmr;
+ struct mqueue *mq;
+ HANDLE hThread;
+ bool run;
+ HANDLE hstdin;
+ DWORD mode;
+};
+
+
+static struct ui_st *wincons;
+
+
+static void destructor(void *arg)
+{
+ struct ui_st *st = arg;
+
+ /* Restore the console to its previous state */
+ if (st->mode)
+ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), st->mode);
+
+ st->run = false;
+ WaitForSingleObject(st->hThread, 5000);
+ CloseHandle(st->hThread);
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->mq);
+}
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ return 1 == fwrite(p, size, 1, stderr) ? 0 : ENOMEM;
+}
+
+
+static void report_key(struct ui_st *ui, char key)
+{
+ static struct re_printf pf_stderr = {print_handler, NULL};
+ (void)ui;
+
+ ui_input_key(baresip_uis(), key, &pf_stderr);
+}
+
+
+static void timeout(void *arg)
+{
+ struct ui_st *st = arg;
+
+ /* Emulate key-release */
+ report_key(st, KEYCODE_REL);
+}
+
+
+static DWORD WINAPI input_thread(LPVOID arg)
+{
+ struct ui_st *st = arg;
+
+ /* Switch to raw mode */
+ SetConsoleMode(st->hstdin, 0);
+
+ while (st->run) {
+
+ INPUT_RECORD buf[4];
+ DWORD i, count = 0;
+
+ ReadConsoleInput(st->hstdin, buf, ARRAY_SIZE(buf), &count);
+
+ for (i=0; i<count; i++) {
+
+ if (buf[i].EventType != KEY_EVENT)
+ continue;
+
+ if (buf[i].Event.KeyEvent.bKeyDown) {
+
+ int ch = buf[i].Event.KeyEvent.uChar.AsciiChar;
+
+ if (ch == '\r')
+ ch = '\n';
+
+ /* Special handling of 'q' (quit) */
+ if (ch == 'q')
+ st->run = false;
+
+ /*
+ * The keys are read from a thread so we have
+ * to send them to the RE main event loop via
+ * a message queue
+ */
+ if (ch)
+ mqueue_push(st->mq, ch, NULL);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static void mqueue_handler(int id, void *data, void *arg)
+{
+ struct ui_st *st = arg;
+ (void)data;
+
+ tmr_start(&st->tmr, RELEASE_VAL, timeout, st);
+ report_key(st, id);
+}
+
+
+static int ui_alloc(struct ui_st **stp)
+{
+ struct ui_st *st;
+ DWORD threadID;
+ int err = 0;
+
+ if (!stp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ tmr_init(&st->tmr);
+
+ err = mqueue_alloc(&st->mq, mqueue_handler, st);
+ if (err)
+ goto out;
+
+ st->hstdin = GetStdHandle(STD_INPUT_HANDLE);
+
+ /* save the current console mode */
+ GetConsoleMode(st->hstdin, &st->mode);
+
+ st->run = true;
+ st->hThread = CreateThread(NULL, 0, input_thread, st, 0, &threadID);
+ if (!st->hThread) {
+ st->run = false;
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int output_handler(const char *str)
+{
+ return print_handler(str, str_len(str), NULL);
+}
+
+
+static struct ui ui_wincons = {
+ LE_INIT,
+ "wincons",
+ output_handler
+};
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = ui_alloc(&wincons);
+ if (err)
+ return err;
+
+ ui_register(baresip_uis(), &ui_wincons);
+
+ return 0;
+}
+
+
+static int module_close(void)
+{
+ ui_unregister(&ui_wincons);
+ wincons = mem_deref(wincons);
+ return 0;
+}
+
+
+const struct mod_export DECL_EXPORTS(wincons) = {
+ "wincons",
+ "ui",
+ module_init,
+ module_close
+};
diff --git a/modules/winwave/module.mk b/modules/winwave/module.mk
new file mode 100644
index 0000000..ba47da0
--- /dev/null
+++ b/modules/winwave/module.mk
@@ -0,0 +1,11 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := winwave
+$(MOD)_SRCS += winwave.c src.c play.c
+$(MOD)_LFLAGS += -lwinmm
+
+include mk/mod.mk
diff --git a/modules/winwave/play.c b/modules/winwave/play.c
new file mode 100644
index 0000000..e987fc7
--- /dev/null
+++ b/modules/winwave/play.c
@@ -0,0 +1,236 @@
+/**
+ * @file winwave/play.c Windows sound driver -- playback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+#define WRITE_BUFFERS 4
+#define INC_WPOS(a) ((a) = (((a) + 1) % WRITE_BUFFERS))
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+ struct dspbuf bufs[WRITE_BUFFERS];
+ int pos;
+ HWAVEOUT waveout;
+ volatile bool rdy;
+ size_t inuse;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+ int i;
+
+ st->wh = NULL;
+
+ /* Mark the device for closing, and wait for all the
+ * buffers to be returned by the driver
+ */
+ st->rdy = false;
+ while (st->inuse > 0)
+ Sleep(50);
+
+ waveOutReset(st->waveout);
+
+ for (i = 0; i < WRITE_BUFFERS; i++) {
+ waveOutUnprepareHeader(st->waveout, &st->bufs[i].wh,
+ sizeof(WAVEHDR));
+ mem_deref(st->bufs[i].mb);
+ }
+
+ waveOutClose(st->waveout);
+}
+
+
+static int dsp_write(struct auplay_st *st)
+{
+ MMRESULT res;
+ WAVEHDR *wh;
+ struct mbuf *mb;
+
+ if (!st->rdy)
+ return EINVAL;
+
+ wh = &st->bufs[st->pos].wh;
+ if (wh->dwFlags & WHDR_PREPARED) {
+ return EINVAL;
+ }
+ mb = st->bufs[st->pos].mb;
+ wh->lpData = (LPSTR)mb->buf;
+
+ if (st->wh) {
+ st->wh((void *)mb->buf, mb->size/2, st->arg);
+ }
+
+ wh->dwBufferLength = mb->size;
+ wh->dwFlags = 0;
+ wh->dwUser = (DWORD_PTR) mb;
+
+ waveOutPrepareHeader(st->waveout, wh, sizeof(*wh));
+
+ INC_WPOS(st->pos);
+
+ res = waveOutWrite(st->waveout, wh, sizeof(*wh));
+ if (res != MMSYSERR_NOERROR)
+ warning("winwave: dsp_write: waveOutWrite: failed: %08x\n",
+ res);
+ else
+ st->inuse++;
+
+ return 0;
+}
+
+
+static void CALLBACK waveOutCallback(HWAVEOUT hwo,
+ UINT uMsg,
+ DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2)
+{
+ struct auplay_st *st = (struct auplay_st *)dwInstance;
+ WAVEHDR *wh = (WAVEHDR *)dwParam1;
+
+ (void)hwo;
+ (void)dwParam2;
+
+ switch (uMsg) {
+
+ case WOM_OPEN:
+ st->rdy = true;
+ break;
+
+ case WOM_DONE:
+ /*LOCK();*/
+ waveOutUnprepareHeader(st->waveout, wh, sizeof(*wh));
+ /*UNLOCK();*/
+ st->inuse--;
+ dsp_write(st);
+ break;
+
+ case WOM_CLOSE:
+ st->rdy = false;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static unsigned int find_dev(const char *name)
+{
+ WAVEOUTCAPS wic;
+ unsigned int i, nInDevices = waveOutGetNumDevs();
+
+ if (!str_isset(name))
+ return WAVE_MAPPER;
+
+ for (i=0; i<nInDevices; i++) {
+ if (waveOutGetDevCaps(i, &wic,
+ sizeof(WAVEOUTCAPS))==MMSYSERR_NOERROR) {
+
+ if (0 == str_cmp(name, wic.szPname)) {
+ return i;
+ }
+ }
+ }
+
+ return WAVE_MAPPER;
+}
+
+
+static int write_stream_open(struct auplay_st *st,
+ const struct auplay_prm *prm,
+ unsigned int dev)
+{
+ WAVEFORMATEX wfmt;
+ MMRESULT res;
+ uint32_t sampc;
+ int i;
+
+ /* Open an audio I/O stream. */
+ st->waveout = NULL;
+ st->pos = 0;
+ st->rdy = false;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ for (i = 0; i < WRITE_BUFFERS; i++) {
+ memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR));
+ st->bufs[i].mb = mbuf_alloc(2 * sampc);
+ }
+
+ wfmt.wFormatTag = WAVE_FORMAT_PCM;
+ wfmt.nChannels = prm->ch;
+ wfmt.nSamplesPerSec = prm->srate;
+ wfmt.wBitsPerSample = 16;
+ wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8;
+ wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
+ wfmt.cbSize = 0;
+
+ res = waveOutOpen(&st->waveout, dev, &wfmt,
+ (DWORD_PTR) waveOutCallback,
+ (DWORD_PTR) st,
+ CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT);
+ if (res != MMSYSERR_NOERROR) {
+ warning("winwave: waveOutOpen: failed %d\n", res);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int winwave_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int i, err;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("winwave: playback: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->wh = wh;
+ st->arg = arg;
+
+ err = write_stream_open(st, prm, find_dev(device));
+ if (err)
+ goto out;
+
+ /* The write runs at 100ms intervals
+ * prepare enough buffers to suite its needs
+ */
+ for (i = 0; i < 5; i++)
+ dsp_write(st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/winwave/src.c b/modules/winwave/src.c
new file mode 100644
index 0000000..6240899
--- /dev/null
+++ b/modules/winwave/src.c
@@ -0,0 +1,226 @@
+/**
+ * @file winwave/src.c Windows sound driver -- source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+#define READ_BUFFERS 4
+#define INC_RPOS(a) ((a) = (((a) + 1) % READ_BUFFERS))
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+ struct dspbuf bufs[READ_BUFFERS];
+ int pos;
+ HWAVEIN wavein;
+ volatile bool rdy;
+ size_t inuse;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+ int i;
+
+ st->rh = NULL;
+
+ waveInStop(st->wavein);
+ waveInReset(st->wavein);
+
+ for (i = 0; i < READ_BUFFERS; i++) {
+ waveInUnprepareHeader(st->wavein, &st->bufs[i].wh,
+ sizeof(WAVEHDR));
+ mem_deref(st->bufs[i].mb);
+ }
+
+ waveInClose(st->wavein);
+}
+
+
+static int add_wave_in(struct ausrc_st *st)
+{
+ struct dspbuf *db = &st->bufs[st->pos];
+ WAVEHDR *wh = &db->wh;
+ MMRESULT res;
+
+ wh->lpData = (LPSTR)db->mb->buf;
+ wh->dwBufferLength = db->mb->size;
+ wh->dwBytesRecorded = 0;
+ wh->dwFlags = 0;
+ wh->dwUser = (DWORD_PTR)db->mb;
+
+ waveInPrepareHeader(st->wavein, wh, sizeof(*wh));
+ res = waveInAddBuffer(st->wavein, wh, sizeof(*wh));
+ if (res != MMSYSERR_NOERROR) {
+ warning("winwave: add_wave_in: waveInAddBuffer fail: %08x\n",
+ res);
+ return ENOMEM;
+ }
+
+ INC_RPOS(st->pos);
+
+ st->inuse++;
+
+ return 0;
+}
+
+
+static void CALLBACK waveInCallback(HWAVEOUT hwo,
+ UINT uMsg,
+ DWORD_PTR dwInstance,
+ DWORD_PTR dwParam1,
+ DWORD_PTR dwParam2)
+{
+ struct ausrc_st *st = (struct ausrc_st *)dwInstance;
+ WAVEHDR *wh = (WAVEHDR *)dwParam1;
+
+ (void)hwo;
+ (void)dwParam2;
+
+ if (!st->rh)
+ return;
+
+ switch (uMsg) {
+
+ case WIM_CLOSE:
+ st->rdy = false;
+ break;
+
+ case WIM_OPEN:
+ st->rdy = true;
+ break;
+
+ case WIM_DATA:
+ if (st->inuse < (READ_BUFFERS-1))
+ add_wave_in(st);
+
+ st->rh((void *)wh->lpData, wh->dwBytesRecorded/2, st->arg);
+
+ waveInUnprepareHeader(st->wavein, wh, sizeof(*wh));
+ st->inuse--;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static unsigned int find_dev(const char *name)
+{
+ WAVEINCAPS wic;
+ unsigned int i, nInDevices = waveInGetNumDevs();
+
+ if (!str_isset(name))
+ return WAVE_MAPPER;
+
+ for (i=0; i<nInDevices; i++) {
+ if (waveInGetDevCaps(i, &wic,
+ sizeof(WAVEINCAPS))==MMSYSERR_NOERROR) {
+
+ if (0 == str_casecmp(name, wic.szPname)) {
+ return i;
+ }
+ }
+ }
+
+ return WAVE_MAPPER;
+}
+
+
+static int read_stream_open(struct ausrc_st *st, const struct ausrc_prm *prm,
+ unsigned int dev)
+{
+ WAVEFORMATEX wfmt;
+ MMRESULT res;
+ uint32_t sampc;
+ int i, err = 0;
+
+ /* Open an audio INPUT stream. */
+ st->wavein = NULL;
+ st->pos = 0;
+ st->rdy = false;
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ for (i = 0; i < READ_BUFFERS; i++) {
+ memset(&st->bufs[i].wh, 0, sizeof(WAVEHDR));
+ st->bufs[i].mb = mbuf_alloc(2 * sampc);
+ if (!st->bufs[i].mb)
+ return ENOMEM;
+ }
+
+ wfmt.wFormatTag = WAVE_FORMAT_PCM;
+ wfmt.nChannels = prm->ch;
+ wfmt.nSamplesPerSec = prm->srate;
+ wfmt.wBitsPerSample = 16;
+ wfmt.nBlockAlign = (prm->ch * wfmt.wBitsPerSample) / 8;
+ wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
+ wfmt.cbSize = 0;
+
+ res = waveInOpen(&st->wavein, dev, &wfmt,
+ (DWORD_PTR) waveInCallback,
+ (DWORD_PTR) st,
+ CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT);
+ if (res != MMSYSERR_NOERROR) {
+ warning("winwave: waveInOpen: failed %d\n", err);
+ return EINVAL;
+ }
+
+ /* Prepare enough IN buffers to suite at least 50ms of data */
+ for (i = 0; i < READ_BUFFERS; i++)
+ err |= add_wave_in(st);
+
+ waveInStart(st->wavein);
+
+ return err;
+}
+
+
+int winwave_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ if (prm->fmt != AUFMT_S16LE) {
+ warning("winwave: source: unsupported sample format (%s)\n",
+ aufmt_name(prm->fmt));
+ return ENOTSUP;
+ }
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->rh = rh;
+ st->arg = arg;
+
+ err = read_stream_open(st, prm, find_dev(device));
+
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
diff --git a/modules/winwave/winwave.c b/modules/winwave/winwave.c
new file mode 100644
index 0000000..469f709
--- /dev/null
+++ b/modules/winwave/winwave.c
@@ -0,0 +1,60 @@
+/**
+ * @file winwave.c Windows sound driver
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <baresip.h>
+#include "winwave.h"
+
+
+/**
+ * @defgroup winwave winwave
+ *
+ * Windows audio driver module
+ *
+ */
+
+
+static struct ausrc *ausrc;
+static struct auplay *auplay;
+
+
+static int ww_init(void)
+{
+ int play_dev_count, src_dev_count;
+ int err;
+
+ play_dev_count = waveOutGetNumDevs();
+ src_dev_count = waveInGetNumDevs();
+
+ info("winwave: output devices: %d, input devices: %d\n",
+ play_dev_count, src_dev_count);
+
+ err = ausrc_register(&ausrc, baresip_ausrcl(),
+ "winwave", winwave_src_alloc);
+ err |= auplay_register(&auplay, baresip_auplayl(),
+ "winwave", winwave_play_alloc);
+
+ return err;
+}
+
+
+static int ww_close(void)
+{
+ ausrc = mem_deref(ausrc);
+ auplay = mem_deref(auplay);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(winwave) = {
+ "winwave",
+ "sound",
+ ww_init,
+ ww_close
+};
diff --git a/modules/winwave/winwave.h b/modules/winwave/winwave.h
new file mode 100644
index 0000000..2a49378
--- /dev/null
+++ b/modules/winwave/winwave.h
@@ -0,0 +1,20 @@
+/**
+ * @file winwave.h Windows sound driver -- internal api
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct dspbuf {
+ WAVEHDR wh;
+ struct mbuf *mb;
+};
+
+
+int winwave_src_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg);
+int winwave_play_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg);
diff --git a/modules/x11/module.mk b/modules/x11/module.mk
new file mode 100644
index 0000000..6ccdb5a
--- /dev/null
+++ b/modules/x11/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := x11
+$(MOD)_SRCS += x11.c
+$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext
+$(MOD)_CFLAGS += -Wno-variadic-macros
+
+include mk/mod.mk
diff --git a/modules/x11/x11.c b/modules/x11/x11.c
new file mode 100644
index 0000000..eafb0d6
--- /dev/null
+++ b/modules/x11/x11.c
@@ -0,0 +1,452 @@
+/**
+ * @file x11.c Video driver for X11
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifndef SOLARIS
+#define _XOPEN_SOURCE 1
+#endif
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+/*
+ * DO_REDIRECT has this program handle all of the window manager operations
+ * and displays a borderless window. That window does not take keyboard
+ * focus - which means the keyboard input to baresip continues. Clicking
+ * on the window allows one to drag the window around.
+ * Blewett
+ */
+#define DO_REDIRECT 1
+
+
+/**
+ * @defgroup x11 x11
+ *
+ * X11 video-display module
+ */
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /**< Inheritance (1st) */
+ struct vidsz size; /**< Current size */
+
+ Display *disp;
+ Window win;
+ GC gc;
+ XImage *image;
+ XShmSegmentInfo shm;
+ bool xshmat;
+ bool internal;
+ enum vidfmt pixfmt;
+ Atom XwinDeleted;
+ int button_is_down;
+ Time last_time;
+};
+
+
+static struct vidisp *vid; /**< X11 Video-display */
+
+static struct {
+ int shm_error;
+ int (*errorh) (Display *, XErrorEvent *);
+} x11;
+
+
+/* NOTE: Global handler */
+static int error_handler(Display *d, XErrorEvent *e)
+{
+ if (e->error_code == BadAccess)
+ x11.shm_error = 1;
+ else if (x11.errorh)
+ return x11.errorh(d, e);
+
+ return 0;
+}
+
+
+static void close_window(struct vidisp_st *st)
+{
+ if (st->gc && st->disp) {
+ XFreeGC(st->disp, st->gc);
+ st->gc = NULL;
+ }
+
+ if (st->xshmat && st->disp) {
+ XShmDetach(st->disp, &st->shm);
+ }
+
+ if (st->shm.shmaddr != (char *)-1) {
+ shmdt(st->shm.shmaddr);
+ st->shm.shmaddr = (char *)-1;
+ }
+
+ if (st->shm.shmid >= 0)
+ shmctl(st->shm.shmid, IPC_RMID, NULL);
+
+ if (st->disp) {
+ if (st->internal && st->win) {
+ XDestroyWindow(st->disp, st->win);
+ st->win = 0;
+ }
+
+ XCloseDisplay(st->disp);
+ st->disp = NULL;
+ }
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+
+ if (st->image) {
+ st->image->data = NULL;
+ XDestroyImage(st->image);
+ }
+
+ close_window(st);
+}
+
+
+static int create_window(struct vidisp_st *st, const struct vidsz *sz)
+{
+#ifdef DO_REDIRECT
+ XSetWindowAttributes attr;
+#endif
+ st->win = XCreateSimpleWindow(st->disp, DefaultRootWindow(st->disp),
+ 0, 0, sz->w, sz->h, 1, 0, 0);
+ if (!st->win) {
+ warning("x11: failed to create X window\n");
+ return ENOMEM;
+ }
+
+#ifdef DO_REDIRECT
+ /*
+ * set override rediect to avoid the "kill window" button
+ * we need to set masks to allow for mouse tracking, etc.
+ * to control the window - making us the window manager
+ */
+ attr.override_redirect = true;
+ attr.event_mask = SubstructureRedirectMask |
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | Button1MotionMask;
+
+ XChangeWindowAttributes(st->disp, st->win,
+ CWOverrideRedirect | CWEventMask , &attr);
+#endif
+ XClearWindow(st->disp, st->win);
+ XMapRaised(st->disp, st->win);
+
+ /*
+ * setup to catch window deletion
+ */
+ st->XwinDeleted = XInternAtom(st->disp, "WM_DELETE_WINDOW", True);
+ XSetWMProtocols(st->disp, st->win, &st->XwinDeleted, 1);
+
+ return 0;
+}
+
+
+static int x11_reset(struct vidisp_st *st, const struct vidsz *sz)
+{
+ XWindowAttributes attrs;
+ XGCValues gcv;
+ size_t bufsz, pixsz;
+ int err = 0;
+
+ if (!XGetWindowAttributes(st->disp, st->win, &attrs)) {
+ warning("x11: cant't get window attributes\n");
+ return EINVAL;
+ }
+
+ switch (attrs.depth) {
+
+ case 24:
+ st->pixfmt = VID_FMT_RGB32;
+ pixsz = 4;
+ break;
+
+ case 16:
+ st->pixfmt = VID_FMT_RGB565;
+ pixsz = 2;
+ break;
+
+ case 15:
+ st->pixfmt = VID_FMT_RGB555;
+ pixsz = 2;
+ break;
+
+ default:
+ warning("x11: colordepth not supported: %d\n", attrs.depth);
+ return ENOSYS;
+ }
+
+ bufsz = sz->w * sz->h * pixsz;
+
+ if (st->image) {
+ XDestroyImage(st->image);
+ st->image = NULL;
+ }
+
+ if (st->xshmat)
+ XShmDetach(st->disp, &st->shm);
+
+ if (st->shm.shmaddr != (char *)-1)
+ shmdt(st->shm.shmaddr);
+
+ if (st->shm.shmid >= 0)
+ shmctl(st->shm.shmid, IPC_RMID, NULL);
+
+ st->shm.shmid = shmget(IPC_PRIVATE, bufsz, IPC_CREAT | 0777);
+ if (st->shm.shmid < 0) {
+ warning("x11: failed to allocate shared memory\n");
+ return ENOMEM;
+ }
+
+ st->shm.shmaddr = shmat(st->shm.shmid, NULL, 0);
+ if (st->shm.shmaddr == (char *)-1) {
+ warning("x11: failed to attach to shared memory\n");
+ return ENOMEM;
+ }
+
+ st->shm.readOnly = true;
+
+ x11.shm_error = 0;
+ x11.errorh = XSetErrorHandler(error_handler);
+
+ if (!XShmAttach(st->disp, &st->shm)) {
+ warning("x11: failed to attach X to shared memory\n");
+ return ENOMEM;
+ }
+
+ XSync(st->disp, False);
+ XSetErrorHandler(x11.errorh);
+
+ if (x11.shm_error)
+ info("x11: shared memory disabled\n");
+ else
+ st->xshmat = true;
+
+ gcv.graphics_exposures = false;
+
+ st->gc = XCreateGC(st->disp, st->win, GCGraphicsExposures, &gcv);
+ if (!st->gc) {
+ warning("x11: failed to create graphics context\n");
+ return ENOMEM;
+ }
+
+ if (st->xshmat) {
+ st->image = XShmCreateImage(st->disp, attrs.visual,
+ attrs.depth, ZPixmap,
+ st->shm.shmaddr, &st->shm,
+ sz->w, sz->h);
+ }
+ else {
+ st->image = XCreateImage(st->disp, attrs.visual,
+ attrs.depth, ZPixmap, 0,
+ st->shm.shmaddr,
+ sz->w, sz->h, 32, 0);
+
+ }
+ if (!st->image) {
+ warning("x11: Failed to create X image\n");
+ return ENOMEM;
+ }
+
+ XResizeWindow(st->disp, st->win, sz->w, sz->h);
+
+ st->size = *sz;
+
+ return err;
+}
+
+
+/* prm->view points to the XWINDOW ID */
+static int alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ int err = 0;
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+ st->shm.shmaddr = (char *)-1;
+
+ st->disp = XOpenDisplay(NULL);
+ if (!st->disp) {
+ warning("x11: could not open X display\n");
+ err = ENODEV;
+ goto out;
+ }
+
+ /* Use provided view, or create our own */
+ if (prm && prm->view)
+ st->win = (Window)prm->view;
+ else
+ st->internal = true;
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ struct vidframe frame_rgb;
+ int err = 0;
+
+ if (!st->disp)
+ return ENODEV;
+
+ /*
+ * check for window delete - without blocking
+ * the switch handles both the override redirect window
+ * and the "standard" window manager managed window.
+ */
+ while (XPending(st->disp)) {
+
+ XEvent e;
+
+ XNextEvent(st->disp, &e);
+
+ switch (e.type) {
+
+ case ClientMessage:
+ if ((Atom) e.xclient.data.l[0] == st->XwinDeleted) {
+
+ info("x11: window deleted\n");
+
+ /*
+ * we have to bail as all of the display
+ * pointers are bad.
+ */
+ close_window(st);
+ return ENODEV;
+ }
+ break;
+
+ case ButtonPress:
+ st->button_is_down = 1;
+ break;
+
+ case ButtonRelease:
+ st->button_is_down = 0;
+ break;
+
+ case MotionNotify:
+ if (st->button_is_down == 0)
+ break;
+ if ((e.xmotion.time - st->last_time) < 32)
+ break;
+
+ XMoveWindow(st->disp, st->win,
+ e.xmotion.x_root - 16,
+ e.xmotion.y_root - 16);
+ st->last_time = e.xmotion.time;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ char capt[256];
+
+ if (st->size.w && st->size.h) {
+ info("x11: reset: %u x %u ---> %u x %u\n",
+ st->size.w, st->size.h,
+ frame->size.w, frame->size.h);
+ }
+
+ if (st->internal && !st->win)
+ err = create_window(st, &frame->size);
+
+ err |= x11_reset(st, &frame->size);
+ if (err)
+ return err;
+
+ if (title) {
+ re_snprintf(capt, sizeof(capt), "%s - %u x %u",
+ title, frame->size.w, frame->size.h);
+ }
+ else {
+ re_snprintf(capt, sizeof(capt), "%u x %u",
+ frame->size.w, frame->size.h);
+ }
+
+ XStoreName(st->disp, st->win, capt);
+ }
+
+ /* Convert from YUV420P to RGB */
+
+ vidframe_init_buf(&frame_rgb, st->pixfmt, &frame->size,
+ (uint8_t *)st->shm.shmaddr);
+
+ vidconv(&frame_rgb, frame, 0);
+
+ /* draw */
+ if (st->xshmat)
+ XShmPutImage(st->disp, st->win, st->gc, st->image,
+ 0, 0, 0, 0, st->size.w, st->size.h, false);
+ else
+ XPutImage(st->disp, st->win, st->gc, st->image,
+ 0, 0, 0, 0, st->size.w, st->size.h);
+
+ XSync(st->disp, false);
+
+ return err;
+}
+
+
+static void hide(struct vidisp_st *st)
+{
+ if (!st)
+ return;
+
+ if (st->win)
+ XLowerWindow(st->disp, st->win);
+}
+
+
+static int module_init(void)
+{
+ return vidisp_register(&vid, baresip_vidispl(),
+ "x11", alloc, NULL, display, hide);
+}
+
+
+static int module_close(void)
+{
+ vid = mem_deref(vid);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(x11) = {
+ "x11",
+ "vidisp",
+ module_init,
+ module_close,
+};
diff --git a/modules/x11grab/module.mk b/modules/x11grab/module.mk
new file mode 100644
index 0000000..e4f2f7d
--- /dev/null
+++ b/modules/x11grab/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := x11grab
+$(MOD)_SRCS += x11grab.c
+$(MOD)_LFLAGS += -L$(SYSROOT)/X11/lib -lX11 -lXext
+$(MOD)_CFLAGS += -Wno-variadic-macros
+
+include mk/mod.mk
diff --git a/modules/x11grab/x11grab.c b/modules/x11grab/x11grab.c
new file mode 100644
index 0000000..d3aa287
--- /dev/null
+++ b/modules/x11grab/x11grab.c
@@ -0,0 +1,223 @@
+/**
+ * @file x11grab.c X11 grabbing video-source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#ifndef SOLARIS
+#define _XOPEN_SOURCE 1
+#endif
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <pthread.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+/**
+ * @defgroup x11grab x11grab
+ *
+ * X11 window-grabbing video-source module
+ *
+ *
+ * XXX: add option to select a specific X window and x,y offset
+ */
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+ Display *disp;
+ XImage *image;
+ pthread_t thread;
+ bool run;
+ int fps;
+ struct vidsz size;
+ enum vidfmt pixfmt;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static int x11grab_open(struct vidsrc_st *st, const struct vidsz *sz)
+{
+ int x = 0, y = 0;
+
+ st->disp = XOpenDisplay(NULL);
+ if (!st->disp) {
+ warning("x11grab: error opening display\n");
+ return ENODEV;
+ }
+
+ st->image = XGetImage(st->disp,
+ RootWindow(st->disp, DefaultScreen(st->disp)),
+ x, y, sz->w, sz->h, AllPlanes, ZPixmap);
+ if (!st->image) {
+ warning("x11grab: error creating Ximage\n");
+ return ENODEV;
+ }
+
+ switch (st->image->bits_per_pixel) {
+
+ case 32:
+ st->pixfmt = VID_FMT_RGB32;
+ break;
+
+ case 16:
+ st->pixfmt = (st->image->green_mask == 0x7e0)
+ ? VID_FMT_RGB565
+ : VID_FMT_RGB555;
+ break;
+
+ default:
+ warning("x11grab: not supported: bpp=%d\n",
+ st->image->bits_per_pixel);
+ return ENOSYS;
+ }
+
+ return 0;
+}
+
+
+static inline uint8_t *x11grab_read(struct vidsrc_st *st)
+{
+ const int x = 0, y = 0;
+ XImage *im;
+
+ im = XGetSubImage(st->disp,
+ RootWindow(st->disp, DefaultScreen(st->disp)),
+ x, y, st->size.w, st->size.h, AllPlanes, ZPixmap,
+ st->image, 0, 0);
+ if (!im)
+ return NULL;
+
+ return (uint8_t *)st->image->data;
+}
+
+
+static void call_frame_handler(struct vidsrc_st *st, uint8_t *buf)
+{
+ struct vidframe frame;
+
+ vidframe_init_buf(&frame, st->pixfmt, &st->size, buf);
+
+ st->frameh(&frame, st->arg);
+}
+
+
+static void *read_thread(void *arg)
+{
+ struct vidsrc_st *st = arg;
+ uint64_t ts = tmr_jiffies();
+ uint8_t *buf;
+
+ while (st->run) {
+
+ if (tmr_jiffies() < ts) {
+ sys_msleep(4);
+ continue;
+ }
+
+ buf = x11grab_read(st);
+ if (!buf)
+ continue;
+
+ ts += (1000/st->fps);
+
+ call_frame_handler(st, buf);
+ }
+
+ return NULL;
+}
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->image)
+ XDestroyImage(st->image);
+
+ if (st->disp)
+ XCloseDisplay(st->disp);
+}
+
+
+static int alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)ctx;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->size = *size;
+ st->fps = prm->fps;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = x11grab_open(st, size);
+ if (err)
+ goto out;
+
+ st->run = true;
+ err = pthread_create(&st->thread, NULL, read_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+static int x11grab_init(void)
+{
+ return vidsrc_register(&vidsrc, baresip_vidsrcl(),
+ "x11grab", alloc, NULL);
+}
+
+
+static int x11grab_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(x11grab) = {
+ "x11grab",
+ "vidsrc",
+ x11grab_init,
+ x11grab_close
+};
diff --git a/modules/zrtp/module.mk b/modules/zrtp/module.mk
new file mode 100644
index 0000000..5670b98
--- /dev/null
+++ b/modules/zrtp/module.mk
@@ -0,0 +1,13 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := zrtp
+$(MOD)_SRCS += zrtp.c
+$(MOD)_LFLAGS += -lzrtp -lbn
+$(MOD)_CFLAGS += -isystem /usr/local/include/libzrtp
+$(MOD)_CFLAGS += -Wno-strict-prototypes
+
+include mk/mod.mk
diff --git a/modules/zrtp/zrtp.c b/modules/zrtp/zrtp.c
new file mode 100644
index 0000000..28ae48f
--- /dev/null
+++ b/modules/zrtp/zrtp.c
@@ -0,0 +1,661 @@
+/**
+ * @file zrtp.c ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include <zrtp.h>
+
+#include <string.h>
+
+/**
+ * @defgroup zrtp zrtp
+ *
+ * ZRTP: Media Path Key Agreement for Unicast Secure RTP
+ *
+ * Experimental support for ZRTP
+ *
+ * See http://tools.ietf.org/html/rfc6189
+ *
+ * Briefly tested with Twinkle 1.4.2 and Jitsi 2.2.4603.9615
+ *
+ * This module is using ZRTP implementation in Freeswitch
+ * https://github.com/juha-h/libzrtp
+ *
+ * Thanks:
+ *
+ * Ingo Feinerer
+ *
+ * Configuration options:
+ *
+ \verbatim
+ zrtp_hash {yes,no} # Enable SDP zrtp-hash (recommended)
+ \endverbatim
+ *
+ */
+
+
+enum {
+ PRESZ = 36 /* Preamble size for TURN/STUN header */
+};
+
+struct menc_sess {
+ zrtp_session_t *zrtp_session;
+ menc_error_h *errorh;
+ void *arg;
+ struct tmr abort_timer;
+ int err;
+};
+
+struct menc_media {
+ struct menc_sess *sess;
+ struct udp_helper *uh_rtp;
+ struct udp_helper *uh_rtcp;
+ struct sa raddr;
+ void *rtpsock;
+ void *rtcpsock;
+ zrtp_stream_t *zrtp_stream;
+};
+
+
+static zrtp_global_t *zrtp_global;
+static zrtp_config_t zrtp_config;
+static zrtp_zid_t zid;
+
+
+/* RFC 6189, section 8.1. */
+static bool use_sig_hash = true;
+
+
+enum pkt_type {
+ PKT_TYPE_UNKNOWN = 0,
+ PKT_TYPE_RTP = 1,
+ PKT_TYPE_RTCP = 2,
+ PKT_TYPE_ZRTP = 4
+};
+
+
+static enum pkt_type get_packet_type(const struct mbuf *mb)
+{
+ uint8_t b, pt;
+ uint32_t magic;
+
+ if (mbuf_get_left(mb) < 8)
+ return PKT_TYPE_UNKNOWN;
+
+ b = mbuf_buf(mb)[0];
+
+ if (127 < b && b < 192) {
+ pt = mbuf_buf(mb)[1] & 0x7f;
+ if (72 <= pt && pt <= 76)
+ return PKT_TYPE_RTCP;
+ else
+ return PKT_TYPE_RTP;
+ }
+ else {
+ memcpy(&magic, &mbuf_buf(mb)[4], 4);
+ magic = ntohl(magic);
+ if (magic == ZRTP_PACKETS_MAGIC)
+ return PKT_TYPE_ZRTP;
+ }
+
+ return PKT_TYPE_UNKNOWN;
+}
+
+
+static void session_destructor(void *arg)
+{
+ struct menc_sess *st = arg;
+
+ tmr_cancel(&st->abort_timer);
+
+ if (st->zrtp_session)
+ zrtp_session_down(st->zrtp_session);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct menc_media *st = arg;
+
+ mem_deref(st->uh_rtp);
+ mem_deref(st->uh_rtcp);
+ mem_deref(st->rtpsock);
+ mem_deref(st->rtcpsock);
+
+ if (st->zrtp_stream)
+ zrtp_stream_stop(st->zrtp_stream);
+}
+
+
+static void abort_timer_h(void *arg)
+{
+ struct menc_sess *sess = arg;
+
+ if (sess->errorh) {
+ sess->errorh(sess->err, sess->arg);
+ sess->errorh = NULL;
+ }
+}
+
+
+static void abort_call(struct menc_sess *sess)
+{
+ if (!sess->err) {
+ sess->err = EPIPE;
+ tmr_start(&sess->abort_timer, 0, abort_timer_h, sess);
+ }
+}
+
+
+static bool drop_packets(const struct menc_media *st)
+{
+ return (st)? st->sess->err != 0 : true;
+}
+
+
+static bool udp_helper_send(int *err, struct sa *dst,
+ struct mbuf *mb, void *arg)
+{
+ struct menc_media *st = arg;
+ unsigned int length;
+ zrtp_status_t s;
+ const char *proto_name = "rtp";
+ enum pkt_type ptype = get_packet_type(mb);
+
+ if (drop_packets(st))
+ return true;
+
+ length = (unsigned int)mbuf_get_left(mb);
+
+ /* only RTP/RTCP packets should be processed */
+ if (ptype == PKT_TYPE_RTCP) {
+ proto_name = "rtcp";
+ s = zrtp_process_rtcp(st->zrtp_stream,
+ (char *)mbuf_buf(mb), &length);
+ }
+ else if (ptype == PKT_TYPE_RTP) {
+ s = zrtp_process_rtp(st->zrtp_stream,
+ (char *)mbuf_buf(mb), &length);
+ }
+ else
+ return false;
+
+ if (s != zrtp_status_ok) {
+
+ if (s == zrtp_status_drop)
+ return true;
+
+ warning("zrtp: send(port=%d): zrtp_process_%s failed"
+ " (status = %d '%s')\n",
+ sa_port(dst), proto_name, s, zrtp_log_status2str(s));
+ return false;
+ }
+
+ /* make sure target buffer is large enough */
+ if (length > mbuf_get_space(mb)) {
+ warning("zrtp: zrtp_process_%s: length > space (%u > %u)\n",
+ proto_name, length, mbuf_get_space(mb));
+ *err = ENOMEM;
+ }
+
+ mb->end = mb->pos + length;
+
+ return false;
+}
+
+
+static bool udp_helper_recv(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct menc_media *st = arg;
+ unsigned int length;
+ zrtp_status_t s;
+ const char *proto_name = "srtp";
+ enum pkt_type ptype = get_packet_type(mb);
+
+ if (drop_packets(st))
+ return true;
+
+ length = (unsigned int)mbuf_get_left(mb);
+
+ if (ptype == PKT_TYPE_RTCP) {
+ proto_name = "srtcp";
+ s = zrtp_process_srtcp(st->zrtp_stream,
+ (char *)mbuf_buf(mb), &length);
+ }
+ else if (ptype == PKT_TYPE_RTP || ptype == PKT_TYPE_ZRTP) {
+ s = zrtp_process_srtp(st->zrtp_stream,
+ (char *)mbuf_buf(mb), &length);
+ }
+ else
+ return false;
+
+ if (s != zrtp_status_ok) {
+
+ if (s == zrtp_status_drop)
+ return true;
+
+ warning("zrtp: recv(port=%d): zrtp_process_%s: %d '%s'\n",
+ sa_port(src), proto_name, s, zrtp_log_status2str(s));
+ return false;
+ }
+
+ mb->end = mb->pos + length;
+
+ return false;
+}
+
+
+static int sig_hash_encode(struct zrtp_stream_t *stream,
+ struct sdp_media *m)
+{
+ char buf[ZRTP_SIGN_ZRTP_HASH_LENGTH + 1];
+ zrtp_status_t s;
+ int err = 0;
+
+ s = zrtp_signaling_hash_get(stream, buf, sizeof(buf));
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_signaling_hash_get: status = %d\n", s);
+ return EINVAL;
+ }
+
+ err = sdp_media_set_lattr(m, true, "zrtp-hash", "%s %s",
+ ZRTP_PROTOCOL_VERSION, buf);
+ if (err) {
+ warning("zrtp: sdp_media_set_lattr: %d\n", err);
+ }
+
+ return err;
+}
+
+
+static void sig_hash_decode(struct zrtp_stream_t *stream,
+ const struct sdp_media *m)
+{
+ const char *attr_val;
+ struct pl major, minor, hash;
+ uint32_t version;
+ int err;
+ zrtp_status_t s;
+
+ attr_val = sdp_media_rattr(m, "zrtp-hash");
+ if (!attr_val)
+ return;
+
+ err = re_regex(attr_val, strlen(attr_val),
+ "[0-9]+.[0-9]2 [0-9a-f]+",
+ &major, &minor, &hash);
+ if (err || hash.l < ZRTP_SIGN_ZRTP_HASH_LENGTH) {
+ warning("zrtp: malformed zrtp-hash attribute, ignoring...\n");
+ return;
+ }
+
+ version = pl_u32(&major) * 100 + pl_u32(&minor);
+ /* more version checks? */
+ if (version < 110) {
+ warning("zrtp: zrtp-hash: version (%d) is too low, "
+ "ignoring...", version);
+ }
+
+ s = zrtp_signaling_hash_set(stream, hash.p, (uint32_t)hash.l);
+ if (s != zrtp_status_ok)
+ warning("zrtp: zrtp_signaling_hash_set: status = %d\n", s);
+}
+
+
+static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp,
+ bool offerer, menc_error_h *errorh, void *arg)
+{
+ struct menc_sess *st;
+ zrtp_status_t s;
+ int err = 0;
+ (void)offerer;
+
+ if (!sessp || !sdp)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), session_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->errorh = errorh;
+ st->arg = arg;
+ st->err = 0;
+ tmr_init(&st->abort_timer);
+
+ s = zrtp_session_init(zrtp_global, NULL, zid,
+ ZRTP_SIGNALING_ROLE_UNKNOWN, &st->zrtp_session);
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_session_init failed (status = %d)\n", s);
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *sessp = st;
+
+ return err;
+}
+
+
+static int media_alloc(struct menc_media **stp, struct menc_sess *sess,
+ struct rtp_sock *rtp,
+ int proto, void *rtpsock, void *rtcpsock,
+ struct sdp_media *sdpm)
+{
+ struct menc_media *st;
+ zrtp_status_t s;
+ int layer = 10; /* above zero */
+ int err = 0;
+
+ if (!stp || !sess || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ st = *stp;
+ if (st)
+ goto start;
+
+ st = mem_zalloc(sizeof(*st), media_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->sess = sess;
+ if (rtpsock) {
+ st->rtpsock = mem_ref(rtpsock);
+ err |= udp_register_helper(&st->uh_rtp, rtpsock, layer,
+ udp_helper_send, udp_helper_recv, st);
+ }
+ if (rtcpsock && (rtcpsock != rtpsock)) {
+ st->rtcpsock = mem_ref(rtcpsock);
+ err |= udp_register_helper(&st->uh_rtcp, rtcpsock, layer,
+ udp_helper_send, udp_helper_recv, st);
+ }
+ if (err)
+ goto out;
+
+ s = zrtp_stream_attach(sess->zrtp_session, &st->zrtp_stream);
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_stream_attach failed (status=%d)\n", s);
+ err = EPROTO;
+ goto out;
+ }
+
+ zrtp_stream_set_userdata(st->zrtp_stream, st);
+
+ if (use_sig_hash) {
+ err = sig_hash_encode(st->zrtp_stream, sdpm);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+ else
+ *stp = st;
+
+ start:
+ if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) {
+ st->raddr = *sdp_media_raddr(sdpm);
+
+ if (use_sig_hash)
+ sig_hash_decode(st->zrtp_stream, sdpm);
+
+ s = zrtp_stream_start(st->zrtp_stream, rtp_sess_ssrc(rtp));
+ if (s != zrtp_status_ok) {
+ warning("zrtp: zrtp_stream_start: status = %d\n", s);
+ }
+ }
+
+ return err;
+}
+
+
+static int on_send_packet(const zrtp_stream_t *stream,
+ char *rtp_packet,
+ unsigned int rtp_packet_length)
+{
+ struct menc_media *st = zrtp_stream_get_userdata(stream);
+ struct mbuf *mb;
+ int err;
+
+ if (drop_packets(st))
+ return zrtp_status_ok;
+
+ if (!sa_isset(&st->raddr, SA_ALL))
+ return zrtp_status_ok;
+
+ mb = mbuf_alloc(PRESZ + rtp_packet_length);
+ if (!mb)
+ return zrtp_status_alloc_fail;
+
+ mb->pos = PRESZ;
+ (void)mbuf_write_mem(mb, (void *)rtp_packet, rtp_packet_length);
+ mb->pos = PRESZ;
+
+ err = udp_send_helper(st->rtpsock, &st->raddr, mb, st->uh_rtp);
+ if (err) {
+ warning("zrtp: udp_send %u bytes (%m)\n",
+ rtp_packet_length, err);
+ }
+
+ mem_deref(mb);
+
+ return zrtp_status_ok;
+}
+
+
+static void on_zrtp_secure(zrtp_stream_t *stream)
+{
+ const struct menc_media *st = zrtp_stream_get_userdata(stream);
+ const struct menc_sess *sess = st->sess;
+ zrtp_session_info_t sess_info;
+
+ zrtp_session_get(sess->zrtp_session, &sess_info);
+ if (!sess_info.sas_is_verified && sess_info.sas_is_ready) {
+ info("zrtp: verify SAS <%s> <%s> for remote peer %w"
+ " (type /zrtp %w to verify)\n",
+ sess_info.sas1.buffer,
+ sess_info.sas2.buffer,
+ sess_info.peer_zid.buffer,
+ (size_t)sess_info.peer_zid.length,
+ sess_info.peer_zid.buffer,
+ (size_t)sess_info.peer_zid.length);
+ }
+ else if (sess_info.sas_is_verified) {
+ info("zrtp: secure session with verified remote peer %w\n",
+ sess_info.peer_zid.buffer,
+ (size_t)sess_info.peer_zid.length);
+ }
+}
+
+
+static void on_zrtp_security_event(zrtp_stream_t *stream,
+ zrtp_security_event_t event)
+{
+ if (event == ZRTP_EVENT_WRONG_SIGNALING_HASH) {
+ const struct menc_media *st = zrtp_stream_get_userdata(stream);
+
+ warning("zrtp: Attack detected!!! Signaling hash from the "
+ "zrtp-hash SDP attribute doesn't match the hash of "
+ "the Hello message. Aborting the call.\n");
+
+ /* As this was called from zrtp_process_xxx(), we need
+ a safe shutdown. */
+ abort_call(st->sess);
+ }
+}
+
+
+static struct menc menc_zrtp = {
+ LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc
+};
+
+
+static int verify_sas(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ (void)pf;
+
+ if (str_isset(carg->prm)) {
+ char rzid[ZRTP_STRING16] = "";
+ zrtp_status_t s;
+ zrtp_string16_t local_zid = ZSTR_INIT_EMPTY(local_zid);
+ zrtp_string16_t remote_zid = ZSTR_INIT_EMPTY(remote_zid);
+
+ zrtp_zstrncpyc(ZSTR_GV(local_zid), (const char*)zid,
+ sizeof(zrtp_zid_t));
+
+ if (str_len(carg->prm) != 24) {
+ warning("zrtp: invalid remote ZID (%s)\n", carg->prm);
+ return EINVAL;
+ }
+
+ (void) str2hex(carg->prm, (int) str_len(carg->prm),
+ rzid, sizeof(rzid));
+ zrtp_zstrncpyc(ZSTR_GV(remote_zid), (const char*)rzid,
+ sizeof(zrtp_zid_t));
+
+ s = zrtp_verified_set(zrtp_global, &local_zid, &remote_zid,
+ true);
+ if (s == zrtp_status_ok)
+ info("zrtp: SAS for peer %s verified\n", carg->prm);
+ else {
+ warning("zrtp: zrtp_verified_set"
+ " failed (status = %d)\n", s);
+ return EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+
+static void zrtp_log(int level, char *data, int len, int offset)
+{
+ (void)offset;
+
+ if (level == 1) {
+ warning("%b\n", data, len);
+ }
+ else if (level == 2) {
+ info("%b\n", data, len);
+ }
+ else {
+ debug("%b\n", data, len);
+ }
+}
+
+
+static const struct cmd cmdv[] = {
+ {"zrtp", 0, CMD_PRM, "Verify ZRTP SAS <remote ZID>", verify_sas },
+};
+
+
+static int module_init(void)
+{
+ zrtp_status_t s;
+ char config_path[256] = "";
+ char zrtp_zid_path[256] = "";
+ FILE *f;
+ int ret, err;
+
+ (void)conf_get_bool(conf_cur(), "zrtp_hash", &use_sig_hash);
+
+ zrtp_log_set_log_engine(zrtp_log);
+
+ zrtp_config_defaults(&zrtp_config);
+
+ str_ncpy(zrtp_config.client_id, "baresip/zrtp",
+ sizeof(zrtp_config.client_id));
+
+ zrtp_config.lic_mode = ZRTP_LICENSE_MODE_UNLIMITED;
+
+ zrtp_config.cb.misc_cb.on_send_packet = on_send_packet;
+ zrtp_config.cb.event_cb.on_zrtp_secure = on_zrtp_secure;
+ zrtp_config.cb.event_cb.on_zrtp_security_event =
+ on_zrtp_security_event;
+
+ err = conf_path_get(config_path, sizeof(config_path));
+ if (err) {
+ warning("zrtp: could not get config path: %m\n", err);
+ return err;
+ }
+ ret = re_snprintf(zrtp_config.def_cache_path.buffer,
+ zrtp_config.def_cache_path.max_length,
+ "%s/zrtp_cache.dat", config_path);
+ if (ret < 0) {
+ warning("zrtp: could not write cache path\n");
+ return ENOMEM;
+ }
+ zrtp_config.def_cache_path.length = ret;
+
+ if (re_snprintf(zrtp_zid_path,
+ sizeof(zrtp_zid_path),
+ "%s/zrtp_zid", config_path) < 0)
+ return ENOMEM;
+ if ((f = fopen(zrtp_zid_path, "rb")) != NULL) {
+ if (fread(zid, sizeof(zid), 1, f) != 1) {
+ if (feof(f) || ferror(f)) {
+ warning("zrtp: invalid zrtp_zid file\n");
+ }
+ }
+ }
+ else if ((f = fopen(zrtp_zid_path, "wb")) != NULL) {
+ rand_bytes(zid, sizeof(zid));
+ if (fwrite(zid, sizeof(zid), 1, f) != 1) {
+ warning("zrtp: zrtp_zid file write failed\n");
+ }
+ info("zrtp: generated new persistent ZID (%s)\n",
+ zrtp_zid_path);
+ }
+ else {
+ err = errno;
+ warning("zrtp: fopen() %s (%m)\n", zrtp_zid_path, err);
+ }
+ if (f)
+ (void) fclose(f);
+
+ s = zrtp_init(&zrtp_config, &zrtp_global);
+ if (zrtp_status_ok != s) {
+ warning("zrtp: zrtp_init() failed (status = %d)\n", s);
+ return ENOSYS;
+ }
+
+ menc_register(baresip_mencl(), &menc_zrtp);
+
+ debug("zrtp: cache_file: %s\n", zrtp_config.def_cache_path.buffer);
+ debug(" zid_file: %s\n", zrtp_zid_path);
+ debug(" zid: %w\n",
+ zid, sizeof(zid));
+
+ return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
+}
+
+
+static int module_close(void)
+{
+ cmd_unregister(baresip_commands(), cmdv);
+ menc_unregister(&menc_zrtp);
+
+ if (zrtp_global) {
+ zrtp_down(zrtp_global);
+ zrtp_global = NULL;
+ }
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(zrtp) = {
+ "zrtp",
+ "menc",
+ module_init,
+ module_close
+};
diff --git a/rpm/baresip.spec b/rpm/baresip.spec
new file mode 100644
index 0000000..de6170f
--- /dev/null
+++ b/rpm/baresip.spec
@@ -0,0 +1,51 @@
+%define name baresip
+%define ver 0.5.7
+%define rel 1
+
+Summary: Modular SIP useragent
+Name: %name
+Version: %ver
+Release: %rel
+License: BSD
+Group: Applications/Internet
+Source0: file://%{name}-%{version}.tar.gz
+URL: http://www.creytiv.com/
+Vendor: Creytiv
+Packager: Alfred E. Heggestad <aeh@db.org>
+BuildRoot: /var/tmp/%{name}-build-root
+
+%description
+Baresip is a portable and modular SIP User-Agent with audio and video support
+
+%prep
+%setup
+
+%build
+make RELEASE=1
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT install \
+%ifarch x86_64
+ LIBDIR=/usr/lib64
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/sbin/chkconfig --add baresip
+/sbin/ldconfig
+
+%files
+%defattr(-,root,root,-)
+%{_bindir}/*
+%{_libdir}/%name/modules/*.so
+/usr/share/%name/*
+%doc
+
+
+%changelog
+* Fri Nov 5 2010 Alfred E. Heggestad <aeh@db.org> -
+- Initial build.
+
diff --git a/share/busy.wav b/share/busy.wav
new file mode 100644
index 0000000..9d0ffec
--- /dev/null
+++ b/share/busy.wav
Binary files differ
diff --git a/share/callwaiting.wav b/share/callwaiting.wav
new file mode 100644
index 0000000..c9bf9b2
--- /dev/null
+++ b/share/callwaiting.wav
Binary files differ
diff --git a/share/error.wav b/share/error.wav
new file mode 100644
index 0000000..483daca
--- /dev/null
+++ b/share/error.wav
Binary files differ
diff --git a/share/logo.png b/share/logo.png
new file mode 100644
index 0000000..cf8a57c
--- /dev/null
+++ b/share/logo.png
Binary files differ
diff --git a/share/message.wav b/share/message.wav
new file mode 100644
index 0000000..bd91094
--- /dev/null
+++ b/share/message.wav
Binary files differ
diff --git a/share/notfound.wav b/share/notfound.wav
new file mode 100644
index 0000000..285c61d
--- /dev/null
+++ b/share/notfound.wav
Binary files differ
diff --git a/share/ring.wav b/share/ring.wav
new file mode 100644
index 0000000..09ec1bd
--- /dev/null
+++ b/share/ring.wav
Binary files differ
diff --git a/share/ringback.wav b/share/ringback.wav
new file mode 100644
index 0000000..d21e20e
--- /dev/null
+++ b/share/ringback.wav
Binary files differ
diff --git a/src/account.c b/src/account.c
new file mode 100644
index 0000000..2a99e58
--- /dev/null
+++ b/src/account.c
@@ -0,0 +1,703 @@
+/**
+ * @file src/account.c User-Agent account
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ REG_INTERVAL = 3600,
+};
+
+
+static void destructor(void *arg)
+{
+ struct account *acc = arg;
+ size_t i;
+
+ list_clear(&acc->aucodecl);
+ list_clear(&acc->vidcodecl);
+ mem_deref(acc->auth_user);
+ mem_deref(acc->auth_pass);
+ for (i=0; i<ARRAY_SIZE(acc->outboundv); i++)
+ mem_deref(acc->outboundv[i]);
+ mem_deref(acc->regq);
+ mem_deref(acc->rtpkeep);
+ mem_deref(acc->sipnat);
+ mem_deref(acc->stun_user);
+ mem_deref(acc->stun_pass);
+ mem_deref(acc->stun_host);
+ mem_deref(acc->mnatid);
+ mem_deref(acc->mencid);
+ mem_deref(acc->aor);
+ mem_deref(acc->dispname);
+ mem_deref(acc->buf);
+}
+
+
+static int param_dstr(char **dstr, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (msg_param_decode(params, name, &pl))
+ return 0;
+
+ return pl_strdup(dstr, &pl);
+}
+
+
+static int param_u32(uint32_t *v, const struct pl *params, const char *name)
+{
+ struct pl pl;
+
+ if (msg_param_decode(params, name, &pl))
+ return 0;
+
+ *v = pl_u32(&pl);
+
+ return 0;
+}
+
+
+/*
+ * Decode STUN parameters, inspired by RFC 7064
+ *
+ * See RFC 3986:
+ *
+ * Use of the format "user:password" in the userinfo field is
+ * deprecated.
+ *
+ */
+static int stunsrv_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl srv, tmp;
+ struct uri uri;
+ int err;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ memset(&uri, 0, sizeof(uri));
+
+ if (0 == msg_param_decode(&aor->params, "stunserver", &srv)) {
+
+ info("using stunserver: '%r'\n", &srv);
+
+ err = uri_decode(&uri, &srv);
+ if (err) {
+ warning("account: %r: decode failed: %m\n", &srv, err);
+ memset(&uri, 0, sizeof(uri));
+ }
+
+ if (0 != pl_strcasecmp(&uri.scheme, "stun")) {
+ warning("account: unknown scheme: %r\n", &uri.scheme);
+ return EINVAL;
+ }
+ }
+
+ err = 0;
+
+ if (0 == msg_param_exists(&aor->params, "stunuser", &tmp))
+ err |= param_dstr(&acc->stun_user, &aor->params, "stunuser");
+ else if (pl_isset(&uri.user))
+ err |= pl_strdup(&acc->stun_user, &uri.user);
+ else
+ err |= pl_strdup(&acc->stun_user, &aor->uri.user);
+
+ if (0 == msg_param_exists(&aor->params, "stunpass", &tmp))
+ err |= param_dstr(&acc->stun_pass, &aor->params, "stunpass");
+ else if (pl_isset(&uri.password))
+ err |= pl_strdup(&acc->stun_pass, &uri.password);
+ else if (acc->auth_pass)
+ err |= str_dup(&acc->stun_pass, acc->auth_pass);
+
+ if (pl_isset(&uri.host))
+ err |= pl_strdup(&acc->stun_host, &uri.host);
+ else
+ err |= pl_strdup(&acc->stun_host, &aor->uri.host);
+
+ acc->stun_port = uri.port;
+
+ return err;
+}
+
+
+/** Decode media parameters */
+static int media_decode(struct account *acc, const struct pl *prm)
+{
+ int err = 0;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ err |= param_dstr(&acc->mencid, prm, "mediaenc");
+ err |= param_dstr(&acc->mnatid, prm, "medianat");
+ err |= param_dstr(&acc->rtpkeep, prm, "rtpkeep" );
+ err |= param_u32(&acc->ptime, prm, "ptime" );
+
+ return err;
+}
+
+
+/* Decode answermode parameter */
+static void answermode_decode(struct account *prm, const struct pl *pl)
+{
+ struct pl amode;
+
+ if (0 == msg_param_decode(pl, "answermode", &amode)) {
+
+ if (0 == pl_strcasecmp(&amode, "manual")) {
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ else if (0 == pl_strcasecmp(&amode, "early")) {
+ prm->answermode = ANSWERMODE_EARLY;
+ }
+ else if (0 == pl_strcasecmp(&amode, "auto")) {
+ prm->answermode = ANSWERMODE_AUTO;
+ }
+ else {
+ warning("account: answermode unknown (%r)\n", &amode);
+ prm->answermode = ANSWERMODE_MANUAL;
+ }
+ }
+}
+
+
+static int csl_parse(struct pl *pl, char *str, size_t sz)
+{
+ struct pl ws = PL_INIT, val, ws2 = PL_INIT, cma = PL_INIT;
+ int err;
+
+ err = re_regex(pl->p, pl->l, "[ \t]*[^, \t]+[ \t]*[,]*",
+ &ws, &val, &ws2, &cma);
+ if (err)
+ return err;
+
+ pl_advance(pl, ws.l + val.l + ws2.l + cma.l);
+
+ (void)pl_strcpy(&val, str, sz);
+
+ return 0;
+}
+
+
+static int audio_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct list *aucodecl = baresip_aucodecl();
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->aucodecl);
+
+ if (0 == msg_param_exists(prm, "audio_codecs", &tmp)) {
+ struct pl acs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (msg_param_decode(prm, "audio_codecs", &acs))
+ return 0;
+
+ while (0 == csl_parse(&acs, cname, sizeof(cname))) {
+ struct aucodec *ac;
+ struct pl pl_cname, pl_srate, pl_ch = PL_INIT;
+ uint32_t srate = 8000;
+ uint8_t ch = 1;
+
+ /* Format: "codec/srate/ch" */
+ if (0 == re_regex(cname, str_len(cname),
+ "[^/]+/[0-9]+[/]*[0-9]*",
+ &pl_cname, &pl_srate,
+ NULL, &pl_ch)) {
+ (void)pl_strcpy(&pl_cname, cname,
+ sizeof(cname));
+ srate = pl_u32(&pl_srate);
+ if (pl_isset(&pl_ch))
+ ch = pl_u32(&pl_ch);
+ }
+
+ ac = (struct aucodec *)aucodec_find(aucodecl,
+ cname, srate, ch);
+ if (!ac) {
+ warning("account: audio codec not found:"
+ " %s/%u/%d\n",
+ cname, srate, ch);
+ continue;
+ }
+
+ /* NOTE: static list with references to aucodec */
+ list_append(&acc->aucodecl, &acc->acv[i++], ac);
+
+ if (i >= ARRAY_SIZE(acc->acv))
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+#ifdef USE_VIDEO
+static int video_codecs_decode(struct account *acc, const struct pl *prm)
+{
+ struct list *vidcodecl = baresip_vidcodecl();
+ struct pl tmp;
+
+ if (!acc || !prm)
+ return EINVAL;
+
+ list_init(&acc->vidcodecl);
+
+ if (0 == msg_param_exists(prm, "video_codecs", &tmp)) {
+ struct pl vcs;
+ char cname[64];
+ unsigned i = 0;
+
+ if (msg_param_decode(prm, "video_codecs", &vcs))
+ return 0;
+
+ while (0 == csl_parse(&vcs, cname, sizeof(cname))) {
+ struct vidcodec *vc;
+
+ vc = (struct vidcodec *)vidcodec_find(vidcodecl,
+ cname, NULL);
+ if (!vc) {
+ warning("account: video codec not found: %s\n",
+ cname);
+ continue;
+ }
+
+ /* NOTE: static list with references to vidcodec */
+ list_append(&acc->vidcodecl, &acc->vcv[i++], vc);
+
+ if (i >= ARRAY_SIZE(acc->vcv))
+ break;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+static int sip_params_decode(struct account *acc, const struct sip_addr *aor)
+{
+ struct pl auth_user;
+ size_t i;
+ int err = 0;
+
+ if (!acc || !aor)
+ return EINVAL;
+
+ acc->regint = REG_INTERVAL + (rand_u32()&0xff);
+ err |= param_u32(&acc->regint, &aor->params, "regint");
+
+ acc->pubint = 0;
+ err |= param_u32(&acc->pubint, &aor->params, "pubint");
+
+ err |= param_dstr(&acc->regq, &aor->params, "regq");
+
+ for (i=0; i<ARRAY_SIZE(acc->outboundv); i++) {
+
+ char expr[16] = "outbound";
+
+ expr[8] = i + 1 + 0x30;
+ expr[9] = '\0';
+
+ err |= param_dstr(&acc->outboundv[i], &aor->params, expr);
+ }
+
+ /* backwards compat */
+ if (!acc->outboundv[0]) {
+ err |= param_dstr(&acc->outboundv[0], &aor->params,
+ "outbound");
+ }
+
+ err |= param_dstr(&acc->sipnat, &aor->params, "sipnat");
+
+ if (0 == msg_param_decode(&aor->params, "auth_user", &auth_user))
+ err |= pl_strdup(&acc->auth_user, &auth_user);
+ else
+ err |= pl_strdup(&acc->auth_user, &aor->uri.user);
+
+ if (pl_isset(&aor->dname))
+ err |= pl_strdup(&acc->dispname, &aor->dname);
+
+ return err;
+}
+
+
+static int encode_uri_user(struct re_printf *pf, const struct uri *uri)
+{
+ struct uri uuri = *uri;
+
+ uuri.password = uuri.params = uuri.headers = pl_null;
+
+ return uri_encode(pf, &uuri);
+}
+
+
+int account_alloc(struct account **accp, const char *sipaddr)
+{
+ struct account *acc;
+ struct pl pl;
+ int err = 0;
+
+ if (!accp || !sipaddr)
+ return EINVAL;
+
+ acc = mem_zalloc(sizeof(*acc), destructor);
+ if (!acc)
+ return ENOMEM;
+
+ err = str_dup(&acc->buf, sipaddr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, acc->buf);
+ err = sip_addr_decode(&acc->laddr, &pl);
+ if (err) {
+ warning("account: error parsing SIP address: '%r'\n", &pl);
+ goto out;
+ }
+
+ acc->luri = acc->laddr.uri;
+ acc->luri.password = pl_null;
+
+ err = re_sdprintf(&acc->aor, "%H", encode_uri_user, &acc->luri);
+ if (err)
+ goto out;
+
+ /* Decode parameters */
+ acc->ptime = 20;
+ err |= sip_params_decode(acc, &acc->laddr);
+ answermode_decode(acc, &acc->laddr.params);
+ err |= audio_codecs_decode(acc, &acc->laddr.params);
+#ifdef USE_VIDEO
+ err |= video_codecs_decode(acc, &acc->laddr.params);
+#endif
+ err |= media_decode(acc, &acc->laddr.params);
+ if (err)
+ goto out;
+
+ /* optional password prompt */
+ if (pl_isset(&acc->laddr.uri.password)) {
+
+ err = re_sdprintf(&acc->auth_pass, "%H",
+ uri_password_unescape,
+ &acc->laddr.uri.password);
+ if (err)
+ goto out;
+ }
+ else if (0 == msg_param_decode(&acc->laddr.params, "auth_pass", &pl)) {
+ err = pl_strdup(&acc->auth_pass, &pl);
+ if (err)
+ goto out;
+ }
+
+ err = stunsrv_decode(acc, &acc->laddr);
+ if (err)
+ goto out;
+
+ if (acc->mnatid) {
+ acc->mnat = mnat_find(baresip_mnatl(), acc->mnatid);
+ if (!acc->mnat) {
+ warning("account: medianat not found: `%s'\n",
+ acc->mnatid);
+ }
+ }
+
+ if (acc->mencid) {
+ acc->menc = menc_find(baresip_mencl(), acc->mencid);
+ if (!acc->menc) {
+ warning("account: mediaenc not found: `%s'\n",
+ acc->mencid);
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(acc);
+ else
+ *accp = acc;
+
+ return err;
+}
+
+
+int account_set_auth_pass(struct account *acc, const char *pass)
+{
+ if (!acc)
+ return EINVAL;
+
+ acc->auth_pass = mem_deref(acc->auth_pass);
+
+ if (pass)
+ return str_dup(&acc->auth_pass, pass);
+
+ return 0;
+}
+
+
+/**
+ * Sets the displayed name. Pass null in dname to disable display name
+ *
+ * @param acc User-Agent account
+ * @param dname Display name (NULL to disable)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_set_display_name(struct account *acc, const char *dname)
+{
+ if (!acc)
+ return EINVAL;
+
+ acc->dispname = mem_deref(acc->dispname);
+
+ if (dname)
+ return str_dup(&acc->dispname, dname);
+
+ return 0;
+}
+
+
+/**
+ * Authenticate a User-Agent (UA)
+ *
+ * @param acc User-Agent account
+ * @param username Pointer to allocated username string
+ * @param password Pointer to allocated password string
+ * @param realm Realm string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int account_auth(const struct account *acc, char **username, char **password,
+ const char *realm)
+{
+ if (!acc)
+ return EINVAL;
+
+ (void)realm;
+
+ *username = mem_ref(acc->auth_user);
+ *password = mem_ref(acc->auth_pass);
+
+ return 0;
+}
+
+
+struct list *account_aucodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->aucodecl))
+ ? (struct list *)&acc->aucodecl : baresip_aucodecl();
+}
+
+
+#ifdef USE_VIDEO
+struct list *account_vidcodecl(const struct account *acc)
+{
+ return (acc && !list_isempty(&acc->vidcodecl))
+ ? (struct list *)&acc->vidcodecl : baresip_vidcodecl();
+}
+#endif
+
+
+struct sip_addr *account_laddr(const struct account *acc)
+{
+ return acc ? (struct sip_addr *)&acc->laddr : NULL;
+}
+
+
+uint32_t account_regint(const struct account *acc)
+{
+ return acc ? acc->regint : 0;
+}
+
+
+uint32_t account_pubint(const struct account *acc)
+{
+ return acc ? acc->pubint : 0;
+}
+
+
+enum answermode account_answermode(const struct account *acc)
+{
+ return acc ? acc->answermode : ANSWERMODE_MANUAL;
+}
+
+
+const char *account_aor(const struct account *acc)
+{
+ return acc ? acc->aor : NULL;
+}
+
+
+/**
+ * Get the authentication username of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Authentication username
+ */
+const char *account_auth_user(const struct account *acc)
+{
+ return acc ? acc->auth_user : NULL;
+}
+
+
+/**
+ * Get the SIP authentication password of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Authentication password
+ */
+const char *account_auth_pass(const struct account *acc)
+{
+ return acc ? acc->auth_pass : NULL;
+}
+
+
+/**
+ * Get the outbound SIP server of an account
+ *
+ * @param acc User-Agent account
+ * @param ix Index starting at zero
+ *
+ * @return Outbound SIP proxy, NULL if not configured
+ */
+const char *account_outbound(const struct account *acc, unsigned ix)
+{
+ if (!acc || ix >= ARRAY_SIZE(acc->outboundv))
+ return NULL;
+
+ return acc->outboundv[ix];
+}
+
+
+/**
+ * Get the audio packet-time (ptime) of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return Packet-time (ptime)
+ */
+uint32_t account_ptime(const struct account *acc)
+{
+ return acc ? acc->ptime : 0;
+}
+
+
+/**
+ * Get the STUN username of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN username
+ */
+const char *account_stun_user(const struct account *acc)
+{
+ return acc ? acc->stun_user : NULL;
+}
+
+
+/**
+ * Get the STUN password of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN password
+ */
+const char *account_stun_pass(const struct account *acc)
+{
+ return acc ? acc->stun_pass : NULL;
+}
+
+
+/**
+ * Get the STUN hostname of an account
+ *
+ * @param acc User-Agent account
+ *
+ * @return STUN hostname
+ */
+const char *account_stun_host(const struct account *acc)
+{
+ return acc ? acc->stun_host : NULL;
+}
+
+
+static const char *answermode_str(enum answermode mode)
+{
+ switch (mode) {
+
+ case ANSWERMODE_MANUAL: return "manual";
+ case ANSWERMODE_EARLY: return "early";
+ case ANSWERMODE_AUTO: return "auto";
+ default: return "???";
+ }
+}
+
+
+int account_debug(struct re_printf *pf, const struct account *acc)
+{
+ struct le *le;
+ size_t i;
+ int err = 0;
+
+ if (!acc)
+ return 0;
+
+ err |= re_hprintf(pf, "\nAccount:\n");
+
+ err |= re_hprintf(pf, " address: %s\n", acc->buf);
+ err |= re_hprintf(pf, " luri: %H\n",
+ uri_encode, &acc->luri);
+ err |= re_hprintf(pf, " aor: %s\n", acc->aor);
+ err |= re_hprintf(pf, " dispname: %s\n", acc->dispname);
+ err |= re_hprintf(pf, " answermode: %s\n",
+ answermode_str(acc->answermode));
+ if (!list_isempty(&acc->aucodecl)) {
+ err |= re_hprintf(pf, " audio_codecs:");
+ for (le = list_head(&acc->aucodecl); le; le = le->next) {
+ const struct aucodec *ac = le->data;
+ err |= re_hprintf(pf, " %s/%u/%u",
+ ac->name, ac->srate, ac->ch);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+ err |= re_hprintf(pf, " auth_user: %s\n", acc->auth_user);
+ err |= re_hprintf(pf, " mediaenc: %s\n",
+ acc->mencid ? acc->mencid : "none");
+ err |= re_hprintf(pf, " medianat: %s\n",
+ acc->mnatid ? acc->mnatid : "none");
+ for (i=0; i<ARRAY_SIZE(acc->outboundv); i++) {
+ if (acc->outboundv[i]) {
+ err |= re_hprintf(pf, " outbound%d: %s\n",
+ i+1, acc->outboundv[i]);
+ }
+ }
+ err |= re_hprintf(pf, " ptime: %u\n", acc->ptime);
+ err |= re_hprintf(pf, " regint: %u\n", acc->regint);
+ err |= re_hprintf(pf, " pubint: %u\n", acc->pubint);
+ err |= re_hprintf(pf, " regq: %s\n", acc->regq);
+ err |= re_hprintf(pf, " rtpkeep: %s\n", acc->rtpkeep);
+ err |= re_hprintf(pf, " sipnat: %s\n", acc->sipnat);
+ err |= re_hprintf(pf, " stunserver: stun:%s@%s:%u\n",
+ acc->stun_user, acc->stun_host, acc->stun_port);
+ if (!list_isempty(&acc->vidcodecl)) {
+ err |= re_hprintf(pf, " video_codecs:");
+ for (le = list_head(&acc->vidcodecl); le; le = le->next) {
+ const struct vidcodec *vc = le->data;
+ err |= re_hprintf(pf, " %s", vc->name);
+ }
+ err |= re_hprintf(pf, "\n");
+ }
+
+ return err;
+}
diff --git a/src/aucodec.c b/src/aucodec.c
new file mode 100644
index 0000000..35076ed
--- /dev/null
+++ b/src/aucodec.c
@@ -0,0 +1,66 @@
+/**
+ * @file aucodec.c Audio Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register an Audio Codec
+ *
+ * @param aucodecl List of audio-codecs
+ * @param ac Audio Codec object
+ */
+void aucodec_register(struct list *aucodecl, struct aucodec *ac)
+{
+ if (!aucodecl || !ac)
+ return;
+
+ list_append(aucodecl, &ac->le, ac);
+
+ info("aucodec: %s/%u/%u\n", ac->name, ac->srate, ac->ch);
+}
+
+
+/**
+ * Unregister an Audio Codec
+ *
+ * @param ac Audio Codec object
+ */
+void aucodec_unregister(struct aucodec *ac)
+{
+ if (!ac)
+ return;
+
+ list_unlink(&ac->le);
+}
+
+
+const struct aucodec *aucodec_find(const struct list *aucodecl,
+ const char *name, uint32_t srate,
+ uint8_t ch)
+{
+ struct le *le;
+
+ for (le=list_head(aucodecl); le; le=le->next) {
+
+ struct aucodec *ac = le->data;
+
+ if (name && 0 != str_casecmp(name, ac->name))
+ continue;
+
+ if (srate && srate != ac->srate)
+ continue;
+
+ if (ch && ch != ac->ch)
+ continue;
+
+ return ac;
+ }
+
+ return NULL;
+}
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..17eb5f6
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,2081 @@
+/**
+ * @file src/audio.c Audio stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ * \ref GenericAudioStream
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <string.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0x000a0d10
+#include "magic.h"
+
+
+/**
+ * \page GenericAudioStream Generic Audio Stream
+ *
+ * Implements a generic audio stream. The application can allocate multiple
+ * instances of a audio stream, mapping it to a particular SDP media line.
+ * The audio object has a DSP sound card sink and source, and an audio encoder
+ * and decoder. A particular audio object is mapped to a generic media
+ * stream object. Each audio channel has an optional audio filtering chain.
+ *
+ *<pre>
+ * write read
+ * | /|\
+ * \|/ |
+ * .------. .---------. .-------.
+ * |filter|<--| audio |--->|encoder|
+ * '------' | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .------. .-----.
+ * |auplay| |ausrc|
+ * '------' '-----'
+ *</pre>
+ */
+
+enum {
+ AUDIO_SAMPSZ = 3*1920 /* Max samples, 48000Hz 2ch at 60ms */
+};
+
+
+/**
+ * Audio transmit/encoder
+ *
+ *
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .-------. .-------. .--------. .--------. .--------.
+ | | | | | | | | | | |
+ |O-->| ausrc |-->| aubuf |-->| resamp |-->| aufilt |-->| encode |---> RTP
+ | | | | | | | | | | |
+ ' '-------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ *
+ */
+struct autx {
+ struct ausrc_st *ausrc; /**< Audio Source */
+ struct ausrc_prm ausrc_prm; /**< Audio Source parameters */
+ const struct aucodec *ac; /**< Current audio encoder */
+ struct auenc_state *enc; /**< Audio encoder state (optional) */
+ struct aubuf *aubuf; /**< Packetize outgoing stream */
+ size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */
+ volatile bool aubuf_started;
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in encoding order */
+ struct mbuf *mb; /**< Buffer for outgoing RTP packets */
+ char device[64]; /**< Audio source device name */
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for sending */
+ uint64_t ts_ext; /**< Ext. Timestamp for outgoing RTP */
+ uint32_t ts_base;
+ uint32_t ts_tel; /**< Timestamp for Telephony Events */
+ size_t psize; /**< Packet size for sending */
+ bool marker; /**< Marker bit for outgoing RTP */
+ bool muted; /**< Audio source is muted */
+ int cur_key; /**< Currently transmitted event */
+ enum aufmt src_fmt;
+ bool need_conv;
+
+ struct {
+ uint64_t aubuf_overrun;
+ uint64_t aubuf_underrun;
+ } stats;
+
+#ifdef HAVE_PTHREAD
+ union {
+ struct {
+ pthread_t tid;/**< Audio transmit thread */
+ bool run; /**< Audio transmit thread running */
+ } thr;
+ } u;
+#endif
+};
+
+
+/**
+ * Audio receive/decoder
+ *
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .--------. .-------. .--------. .--------. .--------.
+ |\ | | | | | | | | | |
+ | |<--| auplay |<--| aubuf |<--| resamp |<--| aufilt |<--| decode |<--- RTP
+ |/ | | | | | | | | | |
+ '--------' '-------' '--------' '--------' '--------'
+
+ \endverbatim
+ */
+struct aurx {
+ struct auplay_st *auplay; /**< Audio Player */
+ struct auplay_prm auplay_prm; /**< Audio Player parameters */
+ const struct aucodec *ac; /**< Current audio decoder */
+ struct audec_state *dec; /**< Audio decoder state (optional) */
+ struct aubuf *aubuf; /**< Incoming audio buffer */
+ size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */
+ volatile bool aubuf_started;
+ struct auresamp resamp; /**< Optional resampler for DSP */
+ struct list filtl; /**< Audio filters in decoding order */
+ char device[64]; /**< Audio player device name */
+ int16_t *sampv; /**< Sample buffer */
+ int16_t *sampv_rs; /**< Sample buffer for resampler */
+ uint32_t ptime; /**< Packet time for receiving */
+ int pt; /**< Payload type for incoming RTP */
+ double level_last;
+ bool level_set;
+ enum aufmt play_fmt;
+ bool need_conv;
+ struct timestamp_recv ts_recv;
+ uint64_t n_discard;
+
+ struct {
+ uint64_t aubuf_overrun;
+ uint64_t aubuf_underrun;
+ } stats;
+};
+
+
+/** Generic Audio stream */
+struct audio {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct autx tx; /**< Transmit */
+ struct aurx rx; /**< Receive */
+ struct stream *strm; /**< Generic media stream */
+ struct telev *telev; /**< Telephony events */
+ struct config_audio cfg; /**< Audio configuration */
+ bool started; /**< Stream is started flag */
+ bool level_enabled; /**< Audio level RTP ext. enabled */
+ unsigned extmap_aulevel; /**< ID Range 1-14 inclusive */
+ audio_event_h *eventh; /**< Event handler */
+ audio_err_h *errh; /**< Audio error handler */
+ void *arg; /**< Handler argument */
+};
+
+
+/* RFC 6464 */
+static const char *uri_aulevel = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+
+
+static double audio_calc_seconds(uint64_t rtp_ts, uint32_t clock_rate)
+{
+ double timestamp;
+
+ /* convert from RTP clockrate to seconds */
+ timestamp = (double)rtp_ts / (double)clock_rate;
+
+ return timestamp;
+}
+
+
+static double autx_calc_seconds(const struct autx *autx)
+{
+ uint64_t dur;
+
+ if (!autx->ac)
+ return .0;
+
+ dur = autx->ts_ext - autx->ts_base;
+
+ return audio_calc_seconds(dur, autx->ac->crate);
+}
+
+
+static double aurx_calc_seconds(const struct aurx *aurx)
+{
+ uint64_t dur;
+
+ if (!aurx->ac)
+ return .0;
+
+ dur = timestamp_duration(&aurx->ts_recv);
+
+ return audio_calc_seconds(dur, aurx->ac->crate);
+}
+
+
+static void stop_tx(struct autx *tx, struct audio *a)
+{
+ if (!tx || !a)
+ return;
+
+ switch (a->cfg.txmode) {
+
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ if (tx->u.thr.run) {
+ tx->u.thr.run = false;
+ pthread_join(tx->u.thr.tid, NULL);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+
+ /* audio source must be stopped first */
+ tx->ausrc = mem_deref(tx->ausrc);
+ tx->aubuf = mem_deref(tx->aubuf);
+
+ list_flush(&tx->filtl);
+}
+
+
+static void stop_rx(struct aurx *rx)
+{
+ if (!rx)
+ return;
+
+ /* audio player must be stopped first */
+ rx->auplay = mem_deref(rx->auplay);
+ rx->aubuf = mem_deref(rx->aubuf);
+
+ list_flush(&rx->filtl);
+}
+
+
+static void audio_destructor(void *arg)
+{
+ struct audio *a = arg;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+
+ mem_deref(a->tx.enc);
+ mem_deref(a->rx.dec);
+ mem_deref(a->tx.aubuf);
+ mem_deref(a->tx.mb);
+ mem_deref(a->tx.sampv);
+ mem_deref(a->rx.sampv);
+ mem_deref(a->rx.aubuf);
+ mem_deref(a->tx.sampv_rs);
+ mem_deref(a->rx.sampv_rs);
+
+ list_flush(&a->tx.filtl);
+ list_flush(&a->rx.filtl);
+
+ mem_deref(a->strm);
+ mem_deref(a->telev);
+}
+
+
+/**
+ * Calculate number of samples from sample rate, channels and packet time
+ *
+ * @param srate Sample rate in [Hz]
+ * @param channels Number of channels
+ * @param ptime Packet time in [ms]
+ *
+ * @return Number of samples
+ */
+static inline uint32_t calc_nsamp(uint32_t srate, uint8_t channels,
+ uint16_t ptime)
+{
+ return srate * channels * ptime / 1000;
+}
+
+
+static inline double calc_ptime(size_t nsamp, uint32_t srate, uint8_t channels)
+{
+ double ptime;
+
+ ptime = 1000.0 * (double)nsamp / (double)(srate * channels);
+
+ return ptime;
+}
+
+
+/**
+ * Get the DSP samplerate for an audio-codec (exception for G.722 and MPA)
+ */
+static inline uint32_t get_srate(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return ac->srate;
+}
+
+
+/**
+ * Get the DSP channels for an audio-codec (exception for MPA)
+ */
+static inline uint32_t get_ch(const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return !str_casecmp(ac->name, "MPA") ? 2 : ac->ch;
+}
+
+
+static inline uint32_t get_framesize(const struct aucodec *ac,
+ uint32_t ptime)
+{
+ if (!ac)
+ return 0;
+
+ return calc_nsamp(get_srate(ac), get_ch(ac), ptime);
+}
+
+
+static bool aucodec_equal(const struct aucodec *a, const struct aucodec *b)
+{
+ if (!a || !b)
+ return false;
+
+ return get_srate(a) == get_srate(b) && get_ch(a) == get_ch(b);
+}
+
+
+static int add_audio_codec(struct audio *a, struct sdp_media *m,
+ struct aucodec *ac)
+{
+ if (!in_range(&a->cfg.srate, get_srate(ac))) {
+ debug("audio: skip %uHz codec (audio range %uHz - %uHz)\n",
+ get_srate(ac), a->cfg.srate.min, a->cfg.srate.max);
+ return 0;
+ }
+
+ if (!in_range(&a->cfg.channels, get_ch(ac))) {
+ debug("audio: skip codec with %uch (audio range %uch-%uch)\n",
+ get_ch(ac), a->cfg.channels.min, a->cfg.channels.max);
+ return 0;
+ }
+
+ if (ac->crate < 8000) {
+ warning("audio: illegal clock rate %u\n", ac->crate);
+ return EINVAL;
+ }
+
+ return sdp_format_add(NULL, m, false, ac->pt, ac->name, ac->crate,
+ ac->ch, ac->fmtp_ench, ac->fmtp_cmph, ac, false,
+ "%s", ac->fmtp);
+}
+
+
+static int append_rtpext(struct audio *au, struct mbuf *mb,
+ int16_t *sampv, size_t sampc)
+{
+ uint8_t data[1];
+ double level;
+ int err;
+
+ /* audio level must be calculated from the audio samples that
+ * are actually sent on the network. */
+ level = aulevel_calc_dbov(sampv, sampc);
+
+ data[0] = (int)-level & 0x7f;
+
+ err = rtpext_encode(mb, au->extmap_aulevel, 1, data);
+ if (err) {
+ warning("audio: rtpext_encode failed (%m)\n", err);
+ return err;
+ }
+
+ return err;
+}
+
+
+/**
+ * Encoder audio and send via stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param a Audio object
+ * @param tx Audio transmit object
+ * @param sampv Audio samples
+ * @param sampc Number of audio samples
+ */
+static void encode_rtp_send(struct audio *a, struct autx *tx,
+ int16_t *sampv, size_t sampc)
+{
+ size_t frame_size; /* number of samples per channel */
+ size_t sampc_rtp;
+ size_t len;
+ size_t ext_len = 0;
+ int err;
+
+ if (!tx->ac || !tx->ac->ench)
+ return;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ if (a->level_enabled) {
+
+ /* skip the extension header */
+ tx->mb->pos += RTPEXT_HDR_SIZE;
+
+ err = append_rtpext(a, tx->mb, sampv, sampc);
+ if (err)
+ return;
+
+ ext_len = tx->mb->pos - STREAM_PRESZ;
+
+ /* write the Extension header at the beginning */
+ tx->mb->pos = STREAM_PRESZ;
+
+ err = rtpext_hdr_encode(tx->mb, ext_len - RTPEXT_HDR_SIZE);
+ if (err)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ + ext_len;
+ tx->mb->end = STREAM_PRESZ + ext_len;
+ }
+
+ len = mbuf_get_space(tx->mb);
+
+ err = tx->ac->ench(tx->enc, mbuf_buf(tx->mb), &len, sampv, sampc);
+ if ((err & 0xffff0000) == 0x00010000) {
+ /* MPA needs some special treatment here */
+ tx->ts_ext = err & 0xffff;
+ err = 0;
+ }
+ else if (err) {
+ warning("audio: %s encode error: %d samples (%m)\n",
+ tx->ac->name, sampc, err);
+ goto out;
+ }
+
+ tx->mb->pos = STREAM_PRESZ;
+ tx->mb->end = STREAM_PRESZ + ext_len + len;
+
+ if (mbuf_get_left(tx->mb)) {
+
+ uint32_t rtp_ts = tx->ts_ext & 0xffffffff;
+
+ if (len) {
+ err = stream_send(a->strm, ext_len!=0, tx->marker, -1,
+ rtp_ts, tx->mb);
+ if (err)
+ goto out;
+ }
+ }
+
+ /* Convert from audio samplerate to RTP clockrate */
+ sampc_rtp = sampc * tx->ac->crate / tx->ac->srate;
+
+ /* The RTP clock rate used for generating the RTP timestamp is
+ * independent of the number of channels and the encoding
+ * However, MPA support variable packet durations. Thus, MPA
+ * should update the ts according to its current internal state.
+ */
+ frame_size = sampc_rtp / get_ch(tx->ac);
+
+ tx->ts_ext += (uint32_t)frame_size;
+
+ out:
+ tx->marker = false;
+}
+
+
+/*
+ * @note This function has REAL-TIME properties
+ */
+static void poll_aubuf_tx(struct audio *a)
+{
+ struct autx *tx = &a->tx;
+ int16_t *sampv = tx->sampv;
+ size_t sampc;
+ size_t sz;
+ size_t num_bytes;
+ struct le *le;
+ int err = 0;
+
+ sz = aufmt_sample_size(tx->src_fmt);
+ if (!sz)
+ return;
+
+ num_bytes = tx->psize;
+ sampc = tx->psize / sz;
+
+ /* timed read from audio-buffer */
+
+ if (tx->src_fmt == AUFMT_S16LE) {
+
+ aubuf_read(tx->aubuf, (uint8_t *)tx->sampv, num_bytes);
+ }
+ else {
+ /* Convert from ausrc format to 16-bit format */
+
+ void *tmp_sampv;
+
+ if (!tx->need_conv) {
+ info("audio: NOTE: source sample conversion"
+ " needed: %s --> %s\n",
+ aufmt_name(tx->src_fmt), aufmt_name(AUFMT_S16LE));
+ tx->need_conv = true;
+ }
+
+ tmp_sampv = mem_zalloc(num_bytes, NULL);
+ if (!tmp_sampv)
+ return;
+
+ aubuf_read(tx->aubuf, tmp_sampv, num_bytes);
+
+ auconv_to_s16(sampv, tx->src_fmt, tmp_sampv, sampc);
+
+ mem_deref(tmp_sampv);
+ }
+
+ /* optional resampler */
+ if (tx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&tx->resamp,
+ tx->sampv_rs, &sampc_rs,
+ tx->sampv, sampc);
+ if (err)
+ return;
+
+ sampv = tx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ /* Process exactly one audio-frame in list order */
+ for (le = tx->filtl.head; le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af && st->af->ench)
+ err |= st->af->ench(st, sampv, &sampc);
+ }
+ if (err) {
+ warning("audio: aufilter encode: %m\n", err);
+ }
+
+ /* Encode and send */
+ encode_rtp_send(a, tx, sampv, sampc);
+}
+
+
+static void check_telev(struct audio *a, struct autx *tx)
+{
+ const struct sdp_format *fmt;
+ bool marker = false;
+ int err;
+
+ tx->mb->pos = tx->mb->end = STREAM_PRESZ;
+
+ err = telev_poll(a->telev, &marker, tx->mb);
+ if (err)
+ return;
+
+ if (marker)
+ tx->ts_tel = (uint32_t)tx->ts_ext;
+
+ fmt = sdp_media_rformat(stream_sdpmedia(audio_strm(a)), telev_rtpfmt);
+ if (!fmt)
+ return;
+
+ tx->mb->pos = STREAM_PRESZ;
+ err = stream_send(a->strm, false, marker, fmt->pt, tx->ts_tel, tx->mb);
+ if (err) {
+ warning("audio: telev: stream_send %m\n", err);
+ }
+}
+
+
+/**
+ * Write samples to Audio Player.
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note The application is responsible for filling in silence in
+ * the case of underrun
+ *
+ * @note This function may be called from any thread
+ *
+ * @note The sample format is set in rx->play_fmt
+ *
+ * @param buf Buffer to fill with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ */
+static void auplay_write_handler(void *sampv, size_t sampc, void *arg)
+{
+ struct aurx *rx = arg;
+ size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt);
+
+ if (rx->aubuf_started && aubuf_cur_size(rx->aubuf) < num_bytes) {
+
+ ++rx->stats.aubuf_underrun;
+
+ debug("audio: rx aubuf underrun (total %llu)\n",
+ rx->stats.aubuf_underrun);
+ }
+
+ aubuf_read(rx->aubuf, sampv, num_bytes);
+}
+
+
+/**
+ * Read samples from Audio Source
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @note This function may be called from any thread
+ *
+ * @param buf Buffer with audio samples
+ * @param sz Number of bytes in buffer
+ * @param arg Handler argument
+ */
+static void ausrc_read_handler(const void *sampv, size_t sampc, void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+ size_t num_bytes = sampc * aufmt_sample_size(tx->src_fmt);
+
+ if (tx->muted)
+ memset((void *)sampv, 0, num_bytes);
+
+ if (aubuf_cur_size(tx->aubuf) >= tx->aubuf_maxsz) {
+
+ ++tx->stats.aubuf_overrun;
+
+ debug("audio: tx aubuf overrun (total %llu)\n",
+ tx->stats.aubuf_overrun);
+ }
+
+ (void)aubuf_write(tx->aubuf, sampv, num_bytes);
+
+ tx->aubuf_started = true;
+
+ if (a->cfg.txmode == AUDIO_MODE_POLL) {
+ unsigned i;
+
+ for (i=0; i<16; i++) {
+
+ if (aubuf_cur_size(tx->aubuf) < tx->psize)
+ break;
+
+ poll_aubuf_tx(a);
+ }
+ }
+
+ /* Exact timing: send Telephony-Events from here */
+ check_telev(a, tx);
+}
+
+
+static void ausrc_error_handler(int err, const char *str, void *arg)
+{
+ struct audio *a = arg;
+ MAGIC_CHECK(a);
+
+ if (a->errh)
+ a->errh(err, str, a->arg);
+}
+
+
+static int pt_handler(struct audio *a, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(a->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Audio decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ a->rx.pt = pt_new;
+
+ return audio_decoder_set(a, lc->data, lc->pt, lc->params);
+}
+
+
+static void handle_telev(struct audio *a, struct mbuf *mb)
+{
+ int event, digit;
+ bool end;
+
+ if (telev_recv(a->telev, mb, &event, &end))
+ return;
+
+ digit = telev_code2digit(event);
+ if (digit >= 0 && a->eventh)
+ a->eventh(digit, end, a->arg);
+}
+
+
+static int aurx_stream_decode(struct aurx *rx, struct mbuf *mb)
+{
+ size_t sampc = AUDIO_SAMPSZ;
+ int16_t *sampv;
+ struct le *le;
+ int err = 0;
+
+ /* No decoder set */
+ if (!rx->ac)
+ return 0;
+
+ if (mbuf_get_left(mb)) {
+ err = rx->ac->dech(rx->dec, rx->sampv, &sampc,
+ mbuf_buf(mb), mbuf_get_left(mb));
+ }
+ else if (rx->ac->plch) {
+ sampc = rx->ac->srate * rx->ac->ch * rx->ptime / 1000;
+
+ err = rx->ac->plch(rx->dec, rx->sampv, &sampc);
+ }
+ else {
+ /* no PLC in the codec, might be done in filters below */
+ sampc = 0;
+ }
+
+ if (err) {
+ warning("audio: %s codec decode %u bytes: %m\n",
+ rx->ac->name, mbuf_get_left(mb), err);
+ goto out;
+ }
+
+ /* Process exactly one audio-frame in reverse list order */
+ for (le = rx->filtl.tail; le; le = le->prev) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af && st->af->dech)
+ err |= st->af->dech(st, rx->sampv, &sampc);
+ }
+
+ if (!rx->aubuf)
+ goto out;
+
+ sampv = rx->sampv;
+
+ /* optional resampler */
+ if (rx->resamp.resample) {
+ size_t sampc_rs = AUDIO_SAMPSZ;
+
+ err = auresamp(&rx->resamp,
+ rx->sampv_rs, &sampc_rs,
+ rx->sampv, sampc);
+ if (err)
+ return err;
+
+ sampv = rx->sampv_rs;
+ sampc = sampc_rs;
+ }
+
+ if (aubuf_cur_size(rx->aubuf) >= rx->aubuf_maxsz) {
+
+ ++rx->stats.aubuf_overrun;
+
+ debug("audio: rx aubuf overrun (total %llu)\n",
+ rx->stats.aubuf_overrun);
+ }
+
+ if (rx->play_fmt == AUFMT_S16LE) {
+ err = aubuf_write_samp(rx->aubuf, sampv, sampc);
+ if (err)
+ goto out;
+ }
+ else {
+
+ /* Convert from 16-bit to auplay format */
+
+ void *tmp_sampv;
+ size_t num_bytes = sampc * aufmt_sample_size(rx->play_fmt);
+
+ if (!rx->need_conv) {
+ info("audio: NOTE: playback sample conversion"
+ " needed: %s --> %s\n",
+ aufmt_name(AUFMT_S16LE),
+ aufmt_name(rx->play_fmt));
+ rx->need_conv = true;
+ }
+
+ tmp_sampv = mem_zalloc(num_bytes, NULL);
+ if (!tmp_sampv)
+ return ENOMEM;
+
+ auconv_from_s16(rx->play_fmt, tmp_sampv, sampv, sampc);
+
+ err = aubuf_write(rx->aubuf, tmp_sampv, num_bytes);
+
+ mem_deref(tmp_sampv);
+
+ if (err)
+ goto out;
+ }
+
+ rx->aubuf_started = true;
+
+ out:
+ return err;
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg)
+{
+ struct audio *a = arg;
+ struct aurx *rx = &a->rx;
+ bool discard = false;
+ size_t i;
+ int wrap;
+ int err;
+
+ if (!mb)
+ goto out;
+
+ /* Telephone event? */
+ if (hdr->pt != rx->pt) {
+ const struct sdp_format *fmt;
+
+ fmt = sdp_media_lformat(stream_sdpmedia(a->strm), hdr->pt);
+
+ if (fmt && !str_casecmp(fmt->name, "telephone-event")) {
+ handle_telev(a, mb);
+ return;
+ }
+ }
+
+ /* Comfort Noise (CN) as of RFC 3389 */
+ if (PT_CN == hdr->pt)
+ return;
+
+ /* Audio payload-type changed? */
+ /* XXX: this logic should be moved to stream.c */
+ if (hdr->pt != rx->pt) {
+
+ err = pt_handler(a, rx->pt, hdr->pt);
+ if (err)
+ return;
+ }
+
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ for (i=0; i<extc; i++) {
+
+ if (extv[i].id == a->extmap_aulevel) {
+
+ a->rx.level_last = -(double)(extv[i].data[0] & 0x7f);
+ a->rx.level_set = true;
+ }
+ else {
+ info("audio: rtp header ext ignored (id=%u)\n",
+ extv[i].id);
+ }
+ }
+
+ /* Save timestamp for incoming RTP packets */
+
+ if (rx->ts_recv.is_set) {
+
+ uint64_t ext_last, ext_now;
+
+ ext_last = calc_extended_timestamp(rx->ts_recv.num_wraps,
+ rx->ts_recv.last);
+
+ ext_now = calc_extended_timestamp(rx->ts_recv.num_wraps,
+ hdr->ts);
+
+ if (ext_now <= ext_last) {
+ uint64_t delta;
+
+ delta = ext_last - ext_now;
+
+ warning("audio: [time=%.3f]"
+ " discard old frame (%.3f seconds old)\n",
+ aurx_calc_seconds(rx),
+ audio_calc_seconds(delta, rx->ac->crate));
+
+ discard = true;
+ }
+ }
+ else {
+ rx->ts_recv.first = hdr->ts;
+ rx->ts_recv.last = hdr->ts;
+ rx->ts_recv.is_set = true;
+ }
+
+ wrap = timestamp_wrap(hdr->ts, rx->ts_recv.last);
+
+ switch (wrap) {
+
+ case -1:
+ warning("audio: rtp timestamp wraps backwards"
+ " (delta = %d) -- discard\n",
+ (int32_t)(rx->ts_recv.last - hdr->ts));
+ discard = true;
+ break;
+
+ case 0:
+ break;
+
+ case 1:
+ ++rx->ts_recv.num_wraps;
+ break;
+
+ default:
+ break;
+ }
+
+ rx->ts_recv.last = hdr->ts;
+
+#if 0
+ re_printf("[time=%.3f] wrap=%d discard=%d\n",
+ aurx_calc_seconds(rx), wrap, discard);
+#endif
+
+ if (discard) {
+ ++a->rx.n_discard;
+ return;
+ }
+
+ out:
+ (void)aurx_stream_decode(&a->rx, mb);
+}
+
+
+static int add_telev_codec(struct audio *a)
+{
+ struct sdp_media *m = stream_sdpmedia(audio_strm(a));
+ struct sdp_format *sf;
+ int err;
+
+ /* Use payload-type 101 if available, for CiscoGW interop */
+ err = sdp_format_add(&sf, m, false,
+ (!sdp_media_lformat(m, 101)) ? "101" : NULL,
+ telev_rtpfmt, TELEV_SRATE, 1, NULL,
+ NULL, NULL, false, "0-15");
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+/*
+ * EBU ACIP (Audio Contribution over IP) Profile
+ *
+ * Ref: https://tech.ebu.ch/docs/tech/tech3368.pdf
+ */
+static int set_ebuacip_params(struct audio *au, uint32_t ptime)
+{
+ struct sdp_media *sdp = stream_sdpmedia(au->strm);
+ const struct config_avt *avt = &au->strm->cfg;
+ char str[64];
+ int jbvalue = 0;
+ int jb_id = 0;
+ int err = 0;
+
+ /* set ebuacip version fixed value 0 for now. */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "version %i", 0);
+
+ /* set jb option, only one in our case */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "jb %i", jb_id);
+
+ /* define jb value in option */
+ if (0 == conf_get_str(conf_cur(), "ebuacip_jb_type",str,sizeof(str))) {
+
+ if (0 == str_cmp(str, "auto")) {
+
+ err |= sdp_media_set_lattr(sdp, false,
+ "ebuacip",
+ "jbdef %i auto %d-%d",
+ jb_id,
+ avt->jbuf_del.min * ptime,
+ avt->jbuf_del.max * ptime);
+ }
+ else if (0 == str_cmp(str, "fixed")) {
+
+ /* define jb value in option */
+ jbvalue = avt->jbuf_del.max * ptime;
+
+ err |= sdp_media_set_lattr(sdp, false,
+ "ebuacip",
+ "jbdef %i fixed %d",
+ jb_id, jbvalue);
+ }
+ }
+
+ /* set QOS recomendation use tos / 4 to set DSCP value */
+ err |= sdp_media_set_lattr(sdp, false, "ebuacip", "qosrec %u",
+ avt->rtp_tos / 4);
+
+ /* EBU ACIP FEC:: NOT SET IN BARESIP */
+
+ return err;
+}
+
+
+int audio_alloc(struct audio **ap, const struct stream_param *stream_prm,
+ const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ uint32_t ptime, const struct list *aucodecl, bool offerer,
+ audio_event_h *eventh, audio_err_h *errh, void *arg)
+{
+ struct audio *a;
+ struct autx *tx;
+ struct aurx *rx;
+ struct le *le;
+ int err;
+ (void)offerer;
+
+ if (!ap || !cfg)
+ return EINVAL;
+
+ a = mem_zalloc(sizeof(*a), audio_destructor);
+ if (!a)
+ return ENOMEM;
+
+ MAGIC_INIT(a);
+
+ a->cfg = cfg->audio;
+ tx = &a->tx;
+ rx = &a->rx;
+
+ tx->src_fmt = cfg->audio.src_fmt;
+ rx->play_fmt = cfg->audio.play_fmt;
+
+ err = stream_alloc(&a->strm, stream_prm, &cfg->avt, call, sdp_sess,
+ "audio", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, NULL, a);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max) {
+ stream_set_bw(a->strm, AUDIO_BANDWIDTH);
+ }
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "ptime", "%u", ptime);
+ if (err)
+ goto out;
+
+ if (cfg->audio.level && offerer) {
+
+ a->extmap_aulevel = 1;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(a->strm), true,
+ "extmap",
+ "%u %s",
+ a->extmap_aulevel, uri_aulevel);
+ if (err)
+ goto out;
+ }
+
+ if (cfg->sdp.ebuacip) {
+
+ err = set_ebuacip_params(a, ptime);
+ if (err)
+ goto out;
+ }
+
+ /* Audio codecs */
+ for (le = list_head(aucodecl); le; le = le->next) {
+ err = add_audio_codec(a, stream_sdpmedia(a->strm), le->data);
+ if (err)
+ goto out;
+ }
+
+ tx->mb = mbuf_alloc(STREAM_PRESZ + 4096);
+ tx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL);
+ rx->sampv = mem_zalloc(AUDIO_SAMPSZ * sizeof(int16_t), NULL);
+ if (!tx->mb || !tx->sampv || !rx->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = telev_alloc(&a->telev, ptime);
+ if (err)
+ goto out;
+
+ err = add_telev_codec(a);
+ if (err)
+ goto out;
+
+ auresamp_init(&tx->resamp);
+ str_ncpy(tx->device, a->cfg.src_dev, sizeof(tx->device));
+ tx->ptime = ptime;
+ tx->ts_ext = tx->ts_base = rand_u16();
+ tx->marker = true;
+
+ auresamp_init(&rx->resamp);
+ str_ncpy(rx->device, a->cfg.play_dev, sizeof(rx->device));
+ rx->pt = -1;
+ rx->ptime = ptime;
+
+ a->eventh = eventh;
+ a->errh = errh;
+ a->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(a);
+ else
+ *ap = a;
+
+ return err;
+}
+
+
+#ifdef HAVE_PTHREAD
+static void *tx_thread(void *arg)
+{
+ struct audio *a = arg;
+ struct autx *tx = &a->tx;
+ uint64_t ts = 0;
+
+ while (a->tx.u.thr.run) {
+
+ uint64_t now;
+
+ sys_msleep(4);
+
+ if (!tx->aubuf_started)
+ continue;
+
+ if (!a->tx.u.thr.run)
+ break;
+
+ now = tmr_jiffies();
+ if (!ts)
+ ts = now;
+
+ if (ts > now)
+ continue;
+
+ /* Now is the time to send */
+
+ if (aubuf_cur_size(tx->aubuf) >= tx->psize) {
+
+ poll_aubuf_tx(a);
+ }
+ else {
+ ++tx->stats.aubuf_underrun;
+
+ debug("audio: thread: tx aubuf underrun"
+ " (total %llu)\n", tx->stats.aubuf_underrun);
+ }
+
+ ts += tx->ptime;
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void aufilt_param_set(struct aufilt_prm *prm,
+ const struct aucodec *ac, uint32_t ptime)
+{
+ if (!ac) {
+ memset(prm, 0, sizeof(*prm));
+ return;
+ }
+
+ prm->srate = get_srate(ac);
+ prm->ch = get_ch(ac);
+ prm->ptime = ptime;
+}
+
+
+static int autx_print_pipeline(struct re_printf *pf, const struct autx *autx)
+{
+ struct le *le;
+ int err;
+
+ if (!autx)
+ return 0;
+
+ err = re_hprintf(pf, "audio tx pipeline: %10s",
+ autx->ausrc ? autx->ausrc->as->name : "src");
+
+ for (le = list_head(&autx->filtl); le; le = le->next) {
+ struct aufilt_enc_st *st = le->data;
+
+ if (st->af->ench)
+ err |= re_hprintf(pf, " ---> %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ autx->ac ? autx->ac->name : "encoder");
+
+ return err;
+}
+
+
+static int aurx_print_pipeline(struct re_printf *pf, const struct aurx *aurx)
+{
+ struct le *le;
+ int err;
+
+ if (!aurx)
+ return 0;
+
+ err = re_hprintf(pf, "audio rx pipeline: %10s",
+ aurx->auplay ? aurx->auplay->ap->name : "play");
+
+ for (le = list_head(&aurx->filtl); le; le = le->next) {
+ struct aufilt_dec_st *st = le->data;
+
+ if (st->af->dech)
+ err |= re_hprintf(pf, " <--- %s", st->af->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ aurx->ac ? aurx->ac->name : "decoder");
+
+ return err;
+}
+
+
+/**
+ * Setup the audio-filter chain
+ *
+ * must be called before auplay/ausrc-alloc
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int aufilt_setup(struct audio *a)
+{
+ struct aufilt_prm encprm, decprm;
+ struct autx *tx = &a->tx;
+ struct aurx *rx = &a->rx;
+ struct le *le;
+ int err = 0;
+
+ /* wait until we have both Encoder and Decoder */
+ if (!tx->ac || !rx->ac)
+ return 0;
+
+ if (!list_isempty(&tx->filtl) || !list_isempty(&rx->filtl))
+ return 0;
+
+ aufilt_param_set(&encprm, tx->ac, tx->ptime);
+ aufilt_param_set(&decprm, rx->ac, rx->ptime);
+
+ /* Audio filters */
+ for (le = list_head(baresip_aufiltl()); le; le = le->next) {
+ struct aufilt *af = le->data;
+ struct aufilt_enc_st *encst = NULL;
+ struct aufilt_dec_st *decst = NULL;
+ void *ctx = NULL;
+
+ if (af->encupdh) {
+ err |= af->encupdh(&encst, &ctx, af, &encprm);
+ if (err)
+ break;
+
+ encst->af = af;
+ list_append(&tx->filtl, &encst->le, encst);
+ }
+
+ if (af->decupdh) {
+ err |= af->decupdh(&decst, &ctx, af, &decprm);
+ if (err)
+ break;
+
+ decst->af = af;
+ list_append(&rx->filtl, &decst->le, decst);
+ }
+
+ if (err) {
+ warning("audio: audio-filter '%s'"
+ " update failed (%m)\n", af->name, err);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int start_player(struct aurx *rx, struct audio *a)
+{
+ const struct aucodec *ac = rx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = get_ch(ac);
+
+ if (a->cfg.srate_play && a->cfg.srate_play != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_play;
+ }
+ if (a->cfg.channels_play && a->cfg.channels_play != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_play;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !rx->sampv_rs) {
+
+ info("audio: enable auplay resampler:"
+ " %uHz/%uch --> %uHz/%uch\n",
+ get_srate(ac), get_ch(ac), srate_dsp, channels_dsp);
+
+ rx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!rx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&rx->resamp,
+ get_srate(ac), get_ch(ac),
+ srate_dsp, channels_dsp);
+ if (err) {
+ warning("audio: could not setup auplay resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Player */
+ if (!rx->auplay && auplay_find(baresip_auplayl(), NULL)) {
+
+ struct auplay_prm prm;
+
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = rx->ptime;
+ prm.fmt = rx->play_fmt;
+
+ if (!rx->aubuf) {
+ size_t psize;
+ size_t sz = aufmt_sample_size(rx->play_fmt);
+
+ psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ rx->aubuf_maxsz = psize * 8;
+
+ err = aubuf_alloc(&rx->aubuf, psize * 1,
+ rx->aubuf_maxsz);
+ if (err)
+ return err;
+ }
+
+ err = auplay_alloc(&rx->auplay, baresip_auplayl(),
+ a->cfg.play_mod,
+ &prm, rx->device,
+ auplay_write_handler, rx);
+ if (err) {
+ warning("audio: start_player failed (%s.%s): %m\n",
+ a->cfg.play_mod, rx->device, err);
+ return err;
+ }
+
+ rx->auplay_prm = prm;
+
+ info("audio: player started with sample format %s\n",
+ aufmt_name(rx->play_fmt));
+ }
+
+ return 0;
+}
+
+
+static int start_source(struct autx *tx, struct audio *a)
+{
+ const struct aucodec *ac = tx->ac;
+ uint32_t srate_dsp = get_srate(ac);
+ uint32_t channels_dsp;
+ bool resamp = false;
+ int err;
+
+ if (!ac)
+ return 0;
+
+ channels_dsp = get_ch(ac);
+
+ if (a->cfg.srate_src && a->cfg.srate_src != srate_dsp) {
+ resamp = true;
+ srate_dsp = a->cfg.srate_src;
+ }
+ if (a->cfg.channels_src && a->cfg.channels_src != channels_dsp) {
+ resamp = true;
+ channels_dsp = a->cfg.channels_src;
+ }
+
+ /* Optional resampler, if configured */
+ if (resamp && !tx->sampv_rs) {
+
+ info("audio: enable ausrc resampler:"
+ " %uHz/%uch <-- %uHz/%uch\n",
+ get_srate(ac), get_ch(ac), srate_dsp, channels_dsp);
+
+ tx->sampv_rs = mem_zalloc(AUDIO_SAMPSZ * 2, NULL);
+ if (!tx->sampv_rs)
+ return ENOMEM;
+
+ err = auresamp_setup(&tx->resamp,
+ srate_dsp, channels_dsp,
+ get_srate(ac), get_ch(ac));
+ if (err) {
+ warning("audio: could not setup ausrc resampler"
+ " (%m)\n", err);
+ return err;
+ }
+ }
+
+ /* Start Audio Source */
+ if (!tx->ausrc && ausrc_find(baresip_ausrcl(), NULL)) {
+
+ struct ausrc_prm prm;
+ size_t sz;
+
+ prm.srate = srate_dsp;
+ prm.ch = channels_dsp;
+ prm.ptime = tx->ptime;
+ prm.fmt = tx->src_fmt;
+
+ sz = aufmt_sample_size(tx->src_fmt);
+
+ tx->psize = sz * calc_nsamp(prm.srate, prm.ch, prm.ptime);
+
+ tx->aubuf_maxsz = tx->psize * 30;
+
+ if (!tx->aubuf) {
+ err = aubuf_alloc(&tx->aubuf, tx->psize,
+ tx->aubuf_maxsz);
+ if (err)
+ return err;
+ }
+
+ err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(),
+ NULL, a->cfg.src_mod,
+ &prm, tx->device,
+ ausrc_read_handler, ausrc_error_handler, a);
+ if (err) {
+ warning("audio: start_source failed (%s.%s): %m\n",
+ a->cfg.src_mod, tx->device, err);
+ return err;
+ }
+
+ switch (a->cfg.txmode) {
+#ifdef HAVE_PTHREAD
+ case AUDIO_MODE_THREAD:
+ if (!tx->u.thr.run) {
+ tx->u.thr.run = true;
+ err = pthread_create(&tx->u.thr.tid, NULL,
+ tx_thread, a);
+ if (err) {
+ tx->u.thr.tid = false;
+ return err;
+ }
+ }
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ tx->ausrc_prm = prm;
+
+ info("audio: source started with sample format %s\n",
+ aufmt_name(tx->src_fmt));
+ }
+
+ return 0;
+}
+
+
+/**
+ * Start the audio playback and recording
+ *
+ * @param a Audio object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_start(struct audio *a)
+{
+ int err;
+
+ if (!a)
+ return EINVAL;
+
+ /* Audio filter */
+ if (!list_isempty(baresip_aufiltl())) {
+ err = aufilt_setup(a);
+ if (err)
+ return err;
+ }
+
+ /* configurable order of play/src start */
+ if (a->cfg.src_first) {
+ err = start_source(&a->tx, a);
+ err |= start_player(&a->rx, a);
+ }
+ else {
+ err = start_player(&a->rx, a);
+ err |= start_source(&a->tx, a);
+ }
+ if (err)
+ return err;
+
+ if (a->tx.ac && a->rx.ac) {
+
+ if (!a->started) {
+ info("%H%H",
+ autx_print_pipeline, &a->tx,
+ aurx_print_pipeline, &a->rx);
+ }
+
+ a->started = true;
+ }
+
+ return err;
+}
+
+
+/**
+ * Stop the audio playback and recording
+ *
+ * @param a Audio object
+ */
+void audio_stop(struct audio *a)
+{
+ if (!a)
+ return;
+
+ stop_tx(&a->tx, a);
+ stop_rx(&a->rx);
+}
+
+
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params)
+{
+ struct autx *tx;
+ int err = 0;
+ bool reset;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ tx = &a->tx;
+
+ reset = !aucodec_equal(ac, tx->ac);
+
+ if (ac != tx->ac) {
+ info("audio: Set audio encoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), get_ch(ac));
+
+ /* Audio source must be stopped first */
+ if (reset) {
+ tx->ausrc = mem_deref(tx->ausrc);
+ }
+
+ tx->enc = mem_deref(tx->enc);
+ tx->ac = ac;
+ }
+
+ if (ac->encupdh) {
+ struct auenc_param prm;
+
+ prm.ptime = tx->ptime;
+
+ err = ac->encupdh(&tx->enc, ac, &prm, params);
+ if (err) {
+ warning("audio: alloc encoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, ac->crate, ac->crate);
+ stream_update_encoder(a->strm, pt_tx);
+
+ telev_set_srate(a->telev, ac->crate);
+
+ if (!tx->ausrc) {
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params)
+{
+ struct aurx *rx;
+ bool reset = false;
+ int err = 0;
+
+ if (!a || !ac)
+ return EINVAL;
+
+ rx = &a->rx;
+
+ reset = !aucodec_equal(ac, rx->ac);
+
+ if (ac != rx->ac) {
+
+ info("audio: Set audio decoder: %s %uHz %dch\n",
+ ac->name, get_srate(ac), get_ch(ac));
+
+ rx->pt = pt_rx;
+ rx->ac = ac;
+ rx->dec = mem_deref(rx->dec);
+ }
+
+ if (ac->decupdh) {
+ err = ac->decupdh(&rx->dec, ac, params);
+ if (err) {
+ warning("audio: alloc decoder: %m\n", err);
+ return err;
+ }
+ }
+
+ stream_set_srate(a->strm, ac->crate, ac->crate);
+
+ if (reset) {
+
+ rx->auplay = mem_deref(rx->auplay);
+
+ /* Reset audio filter chain */
+ list_flush(&rx->filtl);
+
+ err |= audio_start(a);
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next audio encoder in the local list of negotiated codecs
+ *
+ * @param audio Audio object
+ */
+void audio_encoder_cycle(struct audio *audio)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!audio)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(audio_strm(audio)));
+ if (!rc) {
+ info("audio: encoder cycle: no remote codec found\n");
+ return;
+ }
+
+ (void)audio_encoder_set(audio, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *audio_strm(const struct audio *a)
+{
+ return a ? a->strm : NULL;
+}
+
+
+int audio_send_digit(struct audio *a, char key)
+{
+ int err = 0;
+
+ if (!a)
+ return EINVAL;
+
+ if (key != KEYCODE_REL) {
+ int event = telev_digit2code(key);
+ info("audio: send DTMF digit: '%c'\n", key);
+
+ if (event == -1) {
+ warning("audio: invalid DTMF digit (0x%02x)\n", key);
+ return EINVAL;
+ }
+
+ err = telev_send(a->telev, event, false);
+ }
+ else if (a->tx.cur_key && a->tx.cur_key != KEYCODE_REL) {
+ /* Key release */
+ info("audio: send DTMF digit end: '%c'\n", a->tx.cur_key);
+ err = telev_send(a->telev,
+ telev_digit2code(a->tx.cur_key), true);
+ }
+
+ a->tx.cur_key = key;
+
+ return err;
+}
+
+
+/**
+ * Mute the audio stream source (i.e. Microphone)
+ *
+ * @param a Audio stream
+ * @param muted True to mute, false to un-mute
+ */
+void audio_mute(struct audio *a, bool muted)
+{
+ if (!a)
+ return;
+
+ a->tx.muted = muted;
+}
+
+
+/**
+ * Get the mute state of an audio source
+ *
+ * @param a Audio stream
+ *
+ * @return True if muted, otherwise false
+ */
+bool audio_ismuted(const struct audio *a)
+{
+ if (!a)
+ return false;
+
+ return a->tx.muted;
+}
+
+
+static bool extmap_handler(const char *name, const char *value, void *arg)
+{
+ struct audio *au = arg;
+ struct sdp_extmap extmap;
+ int err;
+ (void)name;
+
+ err = sdp_extmap_decode(&extmap, value);
+ if (err) {
+ warning("audio: sdp_extmap_decode error (%m)\n", err);
+ return false;
+ }
+
+ if (0 == pl_strcasecmp(&extmap.name, uri_aulevel)) {
+
+ if (extmap.id < RTPEXT_ID_MIN || extmap.id > RTPEXT_ID_MAX) {
+ warning("audio: extmap id out of range (%u)\n",
+ extmap.id);
+ return false;
+ }
+
+ au->extmap_aulevel = extmap.id;
+
+ err = sdp_media_set_lattr(stream_sdpmedia(au->strm), true,
+ "extmap",
+ "%u %s",
+ au->extmap_aulevel,
+ uri_aulevel);
+ if (err)
+ return false;
+
+ au->level_enabled = true;
+ info("audio: client-to-mixer audio levels enabled\n");
+ }
+
+ return false;
+}
+
+
+void audio_sdp_attr_decode(struct audio *a)
+{
+ const char *attr;
+
+ if (!a)
+ return;
+
+ /* This is probably only meaningful for audio data, but
+ may be used with other media types if it makes sense. */
+ attr = sdp_media_rattr(stream_sdpmedia(a->strm), "ptime");
+ if (attr) {
+ struct autx *tx = &a->tx;
+ uint32_t ptime_tx = atoi(attr);
+
+ if (ptime_tx && ptime_tx != a->tx.ptime) {
+
+ info("audio: peer changed ptime_tx %ums -> %ums\n",
+ a->tx.ptime, ptime_tx);
+
+ tx->ptime = ptime_tx;
+
+ if (tx->ac) {
+ tx->psize = 2 * get_framesize(tx->ac,
+ ptime_tx);
+ }
+ }
+ }
+
+ /* Client-to-Mixer Audio Level Indication */
+ if (a->cfg.level) {
+ sdp_media_rattr_apply(stream_sdpmedia(a->strm),
+ "extmap",
+ extmap_handler, a);
+ }
+}
+
+
+/**
+ * Get the last value of the audio level from incoming RTP packets
+ *
+ * @param au Audio object
+ * @param levelp Pointer to where to write audio level value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int audio_level_get(const struct audio *au, double *levelp)
+{
+ if (!au)
+ return EINVAL;
+
+ if (!au->level_enabled)
+ return ENOTSUP;
+
+ if (!au->rx.level_set)
+ return ENOENT;
+
+ if (levelp)
+ *levelp = au->rx.level_last;
+
+ return 0;
+}
+
+
+static int aucodec_print(struct re_printf *pf, const struct aucodec *ac)
+{
+ if (!ac)
+ return 0;
+
+ return re_hprintf(pf, "%s %uHz/%dch",
+ ac->name, get_srate(ac), get_ch(ac));
+}
+
+
+int audio_debug(struct re_printf *pf, const struct audio *a)
+{
+ const struct autx *tx;
+ const struct aurx *rx;
+ size_t sztx, szrx;
+ int err;
+
+ if (!a)
+ return 0;
+
+ tx = &a->tx;
+ rx = &a->rx;
+
+ sztx = aufmt_sample_size(tx->src_fmt);
+ szrx = aufmt_sample_size(rx->play_fmt);
+
+ err = re_hprintf(pf, "\n--- Audio stream ---\n");
+
+ err |= re_hprintf(pf, " tx: %H ptime=%ums\n",
+ aucodec_print, tx->ac,
+ tx->ptime);
+ err |= re_hprintf(pf, " aubuf: %H"
+ " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n",
+ aubuf_debug, tx->aubuf,
+ calc_ptime(aubuf_cur_size(tx->aubuf)/sztx,
+ tx->ausrc_prm.srate,
+ tx->ausrc_prm.ch),
+ calc_ptime(tx->aubuf_maxsz/sztx,
+ tx->ausrc_prm.srate,
+ tx->ausrc_prm.ch),
+ tx->stats.aubuf_overrun,
+ tx->stats.aubuf_underrun);
+
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ autx_calc_seconds(tx));
+
+ err |= re_hprintf(pf,
+ " rx: %H\n"
+ " ptime=%ums pt=%d\n",
+ aucodec_print, rx->ac,
+ rx->ptime, rx->pt);
+ err |= re_hprintf(pf, " aubuf: %H"
+ " (cur %.2fms, max %.2fms, or %llu, ur %llu)\n",
+ aubuf_debug, rx->aubuf,
+ calc_ptime(aubuf_cur_size(rx->aubuf)/szrx,
+ rx->auplay_prm.srate,
+ rx->auplay_prm.ch),
+ calc_ptime(rx->aubuf_maxsz/szrx,
+ rx->auplay_prm.srate,
+ rx->auplay_prm.ch),
+ rx->stats.aubuf_overrun,
+ rx->stats.aubuf_underrun
+ );
+
+ err |= re_hprintf(pf, " n_discard:%llu\n",
+ rx->n_discard);
+ if (rx->level_set) {
+ err |= re_hprintf(pf, " level %.3f dBov\n",
+ rx->level_last);
+ }
+ if (rx->ts_recv.is_set) {
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ aurx_calc_seconds(rx));
+ }
+ else {
+ err |= re_hprintf(pf, " time = (not started)\n");
+ }
+
+ err |= re_hprintf(pf,
+ " %H"
+ " %H",
+ autx_print_pipeline, tx,
+ aurx_print_pipeline, rx);
+
+ err |= stream_debug(pf, a->strm);
+
+ return err;
+}
+
+
+void audio_set_devicename(struct audio *a, const char *src, const char *play)
+{
+ if (!a)
+ return;
+
+ str_ncpy(a->tx.device, src, sizeof(a->tx.device));
+ str_ncpy(a->rx.device, play, sizeof(a->rx.device));
+}
+
+
+int audio_set_source(struct audio *au, const char *mod, const char *device)
+{
+ struct autx *tx;
+ int err;
+
+ if (!au)
+ return EINVAL;
+
+ tx = &au->tx;
+
+ /* stop the audio device first */
+ tx->ausrc = mem_deref(tx->ausrc);
+
+ err = ausrc_alloc(&tx->ausrc, baresip_ausrcl(),
+ NULL, mod, &tx->ausrc_prm, device,
+ ausrc_read_handler, ausrc_error_handler, au);
+ if (err) {
+ warning("audio: set_source failed (%s.%s): %m\n",
+ mod, device, err);
+ return err;
+ }
+
+ return 0;
+}
+
+
+int audio_set_player(struct audio *au, const char *mod, const char *device)
+{
+ struct aurx *rx;
+ int err;
+
+ if (!au)
+ return EINVAL;
+
+ rx = &au->rx;
+
+ /* stop the audio device first */
+ rx->auplay = mem_deref(rx->auplay);
+
+ err = auplay_alloc(&rx->auplay, baresip_auplayl(),
+ mod, &rx->auplay_prm, device,
+ auplay_write_handler, rx);
+ if (err) {
+ warning("audio: set_player failed (%s.%s): %m\n",
+ mod, device, err);
+ return err;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Reference:
+ *
+ * https://www.avm.de/de/Extern/files/x-rtp/xrtpv32.pdf
+ */
+int audio_print_rtpstat(struct re_printf *pf, const struct audio *a)
+{
+ const struct stream *s;
+ const struct rtcp_stats *rtcp;
+ int srate_tx = 8000;
+ int srate_rx = 8000;
+ int err;
+
+ if (!a)
+ return 1;
+
+ s = a->strm;
+ rtcp = &s->rtcp_stats;
+
+ if (!rtcp->tx.sent)
+ return 1;
+
+ if (a->tx.ac)
+ srate_tx = get_srate(a->tx.ac);
+ if (a->rx.ac)
+ srate_rx = get_srate(a->rx.ac);
+
+ err = re_hprintf(pf,
+ "EX=BareSip;" /* Reporter Identifier */
+ "CS=%d;" /* Call Setup in milliseconds */
+ "CD=%d;" /* Call Duration in seconds */
+ "PR=%u;PS=%u;" /* Packets RX, TX */
+ "PL=%d,%d;" /* Packets Lost RX, TX */
+ "PD=%d,%d;" /* Packets Discarded, RX, TX */
+ "JI=%.1f,%.1f;" /* Jitter RX, TX in timestamp units */
+ "IP=%J,%J" /* Local, Remote IPs */
+ ,
+ call_setup_duration(s->call) * 1000,
+ call_duration(s->call),
+
+ s->metric_rx.n_packets,
+ s->metric_tx.n_packets,
+
+ rtcp->rx.lost, rtcp->tx.lost,
+
+ s->metric_rx.n_err, s->metric_tx.n_err,
+
+ /* timestamp units (ie: 8 ts units = 1 ms @ 8KHZ) */
+ 1.0 * rtcp->rx.jit/1000 * (srate_rx/1000),
+ 1.0 * rtcp->tx.jit/1000 * (srate_tx/1000),
+
+ sdp_media_laddr(s->sdp),
+ sdp_media_raddr(s->sdp)
+ );
+
+ if (a->tx.ac) {
+ err |= re_hprintf(pf, ";EN=%s/%d", a->tx.ac->name, srate_tx );
+ }
+ if (a->rx.ac) {
+ err |= re_hprintf(pf, ";DE=%s/%d", a->rx.ac->name, srate_rx );
+ }
+
+ return err;
+}
diff --git a/src/aufilt.c b/src/aufilt.c
new file mode 100644
index 0000000..240edee
--- /dev/null
+++ b/src/aufilt.c
@@ -0,0 +1,28 @@
+/**
+ * @file aufilt.c Audio Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+void aufilt_register(struct list *aufiltl, struct aufilt *af)
+{
+ if (!aufiltl || !af)
+ return;
+
+ list_append(aufiltl, &af->le, af);
+
+ info("aufilt: %s\n", af->name);
+}
+
+
+void aufilt_unregister(struct aufilt *af)
+{
+ if (!af)
+ return;
+
+ list_unlink(&af->le);
+}
diff --git a/src/aulevel.c b/src/aulevel.c
new file mode 100644
index 0000000..bc9a27a
--- /dev/null
+++ b/src/aulevel.c
@@ -0,0 +1,85 @@
+/**
+ * @file src/aulevel.c Audio level
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <math.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Generic routine to calculate RMS (Root-Mean-Square) from
+ * a set of signed 16-bit values
+ *
+ * \verbatim
+
+ .---------------
+ | N-1
+ | ----.
+ | \
+ | \ 2
+ | | s[n]
+ | /
+ | /
+ _ | ----'
+ \ | n=0
+ \ | ------------
+ \| N
+
+ \endverbatim
+ *
+ * @param data Array of signed 16-bit values
+ * @param len Number of values
+ *
+ * @return RMS value from 0 to 32768
+ */
+static double calc_rms(const int16_t *data, size_t len)
+{
+ double sum = 0;
+ size_t i;
+
+ if (!data || !len)
+ return .0;
+
+ for (i = 0; i < len; i++) {
+ const double sample = data[i];
+
+ sum += sample * sample;
+ }
+
+ return sqrt(sum / (double)len);
+}
+
+
+/**
+ * Calculate the audio level in dBov from a set of audio samples.
+ * dBov is the level, in decibels, relative to the overload point
+ * of the system
+ *
+ * @param sampv Audio samples
+ * @param sampc Number of audio samples
+ *
+ * @return Audio level expressed in dBov
+ */
+double aulevel_calc_dbov(const int16_t *sampv, size_t sampc)
+{
+ static const double peak = 32767.0;
+ double rms, dbov;
+
+ if (!sampv || !sampc)
+ return AULEVEL_MIN;
+
+ rms = calc_rms(sampv, sampc) / peak;
+
+ dbov = 20 * log10(rms);
+
+ if (dbov < AULEVEL_MIN)
+ dbov = AULEVEL_MIN;
+ else if (dbov > AULEVEL_MAX)
+ dbov = AULEVEL_MAX;
+
+ return dbov;
+}
diff --git a/src/auplay.c b/src/auplay.c
new file mode 100644
index 0000000..38a7010
--- /dev/null
+++ b/src/auplay.c
@@ -0,0 +1,109 @@
+/**
+ * @file auplay.c Audio Player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static void destructor(void *arg)
+{
+ struct auplay *ap = arg;
+
+ list_unlink(&ap->le);
+}
+
+
+/**
+ * Register an Audio Player
+ *
+ * @param app Pointer to allocated Audio Player object
+ * @param auplayl List of Audio Players
+ * @param name Audio Player name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_register(struct auplay **app, struct list *auplayl,
+ const char *name, auplay_alloc_h *alloch)
+{
+ struct auplay *ap;
+
+ if (!app)
+ return EINVAL;
+
+ ap = mem_zalloc(sizeof(*ap), destructor);
+ if (!ap)
+ return ENOMEM;
+
+ list_append(auplayl, &ap->le, ap);
+
+ ap->name = name;
+ ap->alloch = alloch;
+
+ info("auplay: %s\n", name);
+
+ *app = ap;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Player by name
+ *
+ * @param auplayl List of Audio Players
+ * @param name Name of the Audio Player to find
+ *
+ * @return Matching Audio Player if found, otherwise NULL
+ */
+const struct auplay *auplay_find(const struct list *auplayl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(auplayl); le; le=le->next) {
+
+ struct auplay *ap = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, ap->name))
+ continue;
+
+ return ap;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Player state
+ *
+ * @param stp Pointer to allocated Audio Player state
+ * @param auplayl List of Audio Players
+ * @param name Name of Audio Player
+ * @param prm Audio Player parameters
+ * @param device Name of Audio Player device (driver specific)
+ * @param wh Write handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int auplay_alloc(struct auplay_st **stp, struct list *auplayl,
+ const char *name,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay *ap;
+
+ ap = (struct auplay *)auplay_find(auplayl, name);
+ if (!ap)
+ return ENOENT;
+
+ if (!prm->srate || !prm->ch)
+ return EINVAL;
+
+ return ap->alloch(stp, ap, prm, device, wh, arg);
+}
diff --git a/src/ausrc.c b/src/ausrc.c
new file mode 100644
index 0000000..c1ca416
--- /dev/null
+++ b/src/ausrc.c
@@ -0,0 +1,108 @@
+/**
+ * @file ausrc.c Audio Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static void destructor(void *arg)
+{
+ struct ausrc *as = arg;
+
+ list_unlink(&as->le);
+}
+
+
+/**
+ * Register an Audio Source
+ *
+ * @param asp Pointer to allocated Audio Source object
+ * @param ausrcl List of Audio Sources
+ * @param name Audio Source name
+ * @param alloch Allocation handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_register(struct ausrc **asp, struct list *ausrcl,
+ const char *name, ausrc_alloc_h *alloch)
+{
+ struct ausrc *as;
+
+ if (!asp)
+ return EINVAL;
+
+ as = mem_zalloc(sizeof(*as), destructor);
+ if (!as)
+ return ENOMEM;
+
+ list_append(ausrcl, &as->le, as);
+
+ as->name = name;
+ as->alloch = alloch;
+
+ info("ausrc: %s\n", name);
+
+ *asp = as;
+
+ return 0;
+}
+
+
+/**
+ * Find an Audio Source by name
+ *
+ * @param ausrcl List of Audio Sources
+ * @param name Name of the Audio Source to find
+ *
+ * @return Matching Audio Source if found, otherwise NULL
+ */
+const struct ausrc *ausrc_find(const struct list *ausrcl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(ausrcl); le; le=le->next) {
+
+ struct ausrc *as = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, as->name))
+ continue;
+
+ return as;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate an Audio Source state
+ *
+ * @param stp Pointer to allocated Audio Source state
+ * @param ausrcl List of Audio Sources
+ * @param ctx Media context (optional)
+ * @param name Name of Audio Source
+ * @param prm Audio Source parameters
+ * @param device Name of Audio Source device (driver specific)
+ * @param rh Read handler
+ * @param errh Error handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ausrc_alloc(struct ausrc_st **stp, struct list *ausrcl,
+ struct media_ctx **ctx,
+ const char *name, struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc *as;
+
+ as = (struct ausrc *)ausrc_find(ausrcl, name);
+ if (!as)
+ return ENOENT;
+
+ return as->alloch(stp, as, ctx, prm, device, rh, errh, arg);
+}
diff --git a/src/baresip.c b/src/baresip.c
new file mode 100644
index 0000000..54a8c2c
--- /dev/null
+++ b/src/baresip.c
@@ -0,0 +1,248 @@
+/**
+ * @file baresip.c Top-level baresip struct
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * Top-level struct that holds all other subsystems
+ * (move this instance to main.c later)
+ */
+static struct baresip {
+ struct network *net;
+ struct contacts contacts;
+ struct commands *commands;
+ struct player *player;
+ struct message *message;
+ struct list mnatl;
+ struct list mencl;
+ struct list aucodecl;
+ struct list ausrcl;
+ struct list auplayl;
+ struct list aufiltl;
+ struct list vidcodecl;
+ struct list vidsrcl;
+ struct list vidispl;
+ struct list vidfiltl;
+ struct ui_sub uis;
+} baresip;
+
+
+static int cmd_quit(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "Quit\n");
+
+ ua_stop_all(false);
+
+ return err;
+}
+
+
+static int insmod_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ int err;
+
+ err = module_load(carg->prm);
+ if (err) {
+ return re_hprintf(pf, "insmod: ERROR: could not load module"
+ " '%s': %m\n", carg->prm, err);
+ }
+
+ return re_hprintf(pf, "loaded module %s\n", carg->prm);
+}
+
+
+static int rmmod_handler(struct re_printf *pf, void *arg)
+{
+ const struct cmd_arg *carg = arg;
+ (void)pf;
+
+ module_unload(carg->prm);
+
+ return 0;
+}
+
+
+static const struct cmd corecmdv[] = {
+ {"quit", 'q', 0, "Quit", cmd_quit },
+ {"insmod", 0, CMD_PRM, "Load module", insmod_handler },
+ {"rmmod", 0, CMD_PRM, "Unload module", rmmod_handler },
+};
+
+
+int baresip_init(struct config *cfg, bool prefer_ipv6)
+{
+ int err;
+
+ if (!cfg)
+ return EINVAL;
+
+ baresip.net = mem_deref(baresip.net);
+
+ list_init(&baresip.mnatl);
+ list_init(&baresip.mencl);
+ list_init(&baresip.aucodecl);
+ list_init(&baresip.ausrcl);
+ list_init(&baresip.auplayl);
+ list_init(&baresip.vidcodecl);
+ list_init(&baresip.vidsrcl);
+ list_init(&baresip.vidispl);
+ list_init(&baresip.vidfiltl);
+
+ /* Initialise Network */
+ err = net_alloc(&baresip.net, &cfg->net,
+ prefer_ipv6 ? AF_INET6 : AF_INET);
+ if (err) {
+ warning("ua: network init failed: %m\n", err);
+ return err;
+ }
+
+ err = contact_init(&baresip.contacts);
+ if (err)
+ return err;
+
+ err = cmd_init(&baresip.commands);
+ if (err)
+ return err;
+
+ err = play_init(&baresip.player);
+ if (err)
+ return err;
+
+ err = message_init(&baresip.message);
+ if (err) {
+ warning("baresip: message init failed: %m\n", err);
+ return err;
+ }
+
+ err = cmd_register(baresip.commands, corecmdv, ARRAY_SIZE(corecmdv));
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void baresip_close(void)
+{
+ cmd_unregister(baresip.commands, corecmdv);
+
+ baresip.message = mem_deref(baresip.message);
+ baresip.player = mem_deref(baresip.player);
+ baresip.commands = mem_deref(baresip.commands);
+ contact_close(&baresip.contacts);
+
+ baresip.net = mem_deref(baresip.net);
+
+ ui_reset(&baresip.uis);
+}
+
+
+struct network *baresip_network(void)
+{
+ return baresip.net;
+}
+
+
+struct contacts *baresip_contacts(void)
+{
+ return &baresip.contacts;
+}
+
+
+struct commands *baresip_commands(void)
+{
+ return baresip.commands;
+}
+
+
+struct player *baresip_player(void)
+{
+ return baresip.player;
+}
+
+
+struct list *baresip_mnatl(void)
+{
+ return &baresip.mnatl;
+}
+
+
+struct list *baresip_mencl(void)
+{
+ return &baresip.mencl;
+}
+
+
+struct message *baresip_message(void)
+{
+ return baresip.message;
+}
+
+
+/**
+ * Get the list of Audio Codecs
+ *
+ * @return List of audio-codecs
+ */
+struct list *baresip_aucodecl(void)
+{
+ return &baresip.aucodecl;
+}
+
+
+struct list *baresip_ausrcl(void)
+{
+ return &baresip.ausrcl;
+}
+
+
+struct list *baresip_auplayl(void)
+{
+ return &baresip.auplayl;
+}
+
+
+struct list *baresip_aufiltl(void)
+{
+ return &baresip.aufiltl;
+}
+
+
+struct list *baresip_vidcodecl(void)
+{
+ return &baresip.vidcodecl;
+}
+
+
+struct list *baresip_vidsrcl(void)
+{
+ return &baresip.vidsrcl;
+}
+
+
+struct list *baresip_vidispl(void)
+{
+ return &baresip.vidispl;
+}
+
+
+struct list *baresip_vidfiltl(void)
+{
+ return &baresip.vidfiltl;
+}
+
+
+struct ui_sub *baresip_uis(void)
+{
+ return &baresip.uis;
+}
diff --git a/src/bfcp.c b/src/bfcp.c
new file mode 100644
index 0000000..5b69142
--- /dev/null
+++ b/src/bfcp.c
@@ -0,0 +1,199 @@
+/**
+ * @file bfcp.c BFCP client
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct bfcp {
+ struct bfcp_conn *conn;
+ struct sdp_media *sdpm;
+ struct mnat_media *mnat_st;
+ bool active;
+
+ /* server */
+ uint32_t lconfid;
+ uint16_t luserid;
+};
+
+
+static void destructor(void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ mem_deref(bfcp->mnat_st);
+ mem_deref(bfcp->sdpm);
+ mem_deref(bfcp->conn);
+}
+
+
+static const char *bfcp_sdp_transp(enum bfcp_transp tp)
+{
+ switch (tp) {
+
+ case BFCP_UDP: return "UDP/BFCP";
+ case BFCP_DTLS: return "UDP/TLS/BFCP";
+ default: return NULL;
+ }
+}
+
+
+static enum bfcp_transp str2tp(const char *proto)
+{
+ if (0 == str_casecmp(proto, "udp"))
+ return BFCP_UDP;
+ else if (0 == str_casecmp(proto, "dtls"))
+ return BFCP_DTLS;
+ else {
+ warning("unsupported BFCP protocol: %s\n", proto);
+ return -1;
+ }
+}
+
+
+static void bfcp_resp_handler(int err, const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+ (void)bfcp;
+
+ if (err) {
+ warning("bfcp: error response: %m\n", err);
+ return;
+ }
+
+ info("bfcp: received BFCP response: '%s'\n",
+ bfcp_prim_name(msg->prim));
+}
+
+
+static void bfcp_msg_handler(const struct bfcp_msg *msg, void *arg)
+{
+ struct bfcp *bfcp = arg;
+
+ info("bfcp: received BFCP message '%s'\n", bfcp_prim_name(msg->prim));
+
+ switch (msg->prim) {
+
+ case BFCP_HELLO:
+ (void)bfcp_reply(bfcp->conn, msg, BFCP_HELLO_ACK, 0);
+ break;
+
+ default:
+ (void)bfcp_ereply(bfcp->conn, msg, BFCP_UNKNOWN_PRIM);
+ break;
+ }
+}
+
+
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess)
+{
+ struct bfcp *bfcp;
+ struct sa laddr;
+ enum bfcp_transp transp;
+ int err;
+
+ if (!bfcpp || !sdp_sess)
+ return EINVAL;
+
+ transp = str2tp(proto);
+
+ bfcp = mem_zalloc(sizeof(*bfcp), destructor);
+ if (!bfcp)
+ return ENOMEM;
+
+ bfcp->active = offerer;
+
+ sa_init(&laddr, AF_INET);
+
+ err = bfcp_listen(&bfcp->conn, transp, &laddr, uag_tls(),
+ bfcp_msg_handler, bfcp);
+ if (err)
+ goto out;
+
+ err = sdp_media_add(&bfcp->sdpm, sdp_sess, "application",
+ sa_port(&laddr), bfcp_sdp_transp(transp));
+ if (err)
+ goto out;
+
+ err = sdp_format_add(NULL, bfcp->sdpm, false, "*", NULL,
+ 0, 0, NULL, NULL, NULL, false, NULL);
+ if (err)
+ goto out;
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "floorctrl", "c-s");
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "setup",
+ bfcp->active ? "active" : "actpass");
+
+ if (bfcp->active) {
+ err |= sdp_media_set_lattr(bfcp->sdpm, true,
+ "connection", "new");
+ }
+ else {
+ bfcp->lconfid = 1000 + (rand_u16() & 0xf);
+ bfcp->luserid = 1 + (rand_u16() & 0x7);
+
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "confid",
+ "%u", bfcp->lconfid);
+ err |= sdp_media_set_lattr(bfcp->sdpm, true, "userid",
+ "%u", bfcp->luserid);
+ }
+
+ if (err)
+ goto out;
+
+ if (mnat) {
+ info("bfcp: enabled medianat '%s' on UDP socket\n", mnat->id);
+
+ err = mnat->mediah(&bfcp->mnat_st, mnat_sess, IPPROTO_UDP,
+ bfcp_sock(bfcp->conn), NULL, bfcp->sdpm);
+ if (err)
+ goto out;
+ }
+
+ info("bfcp: %s BFCP agent protocol '%s' on port %d\n",
+ bfcp->active ? "Active" : "Passive",
+ proto, sa_port(&laddr));
+
+ out:
+ if (err)
+ mem_deref(bfcp);
+ else
+ *bfcpp = bfcp;
+
+ return err;
+}
+
+
+int bfcp_start(struct bfcp *bfcp)
+{
+ const struct sa *paddr;
+ uint32_t confid = 0;
+ uint16_t userid = 0;
+ int err = 0;
+
+ if (!bfcp)
+ return EINVAL;
+
+ if (!sdp_media_rport(bfcp->sdpm)) {
+ info("bfcp channel is disabled\n");
+ return 0;
+ }
+
+ if (bfcp->active) {
+
+ paddr = sdp_media_raddr(bfcp->sdpm);
+ confid = sdp_media_rattr_u32(bfcp->sdpm, "confid");
+ userid = sdp_media_rattr_u32(bfcp->sdpm, "userid");
+
+ err = bfcp_request(bfcp->conn, paddr, BFCP_VER2, BFCP_HELLO,
+ confid, userid, bfcp_resp_handler, bfcp, 0);
+ }
+
+ return err;
+}
diff --git a/src/call.c b/src/call.c
new file mode 100644
index 0000000..99bec4b
--- /dev/null
+++ b/src/call.c
@@ -0,0 +1,1877 @@
+/**
+ * @file src/call.c Call Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0xca11ca11
+#include "magic.h"
+
+
+#define FOREACH_STREAM \
+ for (le = call->streaml.head; le; le = le->next)
+
+/** Call constants */
+enum {
+ PTIME = 20, /**< Packet time for audio */
+};
+
+
+/** Call States */
+enum state {
+ STATE_IDLE = 0,
+ STATE_INCOMING,
+ STATE_OUTGOING,
+ STATE_RINGING,
+ STATE_EARLY,
+ STATE_ESTABLISHED,
+ STATE_TERMINATED
+};
+
+/** SIP Call Control object */
+struct call {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< SIP User-agent */
+ struct account *acc; /**< Account (ref.) */
+ struct sipsess *sess; /**< SIP Session */
+ struct sdp_session *sdp; /**< SDP Session */
+ struct sipsub *sub; /**< Call transfer REFER subscription */
+ struct sipnot *not; /**< REFER/NOTIFY client */
+ struct list streaml; /**< List of mediastreams (struct stream) */
+ struct audio *audio; /**< Audio stream */
+#ifdef USE_VIDEO
+ struct video *video; /**< Video stream */
+ struct bfcp *bfcp; /**< BFCP Client */
+#endif
+ enum state state; /**< Call state */
+ char *local_uri; /**< Local SIP uri */
+ char *local_name; /**< Local display name */
+ char *peer_uri; /**< Peer SIP Address */
+ char *peer_name; /**< Peer display name */
+ struct tmr tmr_inv; /**< Timer for incoming calls */
+ struct tmr tmr_dtmf; /**< Timer for incoming DTMF events */
+ time_t time_start; /**< Time when call started */
+ time_t time_conn; /**< Time when call initiated */
+ time_t time_stop; /**< Time when call stopped */
+ bool outgoing; /**< True if outgoing, false if incoming */
+ bool got_offer; /**< Got SDP Offer from Peer */
+ bool on_hold; /**< True if call is on hold */
+ struct mnat_sess *mnats; /**< Media NAT session */
+ bool mnat_wait; /**< Waiting for MNAT to establish */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ int af; /**< Preferred Address Family */
+ uint16_t scode; /**< Termination status code */
+ call_event_h *eh; /**< Event handler */
+ call_dtmf_h *dtmfh; /**< DTMF handler */
+ void *arg; /**< Handler argument */
+
+ struct config_avt config_avt; /**< AVT config */
+ struct config_call config_call; /**< Call config */
+
+ uint32_t rtp_timeout_ms; /**< RTP Timeout in [ms] */
+ uint32_t linenum; /**< Line number from 1 to N */
+};
+
+
+static int send_invite(struct call *call);
+
+
+static const char *state_name(enum state st)
+{
+ switch (st) {
+
+ case STATE_IDLE: return "IDLE";
+ case STATE_INCOMING: return "INCOMING";
+ case STATE_OUTGOING: return "OUTGOING";
+ case STATE_RINGING: return "RINGING";
+ case STATE_EARLY: return "EARLY";
+ case STATE_ESTABLISHED: return "ESTABLISHED";
+ case STATE_TERMINATED: return "TERMINATED";
+ default: return "???";
+ }
+}
+
+
+static void set_state(struct call *call, enum state st)
+{
+ call->state = st;
+}
+
+
+static void call_stream_start(struct call *call, bool active)
+{
+ const struct sdp_format *sc;
+ int err;
+
+ /* Audio Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+
+ if (ac) {
+ err = audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ warning("call: start:"
+ " audio_encoder_set error: %m\n", err);
+ }
+ err |= audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ warning("call: start:"
+ " audio_decoder_set error: %m\n", err);
+ }
+
+ if (!err) {
+ err = audio_start(call->audio);
+ if (err) {
+ warning("call: start:"
+ " audio_start error: %m\n",
+ err);
+ }
+ }
+ }
+ else {
+ info("call: no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("call: audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ /* Video Stream */
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data, sc->pt,
+ sc->params);
+ err |= video_decoder_set(call->video, sc->data, sc->pt,
+ sc->rparams);
+ if (!err && !video_is_started(call->video)) {
+ err = video_start(call->video, call->peer_uri);
+ }
+ if (err) {
+ warning("call: video stream error: %m\n", err);
+ }
+ }
+ else if (call->video) {
+ info("call: video stream is disabled..\n");
+ }
+
+ if (call->bfcp) {
+ err = bfcp_start(call->bfcp);
+ if (err) {
+ warning("call: could not start BFCP: %m\n", err);
+ }
+ }
+#endif
+
+ if (active) {
+ struct le *le;
+
+ tmr_cancel(&call->tmr_inv);
+ call->time_start = time(NULL);
+
+ FOREACH_STREAM {
+ stream_reset(le->data);
+ }
+ }
+}
+
+
+static void call_stream_stop(struct call *call)
+{
+ if (!call)
+ return;
+
+ call->time_stop = time(NULL);
+
+ /* Audio */
+ audio_stop(call->audio);
+
+ /* Video */
+#ifdef USE_VIDEO
+ video_stop(call->video);
+#endif
+
+ tmr_cancel(&call->tmr_inv);
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *fmt, ...)
+{
+ call_event_h *eh = call->eh;
+ void *eh_arg = call->arg;
+ char buf[256];
+ va_list ap;
+
+ if (!eh)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ eh(call, ev, buf, eh_arg);
+}
+
+
+static void invite_timeout(void *arg)
+{
+ struct call *call = arg;
+
+ info("%s: Local timeout after %u seconds\n",
+ call->peer_uri, call->config_call.local_timeout);
+
+ call_event_handler(call, CALL_EVENT_CLOSED, "Local timeout");
+}
+
+
+/** Called when all media streams are established */
+static void mnat_handler(int err, uint16_t scode, const char *reason,
+ void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ warning("call: medianat '%s' failed: %m\n",
+ call->acc->mnatid, err);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%m", err);
+ return;
+ }
+ else if (scode) {
+ warning("call: medianat failed: %u %s\n", scode, reason);
+ call_event_handler(call, CALL_EVENT_CLOSED, "%u %s",
+ scode, reason);
+ return;
+ }
+
+ info("call: media-nat `%s' established\n", call->acc->mnatid);
+
+ /* Re-INVITE */
+ if (!call->mnat_wait) {
+ info("call: medianat established -- sending Re-INVITE\n");
+ (void)call_modify(call);
+ return;
+ }
+
+ call->mnat_wait = false;
+
+ switch (call->state) {
+
+ case STATE_OUTGOING:
+ (void)send_invite(call);
+ break;
+
+ case STATE_INCOMING:
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int update_media(struct call *call)
+{
+ const struct sdp_format *sc;
+ struct le *le;
+ int err = 0;
+
+ debug("call: update media\n");
+
+ /* media attributes */
+ audio_sdp_attr_decode(call->audio);
+
+#ifdef USE_VIDEO
+ if (call->video)
+ video_sdp_attr_decode(call->video);
+#endif
+
+ /* Update each stream */
+ FOREACH_STREAM {
+ stream_update(le->data);
+ }
+
+ if (call->acc->mnat && call->acc->mnat->updateh && call->mnats)
+ err = call->acc->mnat->updateh(call->mnats);
+
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (sc) {
+ struct aucodec *ac = sc->data;
+ if (ac) {
+ err = audio_decoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ err |= audio_encoder_set(call->audio, sc->data,
+ sc->pt, sc->params);
+ }
+ else {
+ info("no common audio-codecs..\n");
+ }
+ }
+ else {
+ info("audio stream is disabled..\n");
+ }
+
+#ifdef USE_VIDEO
+ sc = sdp_media_rformat(stream_sdpmedia(video_strm(call->video)), NULL);
+ if (sc) {
+ err = video_encoder_set(call->video, sc->data,
+ sc->pt, sc->params);
+ if (err) {
+ warning("call: video stream error: %m\n", err);
+ return err;
+ }
+
+ if (!video_is_started(call->video)) {
+ err = video_start(call->video, call->peer_uri);
+ if (err) {
+ warning("call: update: failed to"
+ " start video (%m)\n", err);
+ }
+ }
+ }
+ else if (call->video) {
+ info("video stream is disabled..\n");
+ video_stop(call->video);
+ }
+#endif
+
+ return err;
+}
+
+
+static void print_summary(const struct call *call)
+{
+ uint32_t dur = call_duration(call);
+ if (!dur)
+ return;
+
+ info("%s: Call with %s terminated (duration: %H)\n",
+ call->local_uri, call->peer_uri, fmt_human_time, &dur);
+}
+
+
+static void call_destructor(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->state != STATE_IDLE)
+ print_summary(call);
+
+ call_stream_stop(call);
+ list_unlink(&call->le);
+ tmr_cancel(&call->tmr_dtmf);
+
+ mem_deref(call->sess);
+ mem_deref(call->local_uri);
+ mem_deref(call->local_name);
+ mem_deref(call->peer_uri);
+ mem_deref(call->peer_name);
+ mem_deref(call->audio);
+#ifdef USE_VIDEO
+ mem_deref(call->video);
+ mem_deref(call->bfcp);
+#endif
+ mem_deref(call->sdp);
+ mem_deref(call->mnats);
+ mem_deref(call->mencs);
+ mem_deref(call->sub);
+ mem_deref(call->not);
+ mem_deref(call->acc);
+}
+
+
+static void audio_event_handler(int key, bool end, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ info("received event: '%c' (end=%d)\n", key, end);
+
+ if (call->dtmfh)
+ call->dtmfh(call, end ? KEYCODE_REL : key, call->arg);
+}
+
+
+static void audio_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ if (err) {
+ warning("call: audio device error: %m (%s)\n", err, str);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+
+
+#ifdef USE_VIDEO
+static void video_error_handler(int err, const char *str, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ warning("call: video device error: %m (%s)\n", err, str);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, str);
+}
+#endif
+
+
+static void menc_error_handler(int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ warning("call: mediaenc '%s' error: %m\n", call->acc->mencid, err);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "mediaenc failed");
+}
+
+
+static void stream_error_handler(struct stream *strm, int err, void *arg)
+{
+ struct call *call = arg;
+ MAGIC_CHECK(call);
+
+ info("call: error in \"%s\" rtp stream (%m)\n",
+ sdp_media_name(stream_sdpmedia(strm)), err);
+
+ call->scode = 701;
+ set_state(call, STATE_TERMINATED);
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, "rtp stream error");
+}
+
+
+static int assign_linenum(uint32_t *linenum, const struct list *lst)
+{
+ uint32_t num;
+
+ for (num=CALL_LINENUM_MIN; num<CALL_LINENUM_MAX; num++) {
+
+ if (!call_find_linenum(lst, num)) {
+ *linenum = num;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+
+/**
+ * Allocate a new Call state object
+ *
+ * @param callp Pointer to allocated Call state object
+ * @param cfg Global configuration
+ * @param lst List of call objects
+ * @param local_name Local display name (optional)
+ * @param local_uri Local SIP uri
+ * @param acc Account parameters
+ * @param ua User-Agent
+ * @param prm Call parameters
+ * @param msg SIP message for incoming calls
+ * @param xcall Optional call to inherit properties from
+ * @param dnsc DNS Client
+ * @param eh Call event handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_alloc(struct call **callp, const struct config *cfg, struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ struct dnsc *dnsc,
+ call_event_h *eh, void *arg)
+{
+ struct call *call;
+ struct le *le;
+ struct stream_param stream_prm;
+ enum vidmode vidmode = prm ? prm->vidmode : VIDMODE_OFF;
+ bool use_video = true, got_offer = false;
+ int label = 0;
+ int err = 0;
+
+ if (!cfg || !local_uri || !acc || !ua || !prm)
+ return EINVAL;
+
+ debug("call: alloc with params laddr=%j, af=%s, use_rtp=%d\n",
+ &prm->laddr, net_af2name(prm->af), prm->use_rtp);
+
+ memset(&stream_prm, 0, sizeof(stream_prm));
+ stream_prm.use_rtp = prm->use_rtp;
+
+ call = mem_zalloc(sizeof(*call), call_destructor);
+ if (!call)
+ return ENOMEM;
+
+ MAGIC_INIT(call);
+
+ call->config_avt = cfg->avt;
+ call->config_call = cfg->call;
+
+ tmr_init(&call->tmr_inv);
+
+ call->acc = mem_ref(acc);
+ call->ua = ua;
+ call->state = STATE_IDLE;
+ call->eh = eh;
+ call->arg = arg;
+ call->af = prm ? prm->af : AF_INET;
+
+ err = str_dup(&call->local_uri, local_uri);
+ if (local_name)
+ err |= str_dup(&call->local_name, local_name);
+ if (err)
+ goto out;
+
+ /* Init SDP info */
+ err = sdp_session_alloc(&call->sdp, &prm->laddr);
+ if (err)
+ goto out;
+
+ err = sdp_session_set_lattr(call->sdp, true,
+ "tool", "baresip " BARESIP_VERSION);
+ if (err)
+ goto out;
+
+ /* Check for incoming SDP Offer */
+ if (msg && mbuf_get_left(msg->mb))
+ got_offer = true;
+
+ /* Initialise media NAT handling */
+ if (acc->mnat) {
+ err = acc->mnat->sessh(&call->mnats,
+ dnsc, call->af,
+ acc->stun_host, acc->stun_port,
+ acc->stun_user, acc->stun_pass,
+ call->sdp, !got_offer,
+ mnat_handler, call);
+ if (err) {
+ warning("call: medianat session: %m\n", err);
+ goto out;
+ }
+ }
+ call->mnat_wait = true;
+
+ /* Media encryption */
+ if (acc->menc) {
+ if (acc->menc->sessh) {
+ err = acc->menc->sessh(&call->mencs, call->sdp,
+ !got_offer,
+ menc_error_handler, call);
+ if (err) {
+ warning("call: mediaenc session: %m\n", err);
+ goto out;
+ }
+ }
+ }
+
+ /* Audio stream */
+ err = audio_alloc(&call->audio, &stream_prm, cfg, call,
+ call->sdp, ++label,
+ acc->mnat, call->mnats, acc->menc, call->mencs,
+ acc->ptime, account_aucodecl(call->acc), !got_offer,
+ audio_event_handler, audio_error_handler, call);
+ if (err)
+ goto out;
+
+#ifdef USE_VIDEO
+ /* We require at least one video codec, and at least one
+ video source or video display */
+ use_video = (vidmode != VIDMODE_OFF)
+ && (list_head(account_vidcodecl(call->acc)) != NULL)
+ && (NULL != vidsrc_find(baresip_vidsrcl(), NULL)
+ || NULL != vidisp_find(baresip_vidispl(), NULL));
+
+ debug("call: use_video=%d\n", use_video);
+
+ /* Video stream */
+ if (use_video) {
+ err = video_alloc(&call->video, &stream_prm, cfg,
+ call, call->sdp, ++label,
+ acc->mnat, call->mnats,
+ acc->menc, call->mencs,
+ "main",
+ account_vidcodecl(call->acc),
+ video_error_handler, call);
+ if (err)
+ goto out;
+ }
+
+ if (str_isset(cfg->bfcp.proto)) {
+
+ err = bfcp_alloc(&call->bfcp, call->sdp,
+ cfg->bfcp.proto, !got_offer,
+ acc->mnat, call->mnats);
+ if (err)
+ goto out;
+ }
+#else
+ (void)use_video;
+ (void)vidmode;
+#endif
+
+ /* inherit certain properties from original call */
+ if (xcall) {
+ call->not = mem_ref(xcall->not);
+ }
+
+ FOREACH_STREAM {
+ struct stream *strm = le->data;
+ stream_set_error_handler(strm, stream_error_handler, call);
+ }
+
+ if (cfg->avt.rtp_timeout) {
+ call_enable_rtp_timeout(call, cfg->avt.rtp_timeout*1000);
+ }
+
+ err = assign_linenum(&call->linenum, lst);
+ if (err) {
+ warning("call: could not assign linenumber\n");
+ goto out;
+ }
+
+ /* NOTE: The new call must always be added to the tail of list,
+ * which indicates the current call.
+ */
+ list_append(lst, &call->le, call);
+
+ out:
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ return err;
+}
+
+
+int call_connect(struct call *call, const struct pl *paddr)
+{
+ struct sip_addr addr;
+ int err;
+
+ if (!call || !paddr)
+ return EINVAL;
+
+ info("call: connecting to '%r'..\n", paddr);
+
+ call->outgoing = true;
+
+ /* if the peer-address is a full SIP address then we need
+ * to parse it and extract the SIP uri part.
+ */
+ if (0 == sip_addr_decode(&addr, paddr) && addr.dname.p) {
+ err = pl_strdup(&call->peer_uri, &addr.auri);
+ }
+ else {
+ err = pl_strdup(&call->peer_uri, paddr);
+ }
+ if (err)
+ return err;
+
+ set_state(call, STATE_OUTGOING);
+
+ /* If we are using asyncronous medianat like STUN/TURN, then
+ * wait until completed before sending the INVITE */
+ if (!call->acc->mnat)
+ err = send_invite(call);
+
+ return err;
+}
+
+
+/**
+ * Update the current call by sending Re-INVITE or UPDATE
+ *
+ * @param call Call object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_modify(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ err = call_sdp_get(call, &desc, true);
+ if (!err)
+ err = sipsess_modify(call->sess, desc);
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+int call_hangup(struct call *call, uint16_t scode, const char *reason)
+{
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ if (call->config_avt.rtp_stats)
+ call_set_xrtpstat(call);
+
+ switch (call->state) {
+
+ case STATE_INCOMING:
+ if (scode < 400) {
+ scode = 486;
+ reason = "Rejected";
+ }
+ info("call: rejecting incoming call from %s (%u %s)\n",
+ call->peer_uri, scode, reason);
+ (void)sipsess_reject(call->sess, scode, reason, NULL);
+ break;
+
+ default:
+ info("call: terminate call '%s' with %s\n",
+ sip_dialog_callid(sipsess_dialog(call->sess)),
+ call->peer_uri);
+
+ call->sess = mem_deref(call->sess);
+ break;
+ }
+
+ set_state(call, STATE_TERMINATED);
+
+ call_stream_stop(call);
+
+ return err;
+}
+
+
+int call_progress(struct call *call)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ tmr_cancel(&call->tmr_inv);
+
+ err = call_sdp_get(call, &desc, false);
+ if (err)
+ return err;
+
+ err = sipsess_progress(call->sess, 183, "Session Progress",
+ desc, "Allow: %s\r\n", uag_allowed_methods());
+
+ if (!err)
+ call_stream_start(call, false);
+
+ mem_deref(desc);
+
+ return 0;
+}
+
+
+int call_answer(struct call *call, uint16_t scode)
+{
+ struct mbuf *desc;
+ int err;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ if (STATE_INCOMING != call->state) {
+ info("call: answer: call is not in incoming state (%s)\n",
+ state_name(call->state));
+ return 0;
+ }
+
+ info("answering call from %s with %u\n", call->peer_uri, scode);
+
+ if (call->got_offer) {
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ err = sdp_encode(&desc, call->sdp, !call->got_offer);
+ if (err)
+ return err;
+
+ err = sipsess_answer(call->sess, scode, "Answering", desc,
+ "Allow: %s\r\n", uag_allowed_methods());
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Check if the current call has an active audio stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_audio(const struct call *call)
+{
+ if (!call)
+ return false;
+
+ return sdp_media_has_media(stream_sdpmedia(audio_strm(call->audio)));
+}
+
+
+/**
+ * Check if the current call has an active video stream
+ *
+ * @param call Call object
+ *
+ * @return True if active stream, otherwise false
+ */
+bool call_has_video(const struct call *call)
+{
+ if (!call)
+ return false;
+
+#ifdef USE_VIDEO
+ return sdp_media_has_media(stream_sdpmedia(video_strm(call->video)));
+#else
+ return false;
+#endif
+}
+
+
+/**
+ * Put the current call on hold/resume
+ *
+ * @param call Call object
+ * @param hold True to hold, false to resume
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_hold(struct call *call, bool hold)
+{
+ struct le *le;
+
+ if (!call || !call->sess)
+ return EINVAL;
+
+ if (hold == call->on_hold)
+ return 0;
+
+ info("call: %s %s\n", hold ? "hold" : "resume", call->peer_uri);
+
+ call->on_hold = hold;
+
+ FOREACH_STREAM
+ stream_hold(le->data, hold);
+
+ return call_modify(call);
+}
+
+
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer)
+{
+ return sdp_encode(descp, call->sdp, offer);
+}
+
+
+const char *call_peeruri(const struct call *call)
+{
+ return call ? call->peer_uri : NULL;
+}
+
+
+const char *call_localuri(const struct call *call)
+{
+ return call ? call->local_uri : NULL;
+}
+
+
+/**
+ * Get the name of the peer
+ *
+ * @param call Call object
+ *
+ * @return Peer name
+ */
+const char *call_peername(const struct call *call)
+{
+ return call ? call->peer_name : NULL;
+}
+
+
+int call_debug(struct re_printf *pf, const struct call *call)
+{
+ int err;
+
+ if (!call)
+ return 0;
+
+ err = re_hprintf(pf, "===== Call debug (%s) =====\n",
+ state_name(call->state));
+
+ /* SIP Session debug */
+ err |= re_hprintf(pf,
+ " local_uri: %s <%s>\n"
+ " peer_uri: %s <%s>\n"
+ " af=%s\n",
+ call->local_name, call->local_uri,
+ call->peer_name, call->peer_uri,
+ net_af2name(call->af));
+ err |= re_hprintf(pf, " direction: %s\n",
+ call->outgoing ? "Outgoing" : "Incoming");
+
+ /* SDP debug */
+ err |= sdp_session_debug(pf, call->sdp);
+
+ return err;
+}
+
+
+static int print_duration(struct re_printf *pf, const struct call *call)
+{
+ const uint32_t dur = call_duration(call);
+ const uint32_t sec = dur%60%60;
+ const uint32_t min = dur/60%60;
+ const uint32_t hrs = dur/60/60;
+
+ return re_hprintf(pf, "%u:%02u:%02u", hrs, min, sec);
+}
+
+
+int call_status(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ switch (call->state) {
+
+ case STATE_EARLY:
+ case STATE_ESTABLISHED:
+ break;
+ default:
+ return 0;
+ }
+
+ err = re_hprintf(pf, "\r[%H]", print_duration, call);
+
+ FOREACH_STREAM
+ err |= stream_print(pf, le->data);
+
+ err |= re_hprintf(pf, " (bit/s)");
+
+#ifdef USE_VIDEO
+ if (call->video)
+ err |= video_print(pf, call->video);
+#endif
+
+ return err;
+}
+
+
+int call_jbuf_stat(struct re_printf *pf, const struct call *call)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!call)
+ return EINVAL;
+
+ FOREACH_STREAM
+ err |= stream_jbuf_stat(pf, le->data);
+
+ return err;
+}
+
+
+int call_info(struct re_printf *pf, const struct call *call)
+{
+ if (!call)
+ return 0;
+
+ return re_hprintf(pf, "[line %u] %H %9s %s %s", call->linenum,
+ print_duration, call,
+ state_name(call->state),
+ call->on_hold ? "(on hold)" : " ",
+ call->peer_uri);
+}
+
+
+/**
+ * Send a DTMF digit to the peer
+ *
+ * @param call Call object
+ * @param key DTMF digit to send (KEYCODE_REL for key release)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_send_digit(struct call *call, char key)
+{
+ if (!call)
+ return EINVAL;
+
+ return audio_send_digit(call->audio, key);
+}
+
+
+struct ua *call_get_ua(const struct call *call)
+{
+ return call ? call->ua : NULL;
+}
+
+
+struct account *call_account(const struct call *call)
+{
+ return call ? call->acc : NULL;
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int sipsess_offer_handler(struct mbuf **descp,
+ const struct sip_msg *msg, void *arg)
+{
+ const bool got_offer = mbuf_get_left(msg->mb);
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ info("call: got re-INVITE%s\n", got_offer ? " (SDP Offer)" : "");
+
+ if (got_offer) {
+
+ /* Decode SDP Offer */
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err) {
+ warning("call: reinvite: could not decode SDP offer:"
+ " %m\n", err);
+ return err;
+ }
+
+ err = update_media(call);
+ if (err)
+ return err;
+ }
+
+ /* Encode SDP Answer */
+ return sdp_encode(descp, call->sdp, !got_offer);
+}
+
+
+static int sipsess_answer_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ int err;
+
+ MAGIC_CHECK(call);
+
+ if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed"))
+ (void)sdp_decode_multipart(&msg->ctyp.params, msg->mb);
+
+ err = sdp_decode(call->sdp, msg->mb, false);
+ if (err) {
+ warning("call: could not decode SDP answer: %m\n", err);
+ return err;
+ }
+
+ err = update_media(call);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+static void sipsess_estab_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+
+ MAGIC_CHECK(call);
+
+ (void)msg;
+
+ if (call->state == STATE_ESTABLISHED)
+ return;
+
+ set_state(call, STATE_ESTABLISHED);
+
+ call_stream_start(call, true);
+
+ if (call->rtp_timeout_ms) {
+
+ struct le *le;
+
+ FOREACH_STREAM {
+ struct stream *strm = le->data;
+ stream_enable_rtp_timeout(strm, call->rtp_timeout_ms);
+ }
+ }
+
+ /* the transferor will hangup this call */
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 200, "OK");
+ }
+
+ /* must be done last, the handler might deref this call */
+ call_event_handler(call, CALL_EVENT_ESTABLISHED, call->peer_uri);
+}
+
+
+#ifdef USE_VIDEO
+static void call_handle_info_req(struct call *call, const struct sip_msg *req)
+{
+ struct pl body;
+ bool pfu;
+ int err;
+
+ (void)call;
+
+ pl_set_mbuf(&body, req->mb);
+
+ err = mctrl_handle_media_control(&body, &pfu);
+ if (err)
+ return;
+
+ if (pfu) {
+ video_update_picture(call->video);
+ }
+}
+#endif
+
+
+static void dtmfend_handler(void *arg)
+{
+ struct call *call = arg;
+
+ if (call->dtmfh)
+ call->dtmfh(call, KEYCODE_REL, call->arg);
+}
+
+
+static void sipsess_info_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (msg_ctype_cmp(&msg->ctyp, "application", "dtmf-relay")) {
+
+ struct pl body, sig, dur;
+ int err;
+
+ pl_set_mbuf(&body, msg->mb);
+
+ err = re_regex(body.p, body.l, "Signal=[0-9*#a-d]+", &sig);
+ err |= re_regex(body.p, body.l, "Duration=[0-9]+", &dur);
+
+ if (err || !pl_isset(&sig) || sig.l == 0) {
+ (void)sip_reply(sip, msg, 400, "Bad Request");
+ }
+ else {
+ char s = toupper(sig.p[0]);
+ uint32_t duration = pl_u32(&dur);
+
+ info("received DTMF: '%c' (duration=%r)\n", s, &dur);
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ if (call->dtmfh) {
+ tmr_start(&call->tmr_dtmf, duration,
+ dtmfend_handler, call);
+ call->dtmfh(call, s, call->arg);
+ }
+ }
+ }
+#ifdef USE_VIDEO
+ else if (msg_ctype_cmp(&msg->ctyp,
+ "application", "media_control+xml")) {
+ call_handle_info_req(call, msg);
+ (void)sip_reply(sip, msg, 200, "OK");
+ }
+#endif
+ else {
+ (void)sip_reply(sip, msg, 488, "Not Acceptable Here");
+ }
+}
+
+
+static void sipnot_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+
+ if (err)
+ info("call: notification closed: %m\n", err);
+ else if (msg)
+ info("call: notification closed: %u %r\n",
+ msg->scode, &msg->reason);
+
+ call->not = mem_deref(call->not);
+}
+
+
+static void sipsess_refer_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ const struct sip_hdr *hdr;
+ int err;
+
+ /* get the transfer target */
+ hdr = sip_msg_hdr(msg, SIP_HDR_REFER_TO);
+ if (!hdr) {
+ warning("call: bad REFER request from %r\n", &msg->from.auri);
+ (void)sip_reply(sip, msg, 400, "Missing Refer-To header");
+ return;
+ }
+
+ /* The REFER creates an implicit subscription.
+ * Reply 202 to the REFER request
+ */
+ call->not = mem_deref(call->not);
+ err = sipevent_accept(&call->not, uag_sipevent_sock(), msg,
+ sipsess_dialog(call->sess), NULL,
+ 202, "Accepted", 60, 60, 60,
+ ua_cuser(call->ua), "message/sipfrag",
+ auth_handler, call->acc, true,
+ sipnot_close_handler, call,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ warning("call: refer: sipevent_accept failed: %m\n", err);
+ return;
+ }
+
+ (void)call_notify_sipfrag(call, 100, "Trying");
+
+ call_event_handler(call, CALL_EVENT_TRANSFER, "%r", &hdr->val);
+}
+
+
+static void sipsess_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ char reason[128] = "";
+
+ MAGIC_CHECK(call);
+
+ if (err) {
+ info("%s: session closed: %m\n", call->peer_uri, err);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, 500, "%m", err);
+ }
+ }
+ else if (msg) {
+
+ call->scode = msg->scode;
+
+ (void)re_snprintf(reason, sizeof(reason), "%u %r",
+ msg->scode, &msg->reason);
+
+ info("%s: session closed: %u %r\n",
+ call->peer_uri, msg->scode, &msg->reason);
+
+ if (call->not) {
+ (void)call_notify_sipfrag(call, msg->scode,
+ "%r", &msg->reason);
+ }
+ }
+ else {
+ info("%s: session closed\n", call->peer_uri);
+ }
+
+ call_stream_stop(call);
+ call_event_handler(call, CALL_EVENT_CLOSED, reason);
+}
+
+
+static bool have_common_audio_codecs(const struct call *call)
+{
+ const struct sdp_format *sc;
+ struct aucodec *ac;
+
+ sc = sdp_media_rformat(stream_sdpmedia(audio_strm(call->audio)), NULL);
+ if (!sc)
+ return false;
+
+ ac = sc->data; /* note: this will exclude telephone-event */
+
+ return ac != NULL;
+}
+
+
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg)
+{
+ bool got_offer;
+ int err;
+
+ if (!call || !msg)
+ return EINVAL;
+
+ call->outgoing = false;
+
+ got_offer = (mbuf_get_left(msg->mb) > 0);
+
+ err = pl_strdup(&call->peer_uri, &msg->from.auri);
+ if (err)
+ return err;
+
+ if (pl_isset(&msg->from.dname)) {
+ err = pl_strdup(&call->peer_name, &msg->from.dname);
+ if (err)
+ return err;
+ }
+
+ if (got_offer) {
+ struct sdp_media *m;
+ const struct sa *raddr;
+
+ err = sdp_decode(call->sdp, msg->mb, true);
+ if (err)
+ return err;
+
+ call->got_offer = true;
+
+ /*
+ * Each media description in the SDP answer MUST
+ * use the same network type as the corresponding
+ * media description in the offer.
+ *
+ * See RFC 6157
+ */
+ m = stream_sdpmedia(audio_strm(call->audio));
+ raddr = sdp_media_raddr(m);
+
+ if (sa_af(raddr) != call->af) {
+ info("call: incompatible address-family"
+ " (local=%s, remote=%s)\n",
+ net_af2name(call->af),
+ net_af2name(sa_af(raddr)));
+
+ sip_treply(NULL, uag_sip(), msg,
+ 488, "Not Acceptable Here");
+
+ call_event_handler(call, CALL_EVENT_CLOSED,
+ "Wrong address family");
+ return 0;
+ }
+
+ /* Check if we have any common audio codecs, after
+ * the SDP offer has been parsed
+ */
+ if (!have_common_audio_codecs(call)) {
+ info("call: no common audio codecs - rejected\n");
+
+ sip_treply(NULL, uag_sip(), msg,
+ 488, "Not Acceptable Here");
+
+ call_event_handler(call, CALL_EVENT_CLOSED,
+ "No audio codecs");
+
+ return 0;
+ }
+ }
+
+ err = sipsess_accept(&call->sess, sess_sock, msg, 180, "Ringing",
+ ua_cuser(call->ua), "application/sdp", NULL,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_estab_handler, sipsess_info_handler,
+ sipsess_refer_handler, sipsess_close_handler,
+ call, "Allow: %s\r\n", uag_allowed_methods());
+ if (err) {
+ warning("call: sipsess_accept: %m\n", err);
+ return err;
+ }
+
+ set_state(call, STATE_INCOMING);
+
+ /* New call */
+ if (call->config_call.local_timeout) {
+ tmr_start(&call->tmr_inv, call->config_call.local_timeout*1000,
+ invite_timeout, call);
+ }
+
+ if (!call->acc->mnat)
+ call_event_handler(call, CALL_EVENT_INCOMING, call->peer_uri);
+
+ return err;
+}
+
+
+static void sipsess_progr_handler(const struct sip_msg *msg, void *arg)
+{
+ struct call *call = arg;
+ bool media;
+
+ MAGIC_CHECK(call);
+
+ info("call: SIP Progress: %u %r (%r/%r)\n",
+ msg->scode, &msg->reason, &msg->ctyp.type, &msg->ctyp.subtype);
+
+ if (msg->scode <= 100)
+ return;
+
+ /* check for 18x and content-type
+ *
+ * 1. start media-stream if application/sdp
+ * 2. play local ringback tone if not
+ *
+ * we must also handle changes to/from 180 and 183,
+ * so we reset the media-stream/ringback each time.
+ */
+ if (msg_ctype_cmp(&msg->ctyp, "application", "sdp")
+ && mbuf_get_left(msg->mb)
+ && !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else if (msg_ctype_cmp(&msg->ctyp, "multipart", "mixed") &&
+ !sdp_decode_multipart(&msg->ctyp.params, msg->mb) &&
+ !sdp_decode(call->sdp, msg->mb, false)) {
+ media = true;
+ }
+ else
+ media = false;
+
+ switch (msg->scode) {
+
+ case 180:
+ set_state(call, STATE_RINGING);
+ break;
+
+ case 183:
+ set_state(call, STATE_EARLY);
+ break;
+ }
+
+ call_stream_stop(call);
+
+ if (media)
+ call_stream_start(call, false);
+
+ if (media)
+ call_event_handler(call, CALL_EVENT_PROGRESS, call->peer_uri);
+ else
+ call_event_handler(call, CALL_EVENT_RINGING, call->peer_uri);
+
+ if (media)
+ update_media(call);
+}
+
+
+static int send_invite(struct call *call)
+{
+ const char *routev[1];
+ struct mbuf *desc;
+ int err;
+
+ routev[0] = ua_outbound(call->ua);
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ return err;
+
+ err = sipsess_connect(&call->sess, uag_sipsess_sock(),
+ call->peer_uri,
+ call->local_name,
+ call->local_uri,
+ ua_cuser(call->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ "application/sdp", desc,
+ auth_handler, call->acc, true,
+ sipsess_offer_handler, sipsess_answer_handler,
+ sipsess_progr_handler, sipsess_estab_handler,
+ sipsess_info_handler, sipsess_refer_handler,
+ sipsess_close_handler, call,
+ "Allow: %s\r\n%H", uag_allowed_methods(),
+ ua_print_supported, call->ua);
+ if (err) {
+ warning("call: sipsess_connect: %m\n", err);
+ }
+
+ /* save call setup timer */
+ call->time_conn = time(NULL);
+
+ mem_deref(desc);
+
+ return err;
+}
+
+
+/**
+ * Get the current call duration in seconds
+ *
+ * @param call Call object
+ *
+ * @return Duration in seconds
+ */
+uint32_t call_duration(const struct call *call)
+{
+ if (!call || !call->time_start)
+ return 0;
+
+ return (uint32_t)(time(NULL) - call->time_start);
+}
+
+
+/**
+ * Get the current call setup time in seconds
+ *
+ * @param call Call object
+ *
+ * @return Call setup in seconds
+ */
+uint32_t call_setup_duration(const struct call *call)
+{
+ if (!call || !call->time_conn || call->time_conn <= 0 )
+ return 0;
+
+ return (uint32_t)(call->time_start - call->time_conn);
+}
+
+
+/**
+ * Get the audio object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Audio object
+ */
+struct audio *call_audio(const struct call *call)
+{
+ return call ? call->audio : NULL;
+}
+
+
+/**
+ * Get the video object for the current call
+ *
+ * @param call Call object
+ *
+ * @return Video object
+ */
+struct video *call_video(const struct call *call)
+{
+#ifdef USE_VIDEO
+ return call ? call->video : NULL;
+#else
+ (void)call;
+ return NULL;
+#endif
+}
+
+
+/**
+ * Get the list of media streams for the current call
+ *
+ * @param call Call object
+ *
+ * @return List of media streams
+ */
+struct list *call_streaml(const struct call *call)
+{
+ return call ? (struct list *)&call->streaml : NULL;
+}
+
+
+int call_reset_transp(struct call *call, const struct sa *laddr)
+{
+ if (!call)
+ return EINVAL;
+
+ sdp_session_set_laddr(call->sdp, laddr);
+
+ return call_modify(call);
+}
+
+
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!call)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, reason);
+ (void)mbuf_printf(mb, "SIP/2.0 %u %v\n", scode, reason, &ap);
+ va_end(ap);
+
+ mb->pos = 0;
+
+ if (scode >= 200) {
+ err = sipevent_notify(call->not, mb, SIPEVENT_TERMINATED,
+ SIPEVENT_NORESOURCE, 0);
+
+ call->not = mem_deref(call->not);
+ }
+ else {
+ err = sipevent_notify(call->not, mb, SIPEVENT_ACTIVE,
+ SIPEVENT_NORESOURCE, 0);
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static void sipsub_notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ struct call *call = arg;
+ struct pl scode, reason;
+ uint32_t sc;
+
+ if (re_regex((char *)mbuf_buf(msg->mb), mbuf_get_left(msg->mb),
+ "SIP/2.0 [0-9]+ [^\r\n]+", &scode, &reason)) {
+ (void)sip_reply(sip, msg, 400, "Bad sipfrag");
+ return;
+ }
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ sc = pl_u32(&scode);
+
+ if (sc >= 300) {
+ warning("call: transfer failed: %u %r\n", sc, &reason);
+ call_event_handler(call, CALL_EVENT_TRANSFER_FAILED,
+ "%u %r", sc, &reason);
+ }
+ else if (sc >= 200) {
+ call_event_handler(call, CALL_EVENT_CLOSED, "Call transfered");
+ }
+}
+
+
+static void sipsub_close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg)
+{
+ struct call *call = arg;
+
+ (void)substate;
+
+ call->sub = mem_deref(call->sub);
+
+ if (err) {
+ info("call: subscription closed: %m\n", err);
+ }
+ else if (msg && msg->scode >= 300) {
+ info("call: transfer failed: %u %r\n",
+ msg->scode, &msg->reason);
+ call_event_handler(call, CALL_EVENT_TRANSFER_FAILED,
+ "%u %r", msg->scode, &msg->reason);
+ }
+}
+
+
+static int normalize_uri(char **out, const char *uri, const struct uri *luri)
+{
+ struct uri uri2;
+ struct pl pl;
+ int err;
+
+ if (!out || !uri || !luri)
+ return EINVAL;
+
+ pl_set_str(&pl, uri);
+
+ if (0 == uri_decode(&uri2, &pl)) {
+
+ err = str_dup(out, uri);
+ }
+ else {
+ uri2 = *luri;
+
+ uri2.user = pl;
+ uri2.password = pl_null;
+ uri2.params = pl_null;
+
+ err = re_sdprintf(out, "%H", uri_encode, &uri2);
+ }
+
+ return err;
+}
+
+
+/**
+ * Transfer the call to a target SIP uri
+ *
+ * @param call Call object
+ * @param uri Target SIP uri
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int call_transfer(struct call *call, const char *uri)
+{
+ char *nuri;
+ int err;
+
+ if (!call || !uri)
+ return EINVAL;
+
+ err = normalize_uri(&nuri, uri, &call->acc->luri);
+ if (err)
+ return err;
+
+ info("transferring call to %s\n", nuri);
+
+ call->sub = mem_deref(call->sub);
+ err = sipevent_drefer(&call->sub, uag_sipevent_sock(),
+ sipsess_dialog(call->sess), ua_cuser(call->ua),
+ auth_handler, call->acc, true,
+ sipsub_notify_handler, sipsub_close_handler,
+ call,
+ "Refer-To: %s\r\n", nuri);
+ if (err) {
+ warning("call: sipevent_drefer: %m\n", err);
+ }
+
+ mem_deref(nuri);
+
+ return err;
+}
+
+
+int call_af(const struct call *call)
+{
+ return call ? call->af : AF_UNSPEC;
+}
+
+
+uint16_t call_scode(const struct call *call)
+{
+ return call ? call->scode : 0;
+}
+
+
+void call_set_handlers(struct call *call, call_event_h *eh,
+ call_dtmf_h *dtmfh, void *arg)
+{
+ if (!call)
+ return;
+
+ if (eh)
+ call->eh = eh;
+
+ if (dtmfh)
+ call->dtmfh = dtmfh;
+
+ if (arg)
+ call->arg = arg;
+}
+
+
+void call_set_xrtpstat(struct call *call)
+{
+ if (!call)
+ return;
+
+ sipsess_set_close_headers(call->sess,
+ "X-RTP-Stat: %H\r\n",
+ audio_print_rtpstat, call->audio);
+}
+
+
+bool call_is_onhold(const struct call *call)
+{
+ return call ? call->on_hold : false;
+}
+
+
+bool call_is_outgoing(const struct call *call)
+{
+ return call ? call->outgoing : false;
+}
+
+
+void call_enable_rtp_timeout(struct call *call, uint32_t timeout_ms)
+{
+ if (!call)
+ return;
+
+ call->rtp_timeout_ms = timeout_ms;
+}
+
+
+/**
+ * Get the line number for this call
+ *
+ * @param call Call object
+ *
+ * @return Line number from 1 to N
+ */
+uint32_t call_linenum(const struct call *call)
+{
+ return call ? call->linenum : 0;
+}
+
+
+struct call *call_find_linenum(const struct list *calls, uint32_t linenum)
+{
+ struct le *le;
+
+ for (le = list_head(calls); le; le = le->next) {
+ struct call *call = le->data;
+
+ if (linenum == call->linenum)
+ return call;
+ }
+
+ return NULL;
+}
+
+
+void call_set_current(struct list *calls, struct call *call)
+{
+ if (!calls || !call)
+ return;
+
+ list_unlink(&call->le);
+ list_append(calls, &call->le, call);
+}
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..c9d1745
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,768 @@
+/**
+ * @file src/cmd.c Command Interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <ctype.h>
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ KEYCODE_DEL = 0x7f,
+ LONG_PREFIX = '/'
+};
+
+
+struct cmds {
+ struct le le;
+ const struct cmd *cmdv;
+ size_t cmdc;
+};
+
+struct cmd_ctx {
+ struct mbuf *mb;
+ const struct cmd *cmd;
+ bool is_long;
+};
+
+struct commands {
+ struct list cmdl; /**< List of command blocks (struct cmds) */
+};
+
+
+static int cmd_print_all(struct re_printf *pf,
+ const struct commands *commands,
+ bool print_long, bool print_short,
+ const char *match, size_t match_len);
+
+
+static void destructor(void *arg)
+{
+ struct cmds *cmds = arg;
+
+ list_unlink(&cmds->le);
+}
+
+
+static void ctx_destructor(void *arg)
+{
+ struct cmd_ctx *ctx = arg;
+
+ mem_deref(ctx->mb);
+}
+
+
+static void commands_destructor(void *data)
+{
+ struct commands *commands = data;
+
+ list_flush(&commands->cmdl);
+}
+
+
+static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd)
+{
+ struct cmd_ctx *ctx;
+
+ ctx = mem_zalloc(sizeof(*ctx), ctx_destructor);
+ if (!ctx)
+ return ENOMEM;
+
+ ctx->mb = mbuf_alloc(32);
+ if (!ctx->mb) {
+ mem_deref(ctx);
+ return ENOMEM;
+ }
+
+ ctx->cmd = cmd;
+
+ *ctxp = ctx;
+
+ return 0;
+}
+
+
+/**
+ * Find a command block
+ *
+ * @param commands Commands container
+ * @param cmdv Command vector
+ *
+ * @return Command block if found, otherwise NULL
+ */
+struct cmds *cmds_find(const struct commands *commands,
+ const struct cmd *cmdv)
+{
+ struct le *le;
+
+ if (!commands || !cmdv)
+ return NULL;
+
+ for (le = commands->cmdl.head; le; le = le->next) {
+ struct cmds *cmds = le->data;
+
+ if (cmds->cmdv == cmdv)
+ return cmds;
+ }
+
+ return NULL;
+}
+
+
+static const struct cmd *cmd_find_by_key(const struct commands *commands,
+ char key)
+{
+ struct le *le;
+
+ if (!commands)
+ return NULL;
+
+ for (le = commands->cmdl.tail; le; le = le->prev) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (cmd->key == key && cmd->h)
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+
+static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd)
+{
+ switch (cmd->key) {
+
+ case ' ': return "SPACE";
+ case '\n': return "ENTER";
+ case KEYCODE_ESC: return "ESC";
+ }
+
+ buf[0] = cmd->key;
+ buf[1] = '\0';
+
+ if (cmd->flags & CMD_PRM)
+ strncat(buf, " ..", sz-1);
+
+ return buf;
+}
+
+
+static size_t get_match_long(const struct commands *commands,
+ const struct cmd **cmdp,
+ const char *str, size_t len)
+{
+ struct le *le;
+ size_t nmatch = 0;
+
+ if (!commands)
+ return 0;
+
+ for (le = commands->cmdl.head; le; le = le->next) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (!str_isset(cmd->name))
+ continue;
+
+ if (str_len(cmd->name) >= len &&
+ 0 == memcmp(cmd->name, str, len)) {
+
+ ++nmatch;
+ *cmdp = cmd;
+ }
+ }
+ }
+
+ return nmatch;
+}
+
+
+static int editor_input(struct commands *commands, struct mbuf *mb, char key,
+ struct re_printf *pf, bool *del, bool is_long)
+{
+ int err = 0;
+
+ switch (key) {
+
+ case KEYCODE_ESC:
+ *del = true;
+ return re_hprintf(pf, "\nCancel\n");
+
+ case KEYCODE_NONE:
+ case KEYCODE_REL:
+ break;
+
+ case '\n':
+ *del = true;
+ return re_hprintf(pf, "\n");
+
+ case '\b':
+ case KEYCODE_DEL:
+ if (mb->pos > 0) {
+ err |= re_hprintf(pf, "\b ");
+ mb->pos = mb->end = (mb->pos - 1);
+ }
+ break;
+
+ case '\t':
+ if (is_long) {
+ const struct cmd *cmd = NULL;
+ size_t n;
+
+ err = re_hprintf(pf,
+ "TAB completion for \"%b\":\n",
+ mb->buf, mb->end);
+ if (err)
+ return err;
+
+ /* Find all long commands that matches the N
+ * first characters of the input string.
+ *
+ * If the number of matches is exactly one,
+ * we can regard it as TAB completion.
+ */
+
+ err = cmd_print_all(pf, commands, true, false,
+ (char *)mb->buf, mb->end);
+ if (err)
+ return err;
+
+ n = get_match_long(commands, &cmd,
+ (char *)mb->buf, mb->end);
+ if (n == 1 && cmd) {
+
+ mb->pos = 0;
+ mbuf_write_str(mb, cmd->name);
+ }
+ else if (n == 0) {
+ err = re_hprintf(pf, "(none)\n");
+ }
+ }
+ else {
+ err = mbuf_write_u8(mb, key);
+ }
+ break;
+
+ default:
+ err = mbuf_write_u8(mb, key);
+ break;
+ }
+
+ if (is_long) {
+ err |= re_hprintf(pf, "\r/%b",
+ mb->buf, mb->end);
+ }
+ else
+ err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end);
+
+ return err;
+}
+
+
+static int cmd_report(const struct cmd *cmd, struct re_printf *pf,
+ struct mbuf *mb, bool compl, void *data)
+{
+ struct cmd_arg arg;
+ int err;
+
+ memset(&arg, 0, sizeof(arg));
+
+ mb->pos = 0;
+ err = mbuf_strdup(mb, &arg.prm, mb->end);
+ if (err)
+ return err;
+
+ arg.key = cmd->key;
+ arg.complete = compl;
+ arg.data = data;
+
+ err = cmd->h(pf, &arg);
+
+ mem_deref(arg.prm);
+
+ return err;
+}
+
+
+/**
+ * Process long commands
+ *
+ * @param commands Commands container
+ * @param str Input string
+ * @param len Length of input string
+ * @param pf_resp Print function for response
+ * @param data Application data
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process_long(struct commands *commands, const char *str, size_t len,
+ struct re_printf *pf_resp, void *data)
+{
+ struct cmd_arg arg;
+ const struct cmd *cmd_long;
+ char *name = NULL, *prm = NULL;
+ struct pl pl_name, pl_prm;
+ int err;
+
+ if (!str || !len)
+ return EINVAL;
+
+ memset(&arg, 0, sizeof(arg));
+
+ err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm);
+ if (err) {
+ return err;
+ }
+
+ err = pl_strdup(&name, &pl_name);
+ if (pl_isset(&pl_prm))
+ err |= pl_strdup(&prm, &pl_prm);
+ if (err)
+ goto out;
+
+ cmd_long = cmd_find_long(commands, name);
+ if (cmd_long) {
+
+ arg.key = LONG_PREFIX;
+ arg.prm = prm;
+ arg.complete = true;
+ arg.data = data;
+
+ if (cmd_long->h)
+ err = cmd_long->h(pf_resp, &arg);
+ }
+ else {
+ err = re_hprintf(pf_resp, "command not found (%s)\n", name);
+ }
+
+ out:
+ mem_deref(name);
+ mem_deref(prm);
+
+ return err;
+}
+
+
+static int cmd_process_edit(struct commands *commands,
+ struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf, void *data)
+{
+ struct cmd_ctx *ctx;
+ bool compl = (key == '\n'), del = false;
+ int err;
+
+ if (!ctxp)
+ return EINVAL;
+
+ ctx = *ctxp;
+
+ err = editor_input(commands, ctx->mb, key, pf, &del, ctx->is_long);
+ if (err)
+ return err;
+
+ if (ctx->is_long) {
+
+ if (compl) {
+
+ err = cmd_process_long(commands,
+ (char *)ctx->mb->buf,
+ ctx->mb->end,
+ pf, data);
+ }
+ }
+ else {
+ if (compl ||
+ (ctx->cmd && ctx->cmd->flags & CMD_PROG))
+ err = cmd_report(ctx->cmd, pf, ctx->mb, compl, data);
+ }
+
+ if (del)
+ *ctxp = mem_deref(*ctxp);
+
+ return err;
+}
+
+
+/**
+ * Register commands
+ *
+ * @param commands Commands container
+ * @param cmdv Array of commands
+ * @param cmdc Number of commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_register(struct commands *commands,
+ const struct cmd *cmdv, size_t cmdc)
+{
+ struct cmds *cmds;
+ size_t i;
+
+ if (!commands || !cmdv || !cmdc)
+ return EINVAL;
+
+ cmds = cmds_find(commands, cmdv);
+ if (cmds)
+ return EALREADY;
+
+ /* verify that command is not registered */
+ for (i=0; i<cmdc; i++) {
+ const struct cmd *cmd = &cmdv[i];
+
+ if (cmd->key) {
+ const struct cmd *x = cmd_find_by_key(commands,
+ cmd->key);
+ if (x) {
+ warning("short command '%c' already"
+ " registered as \"%s\"\n",
+ x->key, x->desc);
+ return EALREADY;
+ }
+ }
+
+ if (cmd->key == LONG_PREFIX) {
+ warning("cmd: cannot register command with"
+ " short key '%c'\n", cmd->key);
+ return EINVAL;
+ }
+
+ if (str_isset(cmd->name) &&
+ cmd_find_long(commands, cmd->name)) {
+ warning("cmd: long command '%s' already registered\n",
+ cmd->name);
+ return EINVAL;
+ }
+ }
+
+ cmds = mem_zalloc(sizeof(*cmds), destructor);
+ if (!cmds)
+ return ENOMEM;
+
+ cmds->cmdv = cmdv;
+ cmds->cmdc = cmdc;
+
+ list_append(&commands->cmdl, &cmds->le, cmds);
+
+ return 0;
+}
+
+
+/**
+ * Unregister commands
+ *
+ * @param commands Commands container
+ * @param cmdv Array of commands
+ */
+void cmd_unregister(struct commands *commands, const struct cmd *cmdv)
+{
+ mem_deref(cmds_find(commands, cmdv));
+}
+
+
+/**
+ * Find a long command
+ *
+ * @param commands Commands container
+ * @param name Name of command, excluding prefix
+ *
+ * @return Command if found, NULL if not found
+ */
+const struct cmd *cmd_find_long(const struct commands *commands,
+ const char *name)
+{
+ struct le *le;
+
+ if (!commands || !name)
+ return NULL;
+
+ for (le = commands->cmdl.tail; le; le = le->prev) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+
+ if (0 == str_casecmp(name, cmd->name) && cmd->h)
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Process input characters to the command system
+ *
+ * @param commands Commands container
+ * @param ctxp Pointer to context for editor (optional)
+ * @param key Input character
+ * @param pf Print function
+ * @param data Application data
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key,
+ struct re_printf *pf, void *data)
+{
+ const struct cmd *cmd;
+
+ if (!commands)
+ return EINVAL;
+
+ if (key == KEYCODE_NONE) {
+ warning("cmd: process: illegal keycode NONE\n");
+ return EINVAL;
+ }
+
+ /* are we in edit-mode? */
+ if (ctxp && *ctxp) {
+
+ if (key == KEYCODE_REL)
+ return 0;
+
+ return cmd_process_edit(commands, ctxp, key, pf, data);
+ }
+
+ cmd = cmd_find_by_key(commands, key);
+ if (cmd) {
+ struct cmd_arg arg;
+
+ /* check for parameters */
+ if (cmd->flags & CMD_PRM) {
+
+ int err = 0;
+
+ if (ctxp) {
+ err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+ }
+
+ key = isdigit(key) ? key : KEYCODE_REL;
+
+ return cmd_process_edit(commands, ctxp, key, pf, data);
+ }
+
+ arg.key = key;
+ arg.prm = NULL;
+ arg.complete = true;
+ arg.data = data;
+
+ return cmd->h(pf, &arg);
+ }
+ else if (key == LONG_PREFIX) {
+
+ int err;
+
+ err = re_hprintf(pf, "%c", LONG_PREFIX);
+ if (err)
+ return err;
+
+ if (!ctxp) {
+ warning("cmd: ctxp is required\n");
+ return EINVAL;
+ }
+
+ err = ctx_alloc(ctxp, cmd);
+ if (err)
+ return err;
+
+ (*ctxp)->is_long = true;
+
+ return 0;
+ }
+ else if (key == '\t') {
+ return cmd_print_all(pf, commands, false, true, NULL, 0);
+ }
+
+ if (key == KEYCODE_REL)
+ return 0;
+
+ return cmd_print(pf, commands);
+}
+
+
+struct cmd_sort {
+ struct le le;
+ const struct cmd *cmd;
+};
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct cmd_sort *cs1 = le1->data;
+ struct cmd_sort *cs2 = le2->data;
+ const struct cmd *cmd1 = cs1->cmd;
+ const struct cmd *cmd2 = cs2->cmd;
+ bool print_long = *(bool *)arg;
+
+ if (print_long) {
+ return str_casecmp(cs2->cmd->name ? cs2->cmd->name : "",
+ cs1->cmd->name ? cs1->cmd->name : "") >= 0;
+ }
+ else {
+ return tolower(cmd2->key) >= tolower(cmd1->key);
+ }
+}
+
+
+static int cmd_print_all(struct re_printf *pf,
+ const struct commands *commands,
+ bool print_long, bool print_short,
+ const char *match, size_t match_len)
+{
+ struct list sortedl = LIST_INIT;
+ struct le *le;
+ size_t width_long = 1;
+ size_t width_short = 5;
+ char fmt[64];
+ char buf[16];
+ int err = 0;
+
+ if (!commands)
+ return EINVAL;
+
+ for (le = commands->cmdl.head; le; le = le->next) {
+
+ struct cmds *cmds = le->data;
+ size_t i;
+
+ for (i=0; i<cmds->cmdc; i++) {
+
+ const struct cmd *cmd = &cmds->cmdv[i];
+ struct cmd_sort *cs;
+
+ if (match && match_len) {
+
+ if (str_len(cmd->name) >= match_len &&
+ 0 == memcmp(cmd->name, match, match_len)) {
+ /* Match */
+ }
+ else {
+ continue;
+ }
+ }
+
+ if (!str_isset(cmd->desc))
+ continue;
+
+ if (print_short && !print_long) {
+
+ if (cmd->key == KEYCODE_NONE)
+ continue;
+ }
+
+ cs = mem_zalloc(sizeof(*cs), NULL);
+ if (!cs) {
+ err = ENOMEM;
+ goto out;
+ }
+ cs->cmd = cmd;
+
+ list_append(&sortedl, &cs->le, cs);
+
+ width_long = max(width_long, 1+str_len(cmd->name)+3);
+ }
+ }
+
+ list_sort(&sortedl, sort_handler, &print_long);
+
+ if (re_snprintf(fmt, sizeof(fmt),
+ " %%-%zus %%-%zus %%s\n",
+ width_long, width_short) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (le = sortedl.head; le; le = le->next) {
+ struct cmd_sort *cs = le->data;
+ const struct cmd *cmd = cs->cmd;
+ char namep[64] = "";
+
+ if (print_long && str_isset(cmd->name)) {
+ re_snprintf(namep, sizeof(namep), "%c%s%s",
+ LONG_PREFIX, cmd->name,
+ (cmd->flags & CMD_PRM) ? " .." : "");
+ }
+
+ err |= re_hprintf(pf, fmt,
+ namep,
+ (print_short && cmd->key)
+ ? cmd_name(buf, sizeof(buf), cmd)
+ : "",
+ cmd->desc);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ out:
+ list_flush(&sortedl);
+ return err;
+}
+
+
+/**
+ * Print a list of available commands
+ *
+ * @param pf Print function
+ * @param commands Commands container
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_print(struct re_printf *pf, const struct commands *commands)
+{
+ int err = 0;
+
+ if (!pf)
+ return EINVAL;
+
+ err |= re_hprintf(pf, "--- Help ---\n");
+ err |= cmd_print_all(pf, commands, true, true, NULL, 0);
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Initialize the commands subsystem.
+ *
+ * @param commandsp Pointer to allocated commands
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int cmd_init(struct commands **commandsp)
+{
+ struct commands *commands;
+
+ if (!commandsp)
+ return EINVAL;
+
+ commands = mem_zalloc(sizeof(*commands), commands_destructor);
+ if (!commands)
+ return ENOMEM;
+
+ list_init(&commands->cmdl);
+
+ *commandsp = commands;
+
+ return 0;
+}
diff --git a/src/conf.c b/src/conf.c
new file mode 100644
index 0000000..2046216
--- /dev/null
+++ b/src/conf.c
@@ -0,0 +1,386 @@
+/**
+ * @file conf.c Configuration utils
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define _BSD_SOURCE 1
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <sys/stat.h>
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#define DEBUG_MODULE ""
+#define DEBUG_LEVEL 0
+#include <re_dbg.h>
+
+
+#ifdef WIN32
+#define open _open
+#define read _read
+#define close _close
+#endif
+
+
+#if defined (WIN32)
+#define DIR_SEP "\\"
+#else
+#define DIR_SEP "/"
+#endif
+
+
+static const char *conf_path = NULL;
+static struct conf *conf_obj;
+
+
+/**
+ * Check if a file exists
+ *
+ * @param path Filename
+ *
+ * @return True if exist, False if not
+ */
+bool conf_fileexist(const char *path)
+{
+ struct stat st;
+
+ if (!path)
+ return false;
+
+ if (stat(path, &st) < 0)
+ return false;
+
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+ return false;
+
+ return st.st_size > 0;
+}
+
+
+static void print_populated(const char *what, uint32_t n)
+{
+ info("Populated %u %s%s\n", n, what, 1==n ? "" : "s");
+}
+
+
+/**
+ * Parse a config file, calling handler for each line
+ *
+ * @param filename Config file
+ * @param ch Line handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_parse(const char *filename, confline_h *ch, void *arg)
+{
+ struct pl pl, val;
+ struct mbuf *mb;
+ int err = 0, fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return errno;
+
+ mb = mbuf_alloc(1024);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (;;) {
+ uint8_t buf[1024];
+
+ const ssize_t n = read(fd, (void *)buf, sizeof(buf));
+ if (n < 0) {
+ err = errno;
+ break;
+ }
+ else if (n == 0)
+ break;
+
+ err |= mbuf_write_mem(mb, buf, n);
+ }
+
+ pl.p = (const char *)mb->buf;
+ pl.l = mb->end;
+
+ while (pl.p < ((const char *)mb->buf + mb->end) && !err) {
+ const char *lb = pl_strchr(&pl, '\n');
+
+ val.p = pl.p;
+ val.l = lb ? (uint32_t)(lb - pl.p) : pl.l;
+ pl_advance(&pl, val.l + 1);
+
+ if (!val.l || val.p[0] == '#')
+ continue;
+
+ err = ch(&val, arg);
+ }
+
+ out:
+ mem_deref(mb);
+ (void)close(fd);
+
+ return err;
+}
+
+
+/**
+ * Set the path to configuration files
+ *
+ * @param path Configuration path
+ */
+void conf_path_set(const char *path)
+{
+ conf_path = path;
+}
+
+
+/**
+ * Get the path to configuration files
+ *
+ * @param path Buffer to write path
+ * @param sz Size of path buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_path_get(char *path, size_t sz)
+{
+ char buf[FS_PATH_MAX];
+ int err;
+
+ /* Use explicit conf path */
+ if (conf_path) {
+ if (re_snprintf(path, sz, "%s", conf_path) < 0)
+ return ENOMEM;
+ return 0;
+ }
+
+#ifdef CONFIG_PATH
+ str_ncpy(buf, CONFIG_PATH, sizeof(buf));
+ (void)err;
+#else
+ err = fs_gethome(buf, sizeof(buf));
+ if (err)
+ return err;
+#endif
+
+ if (re_snprintf(path, sz, "%s" DIR_SEP ".baresip", buf) < 0)
+ return ENOMEM;
+
+ return 0;
+}
+
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng)
+{
+ struct pl r, min, max;
+ uint32_t v;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ err = re_regex(r.p, r.l, "[0-9]+-[0-9]+", &min, &max);
+ if (err) {
+ /* fallback to non-range numeric value */
+ err = conf_get_u32(conf, name, &v);
+ if (err) {
+ warning("conf: %s: could not parse range: (%r)\n",
+ name, &r);
+ return err;
+ }
+
+ rng->min = rng->max = v;
+
+ return err;
+ }
+
+ rng->min = pl_u32(&min);
+ rng->max = pl_u32(&max);
+
+ if (rng->min > rng->max) {
+ warning("conf: %s: invalid range (%u - %u)\n",
+ name, rng->min, rng->max);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2)
+{
+ struct pl r, pl1, pl2 = pl_null;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ /* note: second value may be quoted */
+ err = re_regex(r.p, r.l, "[^,]+,[~]*", &pl1, &pl2);
+ if (err)
+ return err;
+
+ (void)pl_strcpy(&pl1, str1, sz1);
+ if (pl_isset(&pl2))
+ (void)pl_strcpy(&pl2, str2, sz2);
+
+ return 0;
+}
+
+
+int conf_get_vidsz(const struct conf *conf, const char *name, struct vidsz *sz)
+{
+ struct pl r, w, h;
+ int err;
+
+ err = conf_get(conf, name, &r);
+ if (err)
+ return err;
+
+ w.l = h.l = 0;
+ err = re_regex(r.p, r.l, "[0-9]+x[0-9]+", &w, &h);
+ if (err)
+ return err;
+
+ if (pl_isset(&w) && pl_isset(&h)) {
+ sz->w = pl_u32(&w);
+ sz->h = pl_u32(&h);
+ }
+
+ /* check resolution */
+ if (sz->w & 0x1 || sz->h & 0x1) {
+ warning("conf: %s: should be multiple of 2 (%u x %u)\n",
+ name, sz->w, sz->h);
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+int conf_get_sa(const struct conf *conf, const char *name, struct sa *sa)
+{
+ struct pl opt;
+ int err;
+
+ if (!conf || !name || !sa)
+ return EINVAL;
+
+ err = conf_get(conf, name, &opt);
+ if (err)
+ return err;
+
+ return sa_decode(sa, opt.p, opt.l);
+}
+
+
+/**
+ * Configure the system with default settings
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_configure(void)
+{
+ char path[FS_PATH_MAX], file[FS_PATH_MAX];
+ int err;
+
+#if defined (WIN32)
+ dbg_init(DBG_INFO, DBG_NONE);
+#endif
+
+ err = conf_path_get(path, sizeof(path));
+ if (err) {
+ warning("conf: could not get config path: %m\n", err);
+ return err;
+ }
+
+ if (re_snprintf(file, sizeof(file), "%s/config", path) < 0)
+ return ENOMEM;
+
+ if (!conf_fileexist(file)) {
+
+ (void)fs_mkdir(path, 0700);
+
+ err = config_write_template(file, conf_config());
+ if (err)
+ goto out;
+ }
+
+ conf_obj = mem_deref(conf_obj);
+ err = conf_alloc(&conf_obj, file);
+ if (err)
+ goto out;
+
+ err = config_parse_conf(conf_config(), conf_obj);
+ if (err)
+ goto out;
+
+ out:
+ return err;
+}
+
+
+/**
+ * Load all modules from config file
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @note conf_configure must be called first
+ */
+int conf_modules(void)
+{
+ int err;
+
+ err = module_init(conf_obj);
+ if (err) {
+ warning("conf: configure module parse error (%m)\n", err);
+ goto out;
+ }
+
+ print_populated("audio codec", list_count(baresip_aucodecl()));
+ print_populated("audio filter", list_count(baresip_aufiltl()));
+#ifdef USE_VIDEO
+ print_populated("video codec", list_count(baresip_vidcodecl()));
+ print_populated("video filter", list_count(baresip_vidfiltl()));
+#endif
+
+ out:
+ return err;
+}
+
+
+/**
+ * Get the current configuration object
+ *
+ * @return Config object
+ *
+ * @note It is only available after init and before conf_close()
+ */
+struct conf *conf_cur(void)
+{
+ if (!conf_obj) {
+ warning("conf: no config object\n");
+ }
+ return conf_obj;
+}
+
+
+void conf_close(void)
+{
+ conf_obj = mem_deref(conf_obj);
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..ce748d3
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,925 @@
+/**
+ * @file config.c Core Configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <dirent.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+#undef MOD_PRE
+#define MOD_PRE "" /**< Module prefix */
+
+
+#undef SA_INIT
+#define SA_INIT { { {0} }, 0}
+
+
+#ifndef PREFIX
+#define PREFIX "/usr"
+#endif
+
+
+/** Core Run-time Configuration - populated from config file */
+static struct config core_config = {
+
+ /** SIP User-Agent */
+ {
+ 16,
+ "",
+ "",
+ ""
+ },
+
+ /** Call config */
+ {
+ 120,
+ 4
+ },
+
+ /** Audio */
+ {
+ PREFIX "/share/baresip",
+ "","",
+ "","",
+ "","",
+ {8000, 48000},
+ {1, 2},
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ AUDIO_MODE_POLL,
+ false,
+ AUFMT_S16LE,
+ AUFMT_S16LE,
+ },
+
+#ifdef USE_VIDEO
+ /** Video */
+ {
+ "", "",
+ "", "",
+ 352, 288,
+ 500000,
+ 25,
+ true,
+ },
+#endif
+
+ /** Audio/Video Transport */
+ {
+ 0xb8,
+ {1024, 49152},
+ {0, 0},
+ true,
+ false,
+ {5, 10},
+ false,
+ 0
+ },
+
+ /* Network */
+ {
+ "",
+ { {""} },
+ 0
+ },
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ {
+ ""
+ },
+#endif
+
+ /* SDP */
+ {
+ false
+ },
+};
+
+
+static int range_print(struct re_printf *pf, const struct range *rng)
+{
+ if (!rng)
+ return 0;
+
+ return re_hprintf(pf, "%u-%u", rng->min, rng->max);
+}
+
+
+static int dns_server_handler(const struct pl *pl, void *arg)
+{
+ struct config_net *cfg = arg;
+ const size_t max_count = ARRAY_SIZE(cfg->nsv);
+ int err;
+
+ if (cfg->nsc >= max_count) {
+ warning("config: too many DNS nameservers (max %zu)\n",
+ max_count);
+ return EOVERFLOW;
+ }
+
+ /* Append dns_server to the network config */
+ err = pl_strcpy(pl, cfg->nsv[cfg->nsc].addr,
+ sizeof(cfg->nsv[0].addr));
+ if (err) {
+ warning("config: dns_server: could not copy string (%r)\n",
+ pl);
+ return err;
+ }
+
+ ++cfg->nsc;
+
+ return 0;
+}
+
+
+static enum aufmt resolve_aufmt(const struct pl *fmt)
+{
+ if (0 == pl_strcasecmp(fmt, "s16")) return AUFMT_S16LE;
+ if (0 == pl_strcasecmp(fmt, "float")) return AUFMT_FLOAT;
+ if (0 == pl_strcasecmp(fmt, "s24_3le")) return AUFMT_S24_3LE;
+
+ /* XXX remove this after librem is fixed */
+ if (0 == pl_strcasecmp(fmt, "s16le")) return AUFMT_S16LE;
+
+ return (enum aufmt)-1;
+}
+
+
+static int conf_get_aufmt(const struct conf *conf, const char *name,
+ int *fmtp)
+{
+ struct pl pl;
+ int fmt;
+ int err;
+
+ err = conf_get(conf, name, &pl);
+ if (err)
+ return err;
+
+ fmt = resolve_aufmt(&pl);
+ if (fmt == -1) {
+ warning("config: %s: sample format not supported"
+ " (%r)\n", name, &pl);
+ return EINVAL;
+ }
+
+ *fmtp = fmt;
+
+ return 0;
+}
+
+
+/**
+ * Parse the core configuration file and update baresip core config
+ *
+ * @param cfg Baresip core config to update
+ * @param conf Configuration file to parse
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int config_parse_conf(struct config *cfg, const struct conf *conf)
+{
+ struct pl pollm, as, ap;
+ enum poll_method method;
+ struct vidsz size = {0, 0};
+ struct pl txmode;
+ uint32_t v;
+ int err = 0;
+
+ if (!cfg || !conf)
+ return EINVAL;
+
+ /* Core */
+ if (0 == conf_get(conf, "poll_method", &pollm)) {
+ if (0 == poll_method_type(&method, &pollm)) {
+ err = poll_method_set(method);
+ if (err) {
+ warning("config: poll method (%r) set: %m\n",
+ &pollm, err);
+ }
+ }
+ else {
+ warning("config: unknown poll method (%r)\n", &pollm);
+ }
+ }
+
+ /* SIP */
+ (void)conf_get_u32(conf, "sip_trans_bsize", &cfg->sip.trans_bsize);
+ (void)conf_get_str(conf, "sip_listen", cfg->sip.local,
+ sizeof(cfg->sip.local));
+ (void)conf_get_str(conf, "sip_certificate", cfg->sip.cert,
+ sizeof(cfg->sip.cert));
+
+ /* Call */
+ (void)conf_get_u32(conf, "call_local_timeout",
+ &cfg->call.local_timeout);
+ (void)conf_get_u32(conf, "call_max_calls",
+ &cfg->call.max_calls);
+
+ /* Audio */
+ (void)conf_get_str(conf, "audio_path", cfg->audio.audio_path,
+ sizeof(cfg->audio.audio_path));
+ (void)conf_get_csv(conf, "audio_player",
+ cfg->audio.play_mod,
+ sizeof(cfg->audio.play_mod),
+ cfg->audio.play_dev,
+ sizeof(cfg->audio.play_dev));
+
+ (void)conf_get_csv(conf, "audio_source",
+ cfg->audio.src_mod, sizeof(cfg->audio.src_mod),
+ cfg->audio.src_dev, sizeof(cfg->audio.src_dev));
+
+ (void)conf_get_csv(conf, "audio_alert",
+ cfg->audio.alert_mod,
+ sizeof(cfg->audio.alert_mod),
+ cfg->audio.alert_dev,
+ sizeof(cfg->audio.alert_dev));
+
+ (void)conf_get_range(conf, "audio_srate", &cfg->audio.srate);
+ (void)conf_get_range(conf, "audio_channels", &cfg->audio.channels);
+ (void)conf_get_u32(conf, "ausrc_srate", &cfg->audio.srate_src);
+ (void)conf_get_u32(conf, "auplay_srate", &cfg->audio.srate_play);
+ (void)conf_get_u32(conf, "ausrc_channels", &cfg->audio.channels_src);
+ (void)conf_get_u32(conf, "auplay_channels", &cfg->audio.channels_play);
+
+ if (0 == conf_get(conf, "audio_source", &as) &&
+ 0 == conf_get(conf, "audio_player", &ap))
+ cfg->audio.src_first = as.p < ap.p;
+
+ if (0 == conf_get(conf, "audio_txmode", &txmode)) {
+
+ if (0 == pl_strcasecmp(&txmode, "poll"))
+ cfg->audio.txmode = AUDIO_MODE_POLL;
+ else if (0 == pl_strcasecmp(&txmode, "thread"))
+ cfg->audio.txmode = AUDIO_MODE_THREAD;
+ else {
+ warning("unsupported audio txmode (%r)\n", &txmode);
+ }
+ }
+
+ (void)conf_get_bool(conf, "audio_level", &cfg->audio.level);
+
+ conf_get_aufmt(conf, "ausrc_format", &cfg->audio.src_fmt);
+ conf_get_aufmt(conf, "auplay_format", &cfg->audio.play_fmt);
+
+#ifdef USE_VIDEO
+ /* Video */
+ (void)conf_get_csv(conf, "video_source",
+ cfg->video.src_mod, sizeof(cfg->video.src_mod),
+ cfg->video.src_dev, sizeof(cfg->video.src_dev));
+ (void)conf_get_csv(conf, "video_display",
+ cfg->video.disp_mod, sizeof(cfg->video.disp_mod),
+ cfg->video.disp_dev, sizeof(cfg->video.disp_dev));
+ if (0 == conf_get_vidsz(conf, "video_size", &size)) {
+ cfg->video.width = size.w;
+ cfg->video.height = size.h;
+ }
+ (void)conf_get_u32(conf, "video_bitrate", &cfg->video.bitrate);
+ (void)conf_get_u32(conf, "video_fps", &cfg->video.fps);
+ (void)conf_get_bool(conf, "video_fullscreen", &cfg->video.fullscreen);
+#else
+ (void)size;
+#endif
+
+ /* AVT - Audio/Video Transport */
+ if (0 == conf_get_u32(conf, "rtp_tos", &v))
+ cfg->avt.rtp_tos = v;
+ (void)conf_get_range(conf, "rtp_ports", &cfg->avt.rtp_ports);
+ if (0 == conf_get_range(conf, "rtp_bandwidth",
+ &cfg->avt.rtp_bw)) {
+ cfg->avt.rtp_bw.min *= 1000;
+ cfg->avt.rtp_bw.max *= 1000;
+ }
+ (void)conf_get_bool(conf, "rtcp_enable", &cfg->avt.rtcp_enable);
+ (void)conf_get_bool(conf, "rtcp_mux", &cfg->avt.rtcp_mux);
+ (void)conf_get_range(conf, "jitter_buffer_delay",
+ &cfg->avt.jbuf_del);
+ (void)conf_get_bool(conf, "rtp_stats", &cfg->avt.rtp_stats);
+ (void)conf_get_u32(conf, "rtp_timeout", &cfg->avt.rtp_timeout);
+
+ if (err) {
+ warning("config: configure parse error (%m)\n", err);
+ }
+
+ /* Network */
+ (void)conf_apply(conf, "dns_server", dns_server_handler, &cfg->net);
+ (void)conf_get_str(conf, "net_interface",
+ cfg->net.ifname, sizeof(cfg->net.ifname));
+
+#ifdef USE_VIDEO
+ /* BFCP */
+ (void)conf_get_str(conf, "bfcp_proto", cfg->bfcp.proto,
+ sizeof(cfg->bfcp.proto));
+#endif
+
+ /* SDP */
+ (void)conf_get_bool(conf, "sdp_ebuacip", &cfg->sdp.ebuacip);
+
+ return err;
+}
+
+
+/**
+ * Print the baresip core config
+ *
+ * @param pf Print function
+ * @param cfg Baresip core config
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int config_print(struct re_printf *pf, const struct config *cfg)
+{
+ int err;
+
+ if (!cfg)
+ return 0;
+
+ err = re_hprintf(pf,
+ "\n"
+ "# SIP\n"
+ "sip_trans_bsize\t\t%u\n"
+ "sip_listen\t\t%s\n"
+ "sip_certificate\t%s\n"
+ "\n"
+ "# Call\n"
+ "call_local_timeout\t%u\n"
+ "call_max_calls\t%u\n"
+ "\n"
+ "# Audio\n"
+ "audio_path\t\t%s\n"
+ "audio_player\t\t%s,%s\n"
+ "audio_source\t\t%s,%s\n"
+ "audio_alert\t\t%s,%s\n"
+ "audio_srate\t\t%H\n"
+ "audio_channels\t\t%H\n"
+ "auplay_srate\t\t%u\n"
+ "ausrc_srate\t\t%u\n"
+ "auplay_channels\t\t%u\n"
+ "ausrc_channels\t\t%u\n"
+ "audio_level\t\t%s\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# Video\n"
+ "video_source\t\t%s,%s\n"
+ "video_display\t\t%s,%s\n"
+ "video_size\t\t\"%ux%u\"\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n"
+ "\n"
+#endif
+ "# AVT\n"
+ "rtp_tos\t\t\t%u\n"
+ "rtp_ports\t\t%H\n"
+ "rtp_bandwidth\t\t%H\n"
+ "rtcp_enable\t\t%s\n"
+ "rtcp_mux\t\t%s\n"
+ "jitter_buffer_delay\t%H\n"
+ "rtp_stats\t\t%s\n"
+ "rtp_timeout\t\t%u # in seconds\n"
+ "\n"
+ "# Network\n"
+ "net_interface\t\t%s\n"
+ "\n"
+#ifdef USE_VIDEO
+ "# BFCP\n"
+ "bfcp_proto\t\t%s\n"
+ "\n"
+#endif
+ ,
+
+ cfg->sip.trans_bsize, cfg->sip.local, cfg->sip.cert,
+
+ cfg->call.local_timeout,
+ cfg->call.max_calls,
+
+ cfg->audio.audio_path,
+ cfg->audio.play_mod, cfg->audio.play_dev,
+ cfg->audio.src_mod, cfg->audio.src_dev,
+ cfg->audio.alert_mod, cfg->audio.alert_dev,
+ range_print, &cfg->audio.srate,
+ range_print, &cfg->audio.channels,
+ cfg->audio.srate_play, cfg->audio.srate_src,
+ cfg->audio.channels_play, cfg->audio.channels_src,
+ cfg->audio.level ? "yes" : "no",
+
+#ifdef USE_VIDEO
+ cfg->video.src_mod, cfg->video.src_dev,
+ cfg->video.disp_mod, cfg->video.disp_dev,
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps,
+#endif
+
+ cfg->avt.rtp_tos,
+ range_print, &cfg->avt.rtp_ports,
+ range_print, &cfg->avt.rtp_bw,
+ cfg->avt.rtcp_enable ? "yes" : "no",
+ cfg->avt.rtcp_mux ? "yes" : "no",
+ range_print, &cfg->avt.jbuf_del,
+ cfg->avt.rtp_stats ? "yes" : "no",
+ cfg->avt.rtp_timeout,
+
+ cfg->net.ifname
+
+#ifdef USE_VIDEO
+ ,cfg->bfcp.proto
+#endif
+ );
+
+ return err;
+}
+
+
+static const char *default_audio_device(void)
+{
+#if defined (ANDROID)
+ return "opensles,nil";
+#elif defined (DARWIN)
+ return "coreaudio,nil";
+#elif defined (FREEBSD)
+ return "oss,/dev/dsp";
+#elif defined (OPENBSD)
+ return "sndio,default";
+#elif defined (WIN32)
+ return "winwave,nil";
+#else
+ return "alsa,default";
+#endif
+}
+
+
+#ifdef USE_VIDEO
+static const char *default_video_device(void)
+{
+#ifdef DARWIN
+
+#ifdef QTCAPTURE_RUNLOOP
+ return "qtcapture,nil";
+#else
+ return "avcapture,nil";
+#endif
+
+#else
+ return "v4l2,/dev/video0";
+#endif
+}
+
+
+static const char *default_video_display(void)
+{
+#ifdef DARWIN
+ return "opengl,nil";
+#else
+ return "x11,nil";
+#endif
+}
+#endif
+
+
+static int default_interface_print(struct re_printf *pf, void *unused)
+{
+ char ifname[64];
+ (void)unused;
+
+ if (0 == net_rt_default_get(AF_INET, ifname, sizeof(ifname)))
+ return re_hprintf(pf, "%s", ifname);
+ else
+ return re_hprintf(pf, "eth0");
+}
+
+
+static int core_config_template(struct re_printf *pf, const struct config *cfg)
+{
+ int err = 0;
+
+ if (!cfg)
+ return 0;
+
+ err |= re_hprintf(pf,
+ "\n# Core\n"
+ "poll_method\t\t%s\t\t# poll, select"
+#ifdef HAVE_EPOLL
+ ", epoll .."
+#endif
+#ifdef HAVE_KQUEUE
+ ", kqueue .."
+#endif
+ "\n"
+ "\n# SIP\n"
+ "sip_trans_bsize\t\t128\n"
+ "#sip_listen\t\t0.0.0.0:5060\n"
+ "#sip_certificate\tcert.pem\n"
+ "\n"
+ "# Call\n"
+ "call_local_timeout\t%u\n"
+ "call_max_calls\t%u\n"
+ "\n"
+ "# Audio\n"
+#if defined (PREFIX)
+ "#audio_path\t\t" PREFIX "/share/baresip\n"
+#else
+ "#audio_path\t\t/usr/share/baresip\n"
+#endif
+ "audio_player\t\t%s\n"
+ "audio_source\t\t%s\n"
+ "audio_alert\t\t%s\n"
+ "audio_srate\t\t%u-%u\n"
+ "audio_channels\t\t%u-%u\n"
+ "#ausrc_srate\t\t48000\n"
+ "#auplay_srate\t\t48000\n"
+ "#ausrc_channels\t\t0\n"
+ "#auplay_channels\t\t0\n"
+ "#audio_txmode\t\tpoll\t\t# poll, thread\n"
+ "audio_level\t\tno\n"
+ "ausrc_format\t\ts16\t\t# s16, float, ..\n"
+ "auplay_format\t\ts16\t\t# s16, float, ..\n"
+ ,
+ poll_method_name(poll_method_best()),
+ cfg->call.local_timeout,
+ cfg->call.max_calls,
+ default_audio_device(),
+ default_audio_device(),
+ default_audio_device(),
+ cfg->audio.srate.min, cfg->audio.srate.max,
+ cfg->audio.channels.min, cfg->audio.channels.max
+ );
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# Video\n"
+ "#video_source\t\t%s\n"
+ "#video_display\t\t%s\n"
+ "video_size\t\t%dx%d\n"
+ "video_bitrate\t\t%u\n"
+ "video_fps\t\t%u\n"
+ "video_fullscreen\tyes\n",
+ default_video_device(),
+ default_video_display(),
+ cfg->video.width, cfg->video.height,
+ cfg->video.bitrate, cfg->video.fps);
+#endif
+
+ err |= re_hprintf(pf,
+ "\n# AVT - Audio/Video Transport\n"
+ "rtp_tos\t\t\t184\n"
+ "#rtp_ports\t\t10000-20000\n"
+ "#rtp_bandwidth\t\t512-1024 # [kbit/s]\n"
+ "rtcp_enable\t\tyes\n"
+ "rtcp_mux\t\tno\n"
+ "jitter_buffer_delay\t%u-%u\t\t# frames\n"
+ "rtp_stats\t\tno\n"
+ "#rtp_timeout\t\t60\n"
+ "\n# Network\n"
+ "#dns_server\t\t10.0.0.1:53\n"
+ "#net_interface\t\t%H\n",
+ cfg->avt.jbuf_del.min, cfg->avt.jbuf_del.max,
+ default_interface_print, NULL);
+
+#ifdef USE_VIDEO
+ err |= re_hprintf(pf,
+ "\n# BFCP\n"
+ "#bfcp_proto\t\tudp\n");
+#endif
+
+ return err;
+}
+
+
+static uint32_t count_modules(const char *path)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ uint32_t n = 0;
+
+ dirp = opendir(path);
+ if (!dirp)
+ return 0;
+
+ while ((dp = readdir(dirp)) != NULL) {
+
+ size_t len = strlen(dp->d_name);
+ const size_t x = sizeof(MOD_EXT)-1;
+
+ if (len <= x)
+ continue;
+
+ if (0==memcmp(&dp->d_name[len-x], MOD_EXT, x))
+ ++n;
+ }
+
+ (void)closedir(dirp);
+
+ return n;
+}
+
+
+static const char *detect_module_path(bool *valid)
+{
+ static const char * const pathv[] = {
+#if defined (PREFIX)
+ "" PREFIX "/lib/baresip/modules",
+#else
+ "/usr/local/lib/baresip/modules",
+ "/usr/lib/baresip/modules",
+#endif
+ };
+ const char *current = pathv[0];
+ uint32_t nmax = 0;
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(pathv); i++) {
+
+ uint32_t n = count_modules(pathv[i]);
+
+ info("%s: detected %u modules\n", pathv[i], n);
+
+ if (n > nmax) {
+ nmax = n;
+ current = pathv[i];
+ }
+ }
+
+ if (nmax > 0)
+ *valid = true;
+
+ return current;
+}
+
+
+/**
+ * Write the baresip core config template to a file
+ *
+ * @param file Filename of output file
+ * @param cfg Baresip core config
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int config_write_template(const char *file, const struct config *cfg)
+{
+ FILE *f = NULL;
+ int err = 0;
+ const char *modpath;
+ bool modpath_valid = false;
+
+ if (!file || !cfg)
+ return EINVAL;
+
+ info("config: creating config template %s\n", file);
+
+ f = fopen(file, "w");
+ if (!f) {
+ warning("config: writing %s: %m\n", file, errno);
+ return errno;
+ }
+
+ (void)re_fprintf(f,
+ "#\n"
+ "# baresip configuration\n"
+ "#\n"
+ "\n"
+ "#------------------------------------"
+ "------------------------------------------\n");
+
+ (void)re_fprintf(f, "%H", core_config_template, cfg);
+
+ (void)re_fprintf(f,
+ "\n#------------------------------------"
+ "------------------------------------------\n"
+ "# Modules\n"
+ "\n");
+
+ modpath = detect_module_path(&modpath_valid);
+ (void)re_fprintf(f, "%smodule_path\t\t%s\n",
+ modpath_valid ? "" : "#", modpath);
+
+ (void)re_fprintf(f, "\n# UI Modules\n");
+#if defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "wincons" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stdio" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cons" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "evdev" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "httpd" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio codec Modules (in order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "opus" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "silk" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "amr" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g7221" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g722" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "g726" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "g711" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gsm" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "l16" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "bv32" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "mpa" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "codec2" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "ilbc" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "isac" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "vumeter" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sndfile" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_aec" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "speex_pp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "plc" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Audio driver Modules\n");
+#if defined (ANDROID)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opensles" MOD_EXT "\n");
+#elif defined (DARWIN)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "coreaudio" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "audiounit" MOD_EXT "\n");
+#elif defined (FREEBSD)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "oss" MOD_EXT "\n");
+#elif defined (OPENBSD)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "sndio" MOD_EXT "\n");
+#elif defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "winwave" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "alsa" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "pulse" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "jack" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "portaudio" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aubridge" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "aufile" MOD_EXT "\n");
+
+#ifdef USE_VIDEO
+
+ (void)re_fprintf(f, "\n# Video codec Modules (in order)\n");
+#ifdef USE_AVCODEC
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avcodec" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vp8" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vp9" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "h265" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video filter Modules (in encoding order)\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "selfview" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "snapshot" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "swscale" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vidinfo" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video source modules\n");
+#if defined (DARWIN)
+
+#ifdef QTCAPTURE_RUNLOOP
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "qtcapture" MOD_EXT "\n");
+#else
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "avcapture" MOD_EXT "\n");
+#endif
+
+#elif defined (WIN32)
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "dshow" MOD_EXT "\n");
+
+#else
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "v4l2_codec" MOD_EXT "\n");
+#endif
+#ifdef USE_AVFORMAT
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "avformat" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11grab" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "cairo" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "vidbridge" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Video display modules\n");
+#ifdef DARWIN
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "opengl" MOD_EXT "\n");
+#endif
+#ifdef LINUX
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "directfb" MOD_EXT "\n");
+#endif
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "x11" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "sdl2" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "fakevideo" MOD_EXT "\n");
+
+#endif /* USE_VIDEO */
+
+ (void)re_fprintf(f, "\n# Audio/Video source modules\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "rst" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst1" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "gst_video1" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media NAT modules\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "stun" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "turn" MOD_EXT "\n");
+ (void)re_fprintf(f, "module\t\t\t" MOD_PRE "ice" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "natpmp" MOD_EXT "\n");
+
+ (void)re_fprintf(f, "\n# Media encryption modules\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "dtls_srtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "#module\t\t\t" MOD_PRE "zrtp" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Temporary Modules (loaded then unloaded)\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "module_tmp\t\t" MOD_PRE "uuid" MOD_EXT "\n");
+ (void)re_fprintf(f, "module_tmp\t\t" MOD_PRE "account" MOD_EXT "\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Application Modules\n");
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "auloop"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "contact"MOD_EXT"\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "debug_cmd"MOD_EXT"\n");
+#ifdef LINUX
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "dtmfio"MOD_EXT"\n");
+#endif
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "echo"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t\t" MOD_PRE "gtk" MOD_EXT "\n");
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "menu"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mwi"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "natbd"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "presence"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "syslog"MOD_EXT"\n");
+ (void)re_fprintf(f, "#module_app\t\t" MOD_PRE "mqtt" MOD_EXT "\n");
+#ifdef USE_VIDEO
+ (void)re_fprintf(f, "module_app\t\t" MOD_PRE "vidloop"MOD_EXT"\n");
+#endif
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n#------------------------------------"
+ "------------------------------------------\n");
+ (void)re_fprintf(f, "# Module parameters\n");
+ (void)re_fprintf(f, "\n");
+
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "cons_listen\t\t0.0.0.0:5555\n");
+
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "http_listen\t\t0.0.0.0:8000\n");
+
+ (void)re_fprintf(f, "\n");
+ (void)re_fprintf(f, "evdev_device\t\t/dev/input/event0\n");
+
+ (void)re_fprintf(f, "\n# Speex codec parameters\n");
+ (void)re_fprintf(f, "speex_quality\t\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_complexity\t7 # 0-10\n");
+ (void)re_fprintf(f, "speex_enhancement\t0 # 0-1\n");
+ (void)re_fprintf(f, "speex_mode_nb\t\t3 # 1-6\n");
+ (void)re_fprintf(f, "speex_mode_wb\t\t6 # 1-6\n");
+ (void)re_fprintf(f, "speex_vbr\t\t0 # Variable Bit Rate 0-1\n");
+ (void)re_fprintf(f, "speex_vad\t\t0 # Voice Activity Detection 0-1\n");
+ (void)re_fprintf(f, "speex_agc_level\t\t8000\n");
+
+ (void)re_fprintf(f, "\n# Opus codec parameters\n");
+ (void)re_fprintf(f, "opus_bitrate\t\t28000 # 6000-510000\n");
+
+ (void)re_fprintf(f,
+ "\n# Selfview\n"
+ "video_selfview\t\twindow # {window,pip}\n"
+ "#selfview_size\t\t64x64\n");
+
+ (void)re_fprintf(f,
+ "\n# ICE\n"
+ "ice_turn\t\tno\n"
+ "ice_debug\t\tno\n"
+ "ice_nomination\t\tregular\t# {regular,aggressive}\n"
+ "ice_mode\t\tfull\t# {full,lite}\n");
+
+ (void)re_fprintf(f,
+ "\n# ZRTP\n"
+ "#zrtp_hash\t\tno # Disable SDP zrtp-hash "
+ "(not recommended)\n");
+
+ (void)re_fprintf(f,
+ "\n# Menu\n"
+ "#redial_attempts\t\t3 # Num or <inf>\n"
+ "#redial_delay\t\t5 # Delay in seconds\n");
+
+ if (f)
+ (void)fclose(f);
+
+ return err;
+}
+
+
+/**
+ * Get the baresip core config
+ *
+ * @return Core config
+ */
+struct config *conf_config(void)
+{
+ return &core_config;
+}
diff --git a/src/contact.c b/src/contact.c
new file mode 100644
index 0000000..a84890c
--- /dev/null
+++ b/src/contact.c
@@ -0,0 +1,338 @@
+/**
+ * @file src/contact.c Contacts handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+
+
+enum access {
+ ACCESS_UNKNOWN = 0,
+ ACCESS_BLOCK,
+ ACCESS_ALLOW
+};
+
+struct contact {
+ struct le le;
+ struct le he; /* hash-element with key 'auri' */
+ struct sip_addr addr;
+ char *buf;
+ enum presence_status status;
+ enum access access;
+};
+
+
+static void destructor(void *arg)
+{
+ struct contact *c = arg;
+
+ hash_unlink(&c->he);
+ list_unlink(&c->le);
+ mem_deref(c->buf);
+}
+
+
+/**
+ * Add a contact
+ *
+ * @param contacts Contacts container
+ * @param contactp Pointer to allocated contact (optional)
+ * @param addr Contact in SIP address format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_add(struct contacts *contacts,
+ struct contact **contactp, const struct pl *addr)
+{
+ struct contact *c;
+ struct pl pl;
+ int err;
+
+ if (!contacts)
+ return EINVAL;
+
+ c = mem_zalloc(sizeof(*c), destructor);
+ if (!c)
+ return ENOMEM;
+
+ err = pl_strdup(&c->buf, addr);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, c->buf);
+
+ err = sip_addr_decode(&c->addr, &pl);
+ if (err) {
+ warning("contact: decode error '%r'\n", addr);
+ goto out;
+ }
+
+ if (0 == msg_param_decode(&c->addr.params, "access", &pl)) {
+
+ if (0 == pl_strcasecmp(&pl, "block")) {
+ c->access = ACCESS_BLOCK;
+ }
+ else if (0 == pl_strcasecmp(&pl, "allow")) {
+ c->access = ACCESS_ALLOW;
+ }
+ else {
+ warning("contact: unknown 'access=%r' for '%r'\n",
+ &pl, addr);
+ err = EINVAL;
+ goto out;
+ }
+ }
+ else
+ c->access = ACCESS_UNKNOWN;
+
+ c->status = PRESENCE_UNKNOWN;
+
+ list_append(&contacts->cl, &c->le, c);
+ hash_append(contacts->cht, hash_joaat_pl(&c->addr.auri), &c->he, c);
+
+ if (contacts->handler)
+ contacts->handler(c, false, contacts->handler_arg);
+
+ out:
+ if (err)
+ mem_deref(c);
+ else if (contactp)
+ *contactp = c;
+
+ return err;
+}
+
+
+/**
+ * Remove a contact
+ *
+ * @param contacts Contacts container
+ * @param contact Contact to be removed
+ */
+void contact_remove(struct contacts *contacts, struct contact *contact)
+{
+ if (!contacts || !contact)
+ return;
+
+ if (contacts->handler)
+ contacts->handler(contact, true, contacts->handler_arg);
+
+ hash_unlink(&contact->he);
+ list_unlink(&contact->le);
+
+ mem_deref(contact);
+}
+
+
+void contact_set_update_handler(struct contacts *contacts,
+ contact_update_h *updateh, void *arg)
+{
+ if (!contacts) {
+ return;
+ }
+
+ contacts->handler = updateh;
+ contacts->handler_arg = arg;
+}
+
+
+/**
+ * Get the SIP address of a contact
+ *
+ * @param c Contact
+ *
+ * @return SIP Address
+ */
+struct sip_addr *contact_addr(const struct contact *c)
+{
+ return c ? (struct sip_addr *)&c->addr : NULL;
+}
+
+
+/**
+ * Get the contact string
+ *
+ * @param c Contact
+ *
+ * @return Contact string
+ */
+const char *contact_str(const struct contact *c)
+{
+ return c ? c->buf : NULL;
+}
+
+
+/**
+ * Get the list of contacts
+ *
+ * @param contacts Contacts container
+ *
+ * @return List of contacts
+ */
+struct list *contact_list(const struct contacts *contacts)
+{
+ if (!contacts)
+ return NULL;
+
+ return (struct list *)&contacts->cl;
+}
+
+
+void contact_set_presence(struct contact *c, enum presence_status status)
+{
+ if (!c)
+ return;
+
+ if (c->status != PRESENCE_UNKNOWN && c->status != status) {
+
+ info("<%r> changed status from %s to %s\n", &c->addr.auri,
+ contact_presence_str(c->status),
+ contact_presence_str(status));
+ }
+
+ c->status = status;
+}
+
+enum presence_status contact_presence(const struct contact *c)
+{
+ if (!c)
+ return PRESENCE_UNKNOWN;
+
+ return c->status;
+}
+
+const char *contact_presence_str(enum presence_status status)
+{
+ switch (status) {
+
+ default:
+ case PRESENCE_UNKNOWN: return "\x1b[32mUnknown\x1b[;m";
+ case PRESENCE_OPEN: return "\x1b[32mOnline\x1b[;m";
+ case PRESENCE_CLOSED: return "\x1b[31mOffline\x1b[;m";
+ case PRESENCE_BUSY: return "\x1b[31mBusy\x1b[;m";
+ }
+}
+
+
+int contacts_print(struct re_printf *pf, const struct contacts *contacts)
+{
+ const struct list *lst;
+ struct le *le;
+ int err;
+
+ if (!contacts)
+ return 0;
+
+ lst = contact_list(contacts);
+
+ err = re_hprintf(pf, "\n--- Contacts: (%u) ---\n",
+ list_count(lst));
+
+ for (le = list_head(lst); le && !err; le = le->next) {
+ const struct contact *c = le->data;
+ const struct sip_addr *addr = &c->addr;
+
+ err = re_hprintf(pf, "%20s %r <%r>\n",
+ contact_presence_str(c->status),
+ &addr->dname, &addr->auri);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Initialise the contacts sub-system
+ *
+ * @param contacts Contacts container
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int contact_init(struct contacts *contacts)
+{
+ int err = 0;
+
+ if (!contacts)
+ return EINVAL;
+
+ memset(contacts, 0, sizeof(*contacts));
+
+ list_init(&contacts->cl);
+
+ err = hash_alloc(&contacts->cht, 32);
+
+ return err;
+}
+
+
+/**
+ * @param contacts Contacts container
+ *
+ * Close the contacts sub-system
+ */
+void contact_close(struct contacts *contacts)
+{
+ if (!contacts)
+ return;
+
+ hash_clear(contacts->cht);
+ contacts->cht = mem_deref(contacts->cht);
+ list_flush(&contacts->cl);
+}
+
+
+static bool find_handler(struct le *le, void *arg)
+{
+ struct contact *c = le->data;
+
+ return 0 == pl_strcmp(&c->addr.auri, arg);
+}
+
+
+/**
+ * Lookup a SIP uri in all registered contacts
+ *
+ * @param contacts Contacts container
+ * @param uri SIP uri to lookup
+ *
+ * @return Matching contact if found, otherwise NULL
+ */
+struct contact *contact_find(const struct contacts *contacts, const char *uri)
+{
+ if (!contacts)
+ return NULL;
+
+ return list_ledata(hash_lookup(contacts->cht, hash_joaat_str(uri),
+ find_handler, (void *)uri));
+}
+
+
+/**
+ * Check the access parameter of a SIP uri
+ *
+ * - Matching uri has first presedence
+ * - Global <sip:*@*> uri has second presedence
+ *
+ * @param contacts Contacts container
+ * @param uri SIP uri to check for access
+ *
+ * @return True if blocked, false if allowed
+ */
+bool contact_block_access(const struct contacts *contacts, const char *uri)
+{
+ struct contact *c;
+
+ c = contact_find(contacts, uri);
+ if (c && c->access != ACCESS_UNKNOWN)
+ return c->access == ACCESS_BLOCK;
+
+ c = contact_find(contacts, "sip:*@*");
+ if (c && c->access != ACCESS_UNKNOWN)
+ return c->access == ACCESS_BLOCK;
+
+ return false;
+}
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..a390e0c
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,541 @@
+/**
+ * @file core.h Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#include <limits.h>
+
+
+/* max bytes in pathname */
+#if defined (PATH_MAX)
+#define FS_PATH_MAX PATH_MAX
+#elif defined (_POSIX_PATH_MAX)
+#define FS_PATH_MAX _POSIX_PATH_MAX
+#else
+#define FS_PATH_MAX 512
+#endif
+
+
+/**
+ * RFC 3551:
+ *
+ * 0 - 95 Static payload types
+ * 96 - 127 Dynamic payload types
+ */
+enum {
+ PT_CN = 13,
+ PT_STAT_MIN = 0,
+ PT_STAT_MAX = 95,
+ PT_DYN_MIN = 96,
+ PT_DYN_MAX = 127
+};
+
+
+/** Media constants */
+enum {
+ AUDIO_BANDWIDTH = 128000 /**< Bandwidth for audio in bits/s */
+};
+
+
+/* forward declarations */
+struct stream_param;
+
+
+/*
+ * Account
+ */
+
+
+struct account {
+ char *buf; /**< Buffer for the SIP address */
+ struct sip_addr laddr; /**< Decoded SIP address */
+ struct uri luri; /**< Decoded AOR uri */
+ char *dispname; /**< Display name */
+ char *aor; /**< Local SIP uri */
+
+ /* parameters: */
+ enum answermode answermode; /**< Answermode for incoming calls */
+ struct le acv[8]; /**< List elements for aucodecl */
+ struct list aucodecl; /**< List of preferred audio-codecs */
+ char *auth_user; /**< Authentication username */
+ char *auth_pass; /**< Authentication password */
+ char *mnatid; /**< Media NAT handling */
+ char *mencid; /**< Media encryption type */
+ const struct mnat *mnat; /**< MNAT module */
+ const struct menc *menc; /**< MENC module */
+ char *outboundv[2]; /**< Optional SIP outbound proxies */
+ uint32_t ptime; /**< Configured packet time in [ms] */
+ uint32_t regint; /**< Registration interval in [seconds] */
+ uint32_t pubint; /**< Publication interval in [seconds] */
+ char *regq; /**< Registration Q-value */
+ char *rtpkeep; /**< RTP Keepalive mechanism */
+ char *sipnat; /**< SIP Nat mechanism */
+ char *stun_user; /**< STUN Username */
+ char *stun_pass; /**< STUN Password */
+ char *stun_host; /**< STUN Hostname */
+ uint16_t stun_port; /**< STUN Port number */
+ struct le vcv[4]; /**< List elements for vidcodecl */
+ struct list vidcodecl; /**< List of preferred video-codecs */
+};
+
+
+/*
+ * Audio Player
+ */
+
+struct auplay_st {
+ struct auplay *ap;
+};
+
+struct auplay {
+ struct le le;
+ const char *name;
+ auplay_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Source
+ */
+
+struct ausrc_st {
+ const struct ausrc *as;
+};
+
+struct ausrc {
+ struct le le;
+ const char *name;
+ ausrc_alloc_h *alloch;
+};
+
+
+/*
+ * Audio Stream
+ */
+
+struct audio;
+
+typedef void (audio_event_h)(int key, bool end, void *arg);
+typedef void (audio_err_h)(int err, const char *str, void *arg);
+
+int audio_alloc(struct audio **ap, const struct stream_param *stream_prm,
+ const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ uint32_t ptime, const struct list *aucodecl, bool offerer,
+ audio_event_h *eventh, audio_err_h *errh, void *arg);
+int audio_start(struct audio *a);
+void audio_stop(struct audio *a);
+int audio_encoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_tx, const char *params);
+int audio_decoder_set(struct audio *a, const struct aucodec *ac,
+ int pt_rx, const char *params);
+int audio_send_digit(struct audio *a, char key);
+void audio_sdp_attr_decode(struct audio *a);
+int audio_print_rtpstat(struct re_printf *pf, const struct audio *au);
+
+
+/*
+ * BFCP
+ */
+
+struct bfcp;
+int bfcp_alloc(struct bfcp **bfcpp, struct sdp_session *sdp_sess,
+ const char *proto, bool offerer,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess);
+int bfcp_start(struct bfcp *bfcp);
+
+
+/*
+ * Call Control
+ */
+
+enum {
+ CALL_LINENUM_MIN = 1,
+ CALL_LINENUM_MAX = 256
+};
+
+struct call;
+
+/** Call parameters */
+struct call_prm {
+ struct sa laddr;
+ enum vidmode vidmode;
+ int af;
+ bool use_rtp;
+};
+
+int call_alloc(struct call **callp, const struct config *cfg,
+ struct list *lst,
+ const char *local_name, const char *local_uri,
+ struct account *acc, struct ua *ua, const struct call_prm *prm,
+ const struct sip_msg *msg, struct call *xcall,
+ struct dnsc *dnsc,
+ call_event_h *eh, void *arg);
+int call_connect(struct call *call, const struct pl *paddr);
+int call_accept(struct call *call, struct sipsess_sock *sess_sock,
+ const struct sip_msg *msg);
+int call_hangup(struct call *call, uint16_t scode, const char *reason);
+int call_progress(struct call *call);
+int call_answer(struct call *call, uint16_t scode);
+int call_sdp_get(const struct call *call, struct mbuf **descp, bool offer);
+int call_jbuf_stat(struct re_printf *pf, const struct call *call);
+int call_info(struct re_printf *pf, const struct call *call);
+int call_reset_transp(struct call *call, const struct sa *laddr);
+int call_notify_sipfrag(struct call *call, uint16_t scode,
+ const char *reason, ...);
+int call_af(const struct call *call);
+void call_set_xrtpstat(struct call *call);
+struct account *call_account(const struct call *call);
+
+
+/*
+ * Conf
+ */
+
+int conf_get_range(const struct conf *conf, const char *name,
+ struct range *rng);
+int conf_get_csv(const struct conf *conf, const char *name,
+ char *str1, size_t sz1, char *str2, size_t sz2);
+
+
+/*
+ * Media control
+ */
+
+int mctrl_handle_media_control(struct pl *body, bool *pfu);
+
+
+/*
+ * Media NAT traversal
+ */
+
+struct mnat {
+ struct le le;
+ const char *id;
+ const char *ftag;
+ mnat_sess_h *sessh;
+ mnat_media_h *mediah;
+ mnat_update_h *updateh;
+};
+
+const struct mnat *mnat_find(const struct list *mnatl, const char *id);
+
+
+/*
+ * Metric
+ */
+
+struct metric {
+ /* internal stuff: */
+ struct tmr tmr;
+ uint64_t ts_start;
+ bool started;
+
+ /* counters: */
+ uint32_t n_packets;
+ uint32_t n_bytes;
+ uint32_t n_err;
+
+ /* bitrate calculation */
+ uint32_t cur_bitrate;
+ uint64_t ts_last;
+ uint32_t n_bytes_last;
+};
+
+void metric_init(struct metric *metric);
+void metric_reset(struct metric *metric);
+void metric_add_packet(struct metric *metric, size_t packetsize);
+uint32_t metric_avg_bitrate(const struct metric *metric);
+
+
+/*
+ * Module
+ */
+
+int module_init(const struct conf *conf);
+void module_app_unload(void);
+
+
+/*
+ * Register client
+ */
+
+struct reg;
+
+int reg_add(struct list *lst, struct ua *ua, int regid);
+int reg_register(struct reg *reg, const char *reg_uri,
+ const char *params, uint32_t regint, const char *outbound);
+void reg_unregister(struct reg *reg);
+bool reg_isok(const struct reg *reg);
+int reg_debug(struct re_printf *pf, const struct reg *reg);
+int reg_status(struct re_printf *pf, const struct reg *reg);
+
+
+/*
+ * RTP Header Extensions
+ */
+
+#define RTPEXT_HDR_SIZE 4
+#define RTPEXT_TYPE_MAGIC 0xbede
+
+enum {
+ RTPEXT_ID_MIN = 1,
+ RTPEXT_ID_MAX = 14,
+};
+
+enum {
+ RTPEXT_LEN_MIN = 1,
+ RTPEXT_LEN_MAX = 16,
+};
+
+struct rtpext {
+ unsigned id:4;
+ unsigned len:4;
+ uint8_t data[RTPEXT_LEN_MAX];
+};
+
+
+int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes);
+int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len,
+ const uint8_t *data);
+int rtpext_decode(struct rtpext *ext, struct mbuf *mb);
+
+
+/*
+ * RTP keepalive
+ */
+
+struct rtpkeep;
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp);
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts);
+
+
+/*
+ * SDP
+ */
+
+int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb);
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m);
+
+
+/*
+ * Stream
+ */
+
+struct rtp_header;
+
+enum {STREAM_PRESZ = 4+12}; /* same as RTP_HEADER_SIZE */
+
+typedef void (stream_rtp_h)(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg);
+typedef void (stream_rtcp_h)(struct rtcp_msg *msg, void *arg);
+
+typedef void (stream_error_h)(struct stream *strm, int err, void *arg);
+
+/** Common parameters for media stream */
+struct stream_param {
+ bool use_rtp;
+};
+
+/** Defines a generic media stream */
+struct stream {
+ struct le le; /**< Linked list element */
+ struct config_avt cfg; /**< Stream configuration */
+ struct call *call; /**< Ref. to call object */
+ struct sdp_media *sdp; /**< SDP Media line */
+ struct rtp_sock *rtp; /**< RTP Socket */
+ struct rtpkeep *rtpkeep; /**< RTP Keepalive */
+ struct rtcp_stats rtcp_stats;/**< RTCP statistics */
+ struct jbuf *jbuf; /**< Jitter Buffer for incoming RTP */
+ struct mnat_media *mns; /**< Media NAT traversal state */
+ const struct menc *menc; /**< Media encryption module */
+ struct menc_sess *mencs; /**< Media encryption session state */
+ struct menc_media *mes; /**< Media Encryption media state */
+ struct metric metric_tx; /**< Metrics for transmit */
+ struct metric metric_rx; /**< Metrics for receiving */
+ char *cname; /**< RTCP Canonical end-point identifier */
+ uint32_t ssrc_rx; /**< Incoming syncronizing source */
+ uint32_t pseq; /**< Sequence number for incoming RTP */
+ int pt_enc; /**< Payload type for encoding */
+ bool rtcp; /**< Enable RTCP */
+ bool rtcp_mux; /**< RTP/RTCP multiplex supported by peer */
+ bool jbuf_started; /**< True if jitter-buffer was started */
+ stream_rtp_h *rtph; /**< Stream RTP handler */
+ stream_rtcp_h *rtcph; /**< Stream RTCP handler */
+ void *arg; /**< Handler argument */
+ stream_error_h *errorh; /**< Stream error handler */
+ void *errorh_arg; /**< Error handler argument */
+ struct tmr tmr_rtp; /**< Timer for detecting RTP timeout */
+ uint64_t ts_last; /**< Timestamp of last received RTP pkt */
+ bool terminated; /**< Stream is terminated flag */
+ uint32_t rtp_timeout_ms; /**< RTP Timeout value in [ms] */
+};
+
+int stream_alloc(struct stream **sp, const struct stream_param *prm,
+ const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg);
+struct sdp_media *stream_sdpmedia(const struct stream *s);
+int stream_send(struct stream *s, bool ext, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb);
+void stream_update(struct stream *s);
+void stream_update_encoder(struct stream *s, int pt_enc);
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s);
+void stream_hold(struct stream *s, bool hold);
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx);
+void stream_send_fir(struct stream *s, bool pli);
+void stream_reset(struct stream *s);
+void stream_set_bw(struct stream *s, uint32_t bps);
+void stream_set_error_handler(struct stream *strm,
+ stream_error_h *errorh, void *arg);
+int stream_debug(struct re_printf *pf, const struct stream *s);
+int stream_print(struct re_printf *pf, const struct stream *s);
+void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms);
+
+
+/*
+ * User-Agent
+ */
+
+struct ua;
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...);
+void ua_printf(const struct ua *ua, const char *fmt, ...);
+
+struct tls *uag_tls(void);
+const char *uag_allowed_methods(void);
+
+
+/*
+ * Video Display
+ */
+
+struct vidisp {
+ struct le le;
+ const char *name;
+ vidisp_alloc_h *alloch;
+ vidisp_update_h *updateh;
+ vidisp_disp_h *disph;
+ vidisp_hide_h *hideh;
+};
+
+struct vidisp *vidisp_get(struct vidisp_st *st);
+
+
+/*
+ * Video Source
+ */
+
+struct vidsrc {
+ struct le le;
+ const char *name;
+ vidsrc_alloc_h *alloch;
+ vidsrc_update_h *updateh;
+};
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st);
+
+
+/*
+ * Video Stream
+ */
+
+struct video;
+
+typedef void (video_err_h)(int err, const char *str, void *arg);
+
+int video_alloc(struct video **vp, const struct stream_param *stream_prm,
+ const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *content, const struct list *vidcodecl,
+ video_err_h *errh, void *arg);
+int video_start(struct video *v, const char *peer);
+void video_stop(struct video *v);
+bool video_is_started(const struct video *v);
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params);
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp);
+void video_update_picture(struct video *v);
+void video_sdp_attr_decode(struct video *v);
+int video_print(struct re_printf *pf, const struct video *v);
+
+
+/*
+ * Timestamp helpers
+ */
+
+
+/**
+ * This struct is used to keep track of timestamps for
+ * incoming RTP packets.
+ */
+struct timestamp_recv {
+ uint32_t first;
+ uint32_t last;
+ bool is_set;
+ unsigned num_wraps;
+};
+
+
+static inline uint64_t calc_extended_timestamp(uint32_t num_wraps, uint32_t ts)
+{
+ uint64_t ext_ts;
+
+ ext_ts = (uint64_t)num_wraps * 0x100000000ULL;
+ ext_ts += (uint64_t)ts;
+
+ return ext_ts;
+}
+
+
+static inline uint64_t timestamp_duration(const struct timestamp_recv *ts)
+{
+ uint64_t last_ext;
+
+ if (!ts || !ts->is_set)
+ return 0;
+
+ last_ext = calc_extended_timestamp(ts->num_wraps, ts->last);
+
+ return last_ext - ts->first;
+}
+
+
+/*
+ * -1 backwards wrap-around
+ * 0 no wrap-around
+ * 1 forward wrap-around
+ */
+static inline int timestamp_wrap(uint32_t ts_new, uint32_t ts_old)
+{
+ int32_t delta;
+
+ if (ts_new < ts_old) {
+
+ delta = (int32_t)ts_new - (int32_t)ts_old;
+
+ if (delta > 0)
+ return 1;
+ }
+ else if ((int32_t)(ts_old - ts_new) > 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..b29ab8b
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,171 @@
+/**
+ * @file src/event.c Baresip event handling
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static const char *event_class_name(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING:
+ case UA_EVENT_REGISTER_OK:
+ case UA_EVENT_REGISTER_FAIL:
+ case UA_EVENT_UNREGISTERING:
+ return "register";
+
+ case UA_EVENT_SHUTDOWN:
+ case UA_EVENT_EXIT:
+ return "application";
+
+ case UA_EVENT_CALL_INCOMING:
+ case UA_EVENT_CALL_RINGING:
+ case UA_EVENT_CALL_PROGRESS:
+ case UA_EVENT_CALL_ESTABLISHED:
+ case UA_EVENT_CALL_CLOSED:
+ case UA_EVENT_CALL_TRANSFER_FAILED:
+ case UA_EVENT_CALL_DTMF_START:
+ case UA_EVENT_CALL_DTMF_END:
+ case UA_EVENT_CALL_RTCP:
+ return "call";
+
+ default:
+ return "other";
+ }
+}
+
+
+static int add_rtcp_stats(struct odict *od_parent, const struct rtcp_stats *rs)
+{
+ struct odict *od = NULL, *tx = NULL, *rx = NULL;
+ int err = 0;
+
+ if (!od_parent || !rs)
+ return EINVAL;
+
+ err = odict_alloc(&od, 8);
+ err |= odict_alloc(&tx, 8);
+ err |= odict_alloc(&rx, 8);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(tx, "sent", ODICT_INT, (int64_t)rs->tx.sent);
+ err |= odict_entry_add(tx, "lost", ODICT_INT, (int64_t)rs->tx.lost);
+ err |= odict_entry_add(tx, "jit", ODICT_INT, (int64_t)rs->tx.jit);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(rx, "sent", ODICT_INT, (int64_t)rs->rx.sent);
+ err |= odict_entry_add(rx, "lost", ODICT_INT, (int64_t)rs->rx.lost);
+ err |= odict_entry_add(rx, "jit", ODICT_INT, (int64_t)rs->rx.jit);
+ if (err)
+ goto out;
+
+ err = odict_entry_add(od, "tx", ODICT_OBJECT, tx);
+ err |= odict_entry_add(od, "rx", ODICT_OBJECT, rx);
+ err |= odict_entry_add(od, "rtt", ODICT_INT, (int64_t)rs->rtt);
+ if (err)
+ goto out;
+
+ /* add object to the parent */
+ err = odict_entry_add(od_parent, "rtcp_stats", ODICT_OBJECT, od);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(od);
+
+ return err;
+}
+
+
+int event_encode_dict(struct odict *od, struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm)
+{
+ const char *event_str = uag_event_str(ev);
+ int err = 0;
+
+ if (!od)
+ return EINVAL;
+
+ err |= odict_entry_add(od, "type", ODICT_STRING, event_str);
+ err |= odict_entry_add(od, "class",
+ ODICT_STRING, event_class_name(ev));
+ err |= odict_entry_add(od, "accountaor", ODICT_STRING, ua_aor(ua));
+ if (err)
+ goto out;
+
+ if (call) {
+
+ const char *dir;
+
+ dir = call_is_outgoing(call) ? "outgoing" : "incoming";
+
+ err |= odict_entry_add(od, "direction", ODICT_STRING, dir);
+ err |= odict_entry_add(od, "peeruri",
+ ODICT_STRING, call_peeruri(call));
+ if (err)
+ goto out;
+ }
+
+ if (str_isset(prm)) {
+ err = odict_entry_add(od, "param", ODICT_STRING, prm);
+ if (err)
+ goto out;
+ }
+
+ if (ev == UA_EVENT_CALL_RTCP) {
+ struct stream *strm = NULL;
+
+ if (0 == str_casecmp(prm, "audio"))
+ strm = audio_strm(call_audio(call));
+#ifdef USE_VIDEO
+ else if (0 == str_casecmp(prm, "video"))
+ strm = video_strm(call_video(call));
+#endif
+
+ err = add_rtcp_stats(od, stream_rtcp_stats(strm));
+ if (err)
+ goto out;
+ }
+
+ out:
+
+ return err;
+}
+
+
+/**
+ * Get the name of the User-Agent event
+ *
+ * @param ev User-Agent event
+ *
+ * @return Name of the event
+ */
+const char *uag_event_str(enum ua_event ev)
+{
+ switch (ev) {
+
+ case UA_EVENT_REGISTERING: return "REGISTERING";
+ case UA_EVENT_REGISTER_OK: return "REGISTER_OK";
+ case UA_EVENT_REGISTER_FAIL: return "REGISTER_FAIL";
+ case UA_EVENT_UNREGISTERING: return "UNREGISTERING";
+ case UA_EVENT_SHUTDOWN: return "SHUTDOWN";
+ case UA_EVENT_EXIT: return "EXIT";
+ case UA_EVENT_CALL_INCOMING: return "CALL_INCOMING";
+ case UA_EVENT_CALL_RINGING: return "CALL_RINGING";
+ case UA_EVENT_CALL_PROGRESS: return "CALL_PROGRESS";
+ case UA_EVENT_CALL_ESTABLISHED: return "CALL_ESTABLISHED";
+ case UA_EVENT_CALL_CLOSED: return "CALL_CLOSED";
+ case UA_EVENT_CALL_TRANSFER_FAILED: return "TRANSFER_FAILED";
+ case UA_EVENT_CALL_DTMF_START: return "CALL_DTMF_START";
+ case UA_EVENT_CALL_DTMF_END: return "CALL_DTMF_END";
+ case UA_EVENT_CALL_RTCP: return "CALL_RTCP";
+ default: return "?";
+ }
+}
diff --git a/src/h264.c b/src/h264.c
new file mode 100644
index 0000000..2bb4a26
--- /dev/null
+++ b/src/h264.c
@@ -0,0 +1,182 @@
+/**
+ * @file src/h264.c H.264 video codec packetization (RFC 3984)
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+
+
+int h264_hdr_encode(const struct h264_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ v = hdr->f<<7 | hdr->nri<<5 | hdr->type<<0;
+
+ return mbuf_write_u8(mb, v);
+}
+
+
+int h264_hdr_decode(struct h264_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENOENT;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->f = v>>7 & 0x1;
+ hdr->nri = v>>5 & 0x3;
+ hdr->type = v>>0 & 0x1f;
+
+ return 0;
+}
+
+
+int h264_fu_hdr_encode(const struct h264_fu *fu, struct mbuf *mb)
+{
+ uint8_t v = fu->s<<7 | fu->s<<6 | fu->r<<5 | fu->type;
+ return mbuf_write_u8(mb, v);
+}
+
+
+int h264_fu_hdr_decode(struct h264_fu *fu, struct mbuf *mb)
+{
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENOENT;
+
+ v = mbuf_read_u8(mb);
+
+ fu->s = v>>7 & 0x1;
+ fu->e = v>>6 & 0x1;
+ fu->r = v>>5 & 0x1;
+ fu->type = v>>0 & 0x1f;
+
+ return 0;
+}
+
+
+/*
+ * Find the NAL start sequence in a H.264 byte stream
+ *
+ * @note: copied from ffmpeg source
+ */
+const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end)
+{
+ const uint8_t *a = p + 4 - ((long)p & 3);
+
+ for (end -= 3; p < a && p < end; p++ ) {
+ if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+ return p;
+ }
+
+ for (end -= 3; p < end; p += 4) {
+ uint32_t x = *(const uint32_t*)(void *)p;
+ if ( (x - 0x01010101) & (~x) & 0x80808080 ) {
+ if (p[1] == 0 ) {
+ if ( p[0] == 0 && p[2] == 1 )
+ return p;
+ if ( p[2] == 0 && p[3] == 1 )
+ return p+1;
+ }
+ if ( p[3] == 0 ) {
+ if ( p[2] == 0 && p[4] == 1 )
+ return p+2;
+ if ( p[4] == 0 && p[5] == 1 )
+ return p+3;
+ }
+ }
+ }
+
+ for (end += 3; p < end; p++) {
+ if (p[0] == 0 && p[1] == 0 && p[2] == 1)
+ return p;
+ }
+
+ return end + 3;
+}
+
+
+static int rtp_send_data(const uint8_t *hdr, size_t hdr_sz,
+ const uint8_t *buf, size_t sz,
+ bool eof, uint32_t rtp_ts,
+ videnc_packet_h *pkth, void *arg)
+{
+ return pkth(eof, rtp_ts, hdr, hdr_sz, buf, sz, arg);
+}
+
+
+int h264_nal_send(bool first, bool last,
+ bool marker, uint32_t ihdr, uint32_t rtp_ts,
+ const uint8_t *buf, size_t size, size_t maxsz,
+ videnc_packet_h *pkth, void *arg)
+{
+ uint8_t hdr = (uint8_t)ihdr;
+ int err = 0;
+
+ if (first && last && size <= maxsz) {
+ err = rtp_send_data(&hdr, 1, buf, size, marker, rtp_ts,
+ pkth, arg);
+ }
+ else {
+ uint8_t fu_hdr[2];
+ const uint8_t type = hdr & 0x1f;
+ const uint8_t nri = hdr & 0x60;
+ const size_t sz = maxsz - 2;
+
+ fu_hdr[0] = nri | H264_NAL_FU_A;
+ fu_hdr[1] = first ? (1<<7 | type) : type;
+
+ while (size > sz) {
+ err |= rtp_send_data(fu_hdr, 2, buf, sz, false,
+ rtp_ts,
+ pkth, arg);
+ buf += sz;
+ size -= sz;
+ fu_hdr[1] &= ~(1 << 7);
+ }
+
+ if (last)
+ fu_hdr[1] |= 1<<6; /* end bit */
+
+ err |= rtp_send_data(fu_hdr, 2, buf, size, marker && last,
+ rtp_ts,
+ pkth, arg);
+ }
+
+ return err;
+}
+
+
+int h264_packetize(uint32_t rtp_ts, const uint8_t *buf, size_t len,
+ size_t pktsize, videnc_packet_h *pkth, void *arg)
+{
+ const uint8_t *start = buf;
+ const uint8_t *end = buf + len;
+ const uint8_t *r;
+ int err = 0;
+
+ r = h264_find_startcode(start, end);
+
+ while (r < end) {
+ const uint8_t *r1;
+
+ /* skip zeros */
+ while (!*(r++))
+ ;
+
+ r1 = h264_find_startcode(r, end);
+
+ err |= h264_nal_send(true, true, (r1 >= end), r[0],
+ rtp_ts, r+1, r1-r-1, pktsize,
+ pkth, arg);
+ r = r1;
+ }
+
+ return err;
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..650689d
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,153 @@
+/**
+ * @file log.c Logging
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+static struct {
+ struct list logl;
+ bool debug;
+ bool info;
+ bool stder;
+} lg = {
+ LIST_INIT,
+ false,
+ true,
+ true
+};
+
+
+void log_register_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_append(&lg.logl, &log->le, log);
+}
+
+
+void log_unregister_handler(struct log *log)
+{
+ if (!log)
+ return;
+
+ list_unlink(&log->le);
+}
+
+
+void log_enable_debug(bool enable)
+{
+ lg.debug = enable;
+}
+
+
+void log_enable_info(bool enable)
+{
+ lg.info = enable;
+}
+
+
+void log_enable_stderr(bool enable)
+{
+ lg.stder = enable;
+}
+
+
+void vlog(enum log_level level, const char *fmt, va_list ap)
+{
+ char buf[4096];
+ struct le *le;
+
+ if (re_vsnprintf(buf, sizeof(buf), fmt, ap) < 0)
+ return;
+
+ if (lg.stder) {
+
+ bool color = level == LEVEL_WARN || level == LEVEL_ERROR;
+
+ if (color)
+ (void)re_fprintf(stdout, "\x1b[31m"); /* Red */
+
+ (void)re_fprintf(stdout, "%s", buf);
+
+ if (color)
+ (void)re_fprintf(stdout, "\x1b[;m");
+ }
+
+ le = lg.logl.head;
+
+ while (le) {
+
+ struct log *log = le->data;
+ le = le->next;
+
+ if (log->h)
+ log->h(level, buf);
+ }
+}
+
+
+void loglv(enum log_level level, const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((LEVEL_DEBUG == level) && !lg.debug)
+ return;
+
+ if ((LEVEL_INFO == level) && !lg.info)
+ return;
+
+ va_start(ap, fmt);
+ vlog(level, fmt, ap);
+ va_end(ap);
+}
+
+
+void debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!lg.debug)
+ return;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+
+void info(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!lg.info)
+ return;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_INFO, fmt, ap);
+ va_end(ap);
+}
+
+
+void warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_WARN, fmt, ap);
+ va_end(ap);
+}
+
+
+void error_msg(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(LEVEL_ERROR, fmt, ap);
+ va_end(ap);
+}
diff --git a/src/magic.h b/src/magic.h
new file mode 100644
index 0000000..523d38a
--- /dev/null
+++ b/src/magic.h
@@ -0,0 +1,38 @@
+/**
+ * @file magic.h Interface to magic macros
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef RELEASE
+
+#ifndef MAGIC
+#error "macro MAGIC must be defined"
+#endif
+
+
+/*
+ * Any C compiler conforming to C99 or later MUST support __func__
+ */
+#if __STDC_VERSION__ >= 199901L
+#define __MAGIC_FUNC__ (const char *)__func__
+#else
+#define __MAGIC_FUNC__ __FUNCTION__
+#endif
+
+
+/** Check magic number */
+#define MAGIC_DECL uint32_t magic;
+#define MAGIC_INIT(s) (s)->magic = MAGIC
+#define MAGIC_CHECK(s) \
+ if (MAGIC != s->magic) { \
+ warning("%s: wrong magic struct=%p (magic=0x%08x)\n", \
+ __MAGIC_FUNC__, s, s->magic); \
+ BREAKPOINT; \
+ }
+#else
+#define MAGIC_DECL
+#define MAGIC_INIT(s)
+#define MAGIC_CHECK(s) do {(void)(s);} while (0);
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..b789648
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,265 @@
+/**
+ * @file src/main.c Main application code
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#ifdef SOLARIS
+#define __EXTENSIONS__ 1
+#endif
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_GETOPT
+#include <getopt.h>
+#endif
+#include <re.h>
+#include <baresip.h>
+
+
+static void signal_handler(int sig)
+{
+ static bool term = false;
+
+ if (term) {
+ mod_close();
+ exit(0);
+ }
+
+ term = true;
+
+ info("terminated by signal %d\n", sig);
+
+ ua_stop_all(false);
+}
+
+
+static void ua_exit_handler(void *arg)
+{
+ (void)arg;
+ debug("ua exited -- stopping main runloop\n");
+
+ /* The main run-loop can be stopped now */
+ re_cancel();
+}
+
+
+static void usage(void)
+{
+ (void)re_fprintf(stderr,
+ "Usage: baresip [options]\n"
+ "options:\n"
+#if HAVE_INET6
+ "\t-6 Prefer IPv6\n"
+#endif
+ "\t-d Daemon\n"
+ "\t-e <commands> Execute commands (repeat)\n"
+ "\t-f <path> Config path\n"
+ "\t-m <module> Pre-load modules (repeat)\n"
+ "\t-p <path> Audio files\n"
+ "\t-h -? Help\n"
+ "\t-t Test and exit\n"
+ "\t-u <parameters> Extra UA parameters\n"
+ "\t-v Verbose debug\n"
+ );
+}
+
+
+int main(int argc, char *argv[])
+{
+ bool prefer_ipv6 = false, run_daemon = false, test = false;
+ const char *ua_eprm = NULL;
+ const char *execmdv[16];
+ const char *audio_path = NULL;
+ const char *modv[16];
+ size_t execmdc = 0;
+ size_t modc = 0;
+ size_t i;
+ int err;
+
+ (void)re_fprintf(stdout, "baresip v%s"
+ " Copyright (C) 2010 - 2017"
+ " Alfred E. Heggestad et al.\n",
+ BARESIP_VERSION);
+
+ (void)sys_coredump_set(true);
+
+ err = libre_init();
+ if (err)
+ goto out;
+
+#ifdef HAVE_GETOPT
+ for (;;) {
+ const int c = getopt(argc, argv, "6de:f:p:hu:vtm:");
+ if (0 > c)
+ break;
+
+ switch (c) {
+
+ case '?':
+ case 'h':
+ usage();
+ return -2;
+
+#if HAVE_INET6
+ case '6':
+ prefer_ipv6 = true;
+ break;
+#endif
+
+ case 'd':
+ run_daemon = true;
+ break;
+
+ case 'e':
+ if (execmdc >= ARRAY_SIZE(execmdv)) {
+ warning("max %zu commands\n",
+ ARRAY_SIZE(execmdv));
+ err = EINVAL;
+ goto out;
+ }
+ execmdv[execmdc++] = optarg;
+ break;
+
+ case 'f':
+ conf_path_set(optarg);
+ break;
+
+ case 'm':
+ if (modc >= ARRAY_SIZE(modv)) {
+ warning("max %zu modules\n",
+ ARRAY_SIZE(modv));
+ err = EINVAL;
+ goto out;
+ }
+ modv[modc++] = optarg;
+ break;
+
+ case 'p':
+ audio_path = optarg;
+ break;
+
+ case 't':
+ test = true;
+ break;
+
+ case 'u':
+ ua_eprm = optarg;
+ break;
+
+ case 'v':
+ log_enable_debug(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ err = conf_configure();
+ if (err) {
+ warning("main: configure failed: %m\n", err);
+ goto out;
+ }
+
+ /*
+ * Initialise the top-level baresip struct, must be
+ * done AFTER configuration is complete.
+ */
+ err = baresip_init(conf_config(), prefer_ipv6);
+ if (err) {
+ warning("main: baresip init failed (%m)\n", err);
+ goto out;
+ }
+
+ /* Set audio path preferring the one given in -p argument (if any) */
+ if (audio_path)
+ play_set_path(baresip_player(), audio_path);
+ else if (str_isset(conf_config()->audio.audio_path)) {
+ play_set_path(baresip_player(),
+ conf_config()->audio.audio_path);
+ }
+
+ /* NOTE: must be done after all arguments are processed */
+ if (modc) {
+
+ info("pre-loading modules: %zu\n", modc);
+
+ for (i=0; i<modc; i++) {
+
+ err = module_preload(modv[i]);
+ if (err) {
+ re_fprintf(stderr,
+ "could not pre-load module"
+ " '%s' (%m)\n", modv[i], err);
+ }
+ }
+ }
+
+ /* Initialise User Agents */
+ err = ua_init("baresip v" BARESIP_VERSION " (" ARCH "/" OS ")",
+ true, true, true, prefer_ipv6);
+ if (err)
+ goto out;
+
+ uag_set_exit_handler(ua_exit_handler, NULL);
+
+ if (ua_eprm) {
+ err = uag_set_extra_params(ua_eprm);
+ if (err)
+ goto out;
+ }
+
+ if (test)
+ goto out;
+
+ /* Load modules */
+ err = conf_modules();
+ if (err)
+ goto out;
+
+ if (run_daemon) {
+ err = sys_daemon();
+ if (err)
+ goto out;
+
+ log_enable_stderr(false);
+ }
+
+ info("baresip is ready.\n");
+
+ /* Execute any commands from input arguments */
+ for (i=0; i<execmdc; i++) {
+ ui_input_str(execmdv[i]);
+ }
+
+ /* Main loop */
+ err = re_main(signal_handler);
+
+ out:
+ if (err)
+ ua_stop_all(true);
+
+ ua_close();
+ conf_close();
+
+ baresip_close();
+
+ /* NOTE: modules must be unloaded after all application
+ * activity has stopped.
+ */
+ debug("main: unloading modules..\n");
+ mod_close();
+
+ libre_close();
+
+ /* Check for memory leaks */
+ tmr_debug();
+ mem_debug();
+
+ return err;
+}
diff --git a/src/mctrl.c b/src/mctrl.c
new file mode 100644
index 0000000..d3abab8
--- /dev/null
+++ b/src/mctrl.c
@@ -0,0 +1,44 @@
+/**
+ * @file mctrl.c Media Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * RFC 5168 XML Schema for Media Control
+ * note: deprecated, use RTCP FIR instead
+ *
+ *
+ * Example XML Document:
+ *
+ * <pre>
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <media_control>
+ <vc_primitive>
+ <to_encoder>
+ <picture_fast_update>
+ </picture_fast_update>
+ </to_encoder>
+ </vc_primitive>
+ </media_control>
+
+ </pre>
+ */
+int mctrl_handle_media_control(struct pl *body, bool *pfu)
+{
+ if (!body)
+ return EINVAL;
+
+ /* XXX: Poor-mans XML parsing (use xml-parser instead) */
+ if (0 == re_regex(body->p, body->l, "picture_fast_update")) {
+ if (pfu)
+ *pfu = true;
+ }
+
+ return 0;
+}
diff --git a/src/menc.c b/src/menc.c
new file mode 100644
index 0000000..1e1c78f
--- /dev/null
+++ b/src/menc.c
@@ -0,0 +1,65 @@
+/**
+ * @file menc.c Media encryption
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register a new Media encryption module
+ *
+ * @param mencl List of Media-encryption modules
+ * @param menc Media encryption module
+ */
+void menc_register(struct list *mencl, struct menc *menc)
+{
+ if (!mencl || !menc)
+ return;
+
+ list_append(mencl, &menc->le, menc);
+
+ info("mediaenc: %s\n", menc->id);
+}
+
+
+/**
+ * Unregister a Media encryption module
+ *
+ * @param menc Media encryption module
+ */
+void menc_unregister(struct menc *menc)
+{
+ if (!menc)
+ return;
+
+ list_unlink(&menc->le);
+}
+
+
+/**
+ * Find a Media Encryption module by name
+ *
+ * @param mencl List of Media-encryption modules
+ * @param id Name of the Media Encryption module to find
+ *
+ * @return Matching Media Encryption module if found, otherwise NULL
+ */
+const struct menc *menc_find(const struct list *mencl, const char *id)
+{
+ struct le *le;
+
+ if (!mencl)
+ return NULL;
+
+ for (le = mencl->head; le; le = le->next) {
+ struct menc *me = le->data;
+
+ if (0 == str_casecmp(id, me->id))
+ return me;
+ }
+
+ return NULL;
+}
diff --git a/src/message.c b/src/message.c
new file mode 100644
index 0000000..e47f182
--- /dev/null
+++ b/src/message.c
@@ -0,0 +1,192 @@
+/**
+ * @file src/message.c SIP MESSAGE -- RFC 3428
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct message {
+ struct list lsnrl;
+ struct sip_lsnr *sip_lsnr;
+};
+
+struct message_lsnr {
+ struct le le;
+ message_recv_h *recvh;
+ void *arg;
+};
+
+
+static void destructor(void *data)
+{
+ struct message *message = data;
+
+ list_flush(&message->lsnrl);
+ mem_deref(message->sip_lsnr);
+}
+
+
+static void listener_destructor(void *data)
+{
+ struct message_lsnr *lsnr = data;
+
+ list_unlink(&lsnr->le);
+}
+
+
+static void handle_message(struct message_lsnr *lsnr, struct ua *ua,
+ const struct sip_msg *msg)
+{
+ static const char ctype_text[] = "text/plain";
+ struct pl ctype_pl = {ctype_text, sizeof(ctype_text)-1};
+ (void)ua;
+
+ if (msg_ctype_cmp(&msg->ctyp, "text", "plain") && lsnr->recvh) {
+
+ lsnr->recvh(&msg->from.auri, &ctype_pl,
+ msg->mb, lsnr->arg);
+
+ (void)sip_reply(uag_sip(), msg, 200, "OK");
+ }
+ else {
+ (void)sip_replyf(uag_sip(), msg, 415, "Unsupported Media Type",
+ "Accept: %s\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ ctype_text);
+ }
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct message *message = arg;
+ struct ua *ua;
+ struct le *le = message->lsnrl.head;
+ bool hdld = false;
+
+ if (pl_strcmp(&msg->met, "MESSAGE"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ while (le) {
+ struct message_lsnr *lsnr = le->data;
+
+ le = le->next;
+
+ handle_message(lsnr, ua, msg);
+
+ hdld = true;
+ }
+
+ return hdld;
+}
+
+
+int message_init(struct message **messagep)
+{
+ struct message *message;
+ int err = 0;
+
+ if (!messagep)
+ return EINVAL;
+
+ message = mem_zalloc(sizeof(*message), destructor);
+ if (!message)
+ return ENOMEM;
+
+ /* note: cannot create sip listener here, there is not UAs yet */
+
+ if (err)
+ mem_deref(message);
+ else
+ *messagep = message;
+
+ return err;
+}
+
+
+int message_listen(struct message_lsnr **lsnrp, struct message *message,
+ message_recv_h *recvh, void *arg)
+{
+ struct message_lsnr *lsnr;
+ int err = 0;
+
+ if (!message || !recvh)
+ return EINVAL;
+
+ /* create the SIP listener if it does not exist */
+ if (!message->sip_lsnr) {
+
+ err = sip_listen(&message->sip_lsnr, uag_sip(), true,
+ request_handler, message);
+ if (err)
+ goto out;
+ }
+
+ lsnr = mem_zalloc(sizeof(*lsnr), listener_destructor);
+
+ lsnr->recvh = recvh;
+ lsnr->arg = arg;
+
+ list_append(&message->lsnrl, &lsnr->le, lsnr);
+
+ if (lsnrp)
+ *lsnrp = lsnr;
+
+ out:
+ return err;
+}
+
+
+/**
+ * Send SIP instant MESSAGE to a peer
+ *
+ * @param ua User-Agent object
+ * @param peer Peer SIP Address
+ * @param msg Message to send
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int message_send(struct ua *ua, const char *peer, const char *msg,
+ sip_resp_h *resph, void *arg)
+{
+ struct sip_addr addr;
+ struct pl pl;
+ char *uri = NULL;
+ int err = 0;
+
+ if (!ua || !peer || !msg)
+ return EINVAL;
+
+ pl_set_str(&pl, peer);
+
+ err = sip_addr_decode(&addr, &pl);
+ if (err)
+ return err;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ return err;
+
+ err = sip_req_send(ua, "MESSAGE", uri, resph, arg,
+ "Accept: text/plain\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n%s",
+ str_len(msg), msg);
+
+ mem_deref(uri);
+
+ return err;
+}
diff --git a/src/metric.c b/src/metric.c
new file mode 100644
index 0000000..f3e8d06
--- /dev/null
+++ b/src/metric.c
@@ -0,0 +1,90 @@
+/**
+ * @file metric.c Metrics for media transmit/receive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {TMR_INTERVAL = 3};
+static void tmr_handler(void *arg)
+{
+ struct metric *metric = arg;
+ const uint64_t now = tmr_jiffies();
+ uint32_t diff;
+
+ tmr_start(&metric->tmr, TMR_INTERVAL * 1000, tmr_handler, metric);
+
+ if (!metric->started)
+ return;
+
+ if (now <= metric->ts_last)
+ return;
+
+ if (metric->ts_last) {
+ uint32_t bytes = metric->n_bytes - metric->n_bytes_last;
+ diff = (uint32_t)(now - metric->ts_last);
+ metric->cur_bitrate = 1000 * 8 * bytes / diff;
+ }
+
+ /* Update counters */
+ metric->ts_last = now;
+ metric->n_bytes_last = metric->n_bytes;
+}
+
+
+static void metric_start(struct metric *metric)
+{
+ if (metric->started)
+ return;
+
+ metric->ts_start = tmr_jiffies();
+
+ metric->started = true;
+}
+
+
+void metric_init(struct metric *metric)
+{
+ if (!metric)
+ return;
+
+ tmr_start(&metric->tmr, 100, tmr_handler, metric);
+}
+
+
+void metric_reset(struct metric *metric)
+{
+ if (!metric)
+ return;
+
+ tmr_cancel(&metric->tmr);
+}
+
+
+void metric_add_packet(struct metric *metric, size_t packetsize)
+{
+ if (!metric)
+ return;
+
+ if (!metric->started)
+ metric_start(metric);
+
+ metric->n_bytes += (uint32_t)packetsize;
+ metric->n_packets++;
+}
+
+
+uint32_t metric_avg_bitrate(const struct metric *metric)
+{
+ int diff;
+
+ if (!metric || !metric->ts_start)
+ return 0;
+
+ diff = (int)(tmr_jiffies() - metric->ts_start);
+
+ return 1000 * 8 * (metric->n_bytes / diff);
+}
diff --git a/src/mnat.c b/src/mnat.c
new file mode 100644
index 0000000..4349589
--- /dev/null
+++ b/src/mnat.c
@@ -0,0 +1,90 @@
+/**
+ * @file mnat.c Media NAT
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static void destructor(void *arg)
+{
+ struct mnat *mnat = arg;
+
+ list_unlink(&mnat->le);
+}
+
+
+/**
+ * Register a Media NAT traversal module
+ *
+ * @param mnatp Pointer to allocated Media NAT traversal module
+ * @param mnatl List of Media-NAT modules
+ * @param id Media NAT Identifier
+ * @param ftag SIP Feature tag (optional)
+ * @param sessh Session allocation handler
+ * @param mediah Media allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mnat_register(struct mnat **mnatp, struct list *mnatl,
+ const char *id, const char *ftag,
+ mnat_sess_h *sessh, mnat_media_h *mediah,
+ mnat_update_h *updateh)
+{
+ struct mnat *mnat;
+
+ if (!mnatp || !id || !sessh || !mediah)
+ return EINVAL;
+
+ mnat = mem_zalloc(sizeof(*mnat), destructor);
+ if (!mnat)
+ return ENOMEM;
+
+ list_append(mnatl, &mnat->le, mnat);
+
+ mnat->id = id;
+ mnat->ftag = ftag;
+ mnat->sessh = sessh;
+ mnat->mediah = mediah;
+ mnat->updateh = updateh;
+
+ info("medianat: %s\n", id);
+
+ *mnatp = mnat;
+
+ return 0;
+}
+
+
+/**
+ * Find a Media NAT module by name
+ *
+ * @param mnatl List of Media-NAT modules
+ * @param id Name of the Media NAT module to find
+ *
+ * @return Matching Media NAT module if found, otherwise NULL
+ */
+const struct mnat *mnat_find(const struct list *mnatl, const char *id)
+{
+ struct mnat *mnat;
+ struct le *le;
+
+ if (!mnatl)
+ return NULL;
+
+ for (le=mnatl->head; le; le=le->next) {
+
+ mnat = le->data;
+
+ if (str_casecmp(mnat->id, id))
+ continue;
+
+ return mnat;
+ }
+
+ return NULL;
+}
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 0000000..68d469e
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,258 @@
+/**
+ * @file src/module.c Module loading
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * Append module extension, if not exist
+ *
+ * input: foobar
+ * output: foobar.so
+ *
+ */
+static void append_extension(char *buf, size_t sz, const char *name)
+{
+ if (0 == re_regex(name, str_len(name), "[^.]+"MOD_EXT, NULL)) {
+
+ str_ncpy(buf, name, sz);
+ }
+ else {
+ re_snprintf(buf, sz, "%s"MOD_EXT, name);
+ }
+}
+
+
+#ifdef STATIC
+
+/* Declared in static.c */
+extern const struct mod_export *mod_table[];
+
+static const struct mod_export *lookup_static_module(const struct pl *pl)
+{
+ struct pl name;
+ uint32_t i;
+
+ if (re_regex(pl->p, pl->l, "[^.]+.[^]*", &name, NULL))
+ name = *pl;
+
+ for (i=0; ; i++) {
+ const struct mod_export *me = mod_table[i];
+ if (!me)
+ return NULL;
+ if (0 == pl_strcasecmp(&name, me->name))
+ return me;
+ }
+
+ return NULL;
+}
+#endif
+
+
+static int load_module(struct mod **modp, const struct pl *modpath,
+ const struct pl *name)
+{
+ char file[FS_PATH_MAX];
+ char namestr[256];
+ struct mod *m = NULL;
+ int err = 0;
+
+ if (!name)
+ return EINVAL;
+
+#ifdef STATIC
+ /* Try static first */
+ pl_strcpy(name, namestr, sizeof(namestr));
+
+ if (mod_find(namestr)) {
+ info("static module already loaded: %r\n", name);
+ return EALREADY;
+ }
+
+ err = mod_add(&m, lookup_static_module(name));
+ if (!err)
+ goto out;
+#else
+ (void)namestr;
+#endif
+
+ /* Then dynamic */
+ if (re_snprintf(file, sizeof(file), "%r/%r", modpath, name) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ err = mod_load(&m, file);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ warning("module %r: %m\n", name, err);
+ }
+ else if (modp)
+ *modp = m;
+
+ return err;
+}
+
+
+static int module_handler(const struct pl *val, void *arg)
+{
+ (void)load_module(NULL, arg, val);
+ return 0;
+}
+
+
+static int module_tmp_handler(const struct pl *val, void *arg)
+{
+ struct mod *mod = NULL;
+ (void)load_module(&mod, arg, val);
+ mem_deref(mod);
+ return 0;
+}
+
+
+static int module_app_handler(const struct pl *val, void *arg)
+{
+ struct mod *mod = NULL;
+ const struct mod_export *me;
+
+ debug("module: loading app %r\n", val);
+
+ if (load_module(&mod, arg, val)) {
+ return 0;
+ }
+
+ me = mod_export(mod);
+ if (0 != str_casecmp(me->type, "application")) {
+ warning("module_app %r should be type application (%s)\n",
+ val, me->type);
+ }
+
+ return 0;
+}
+
+
+int module_init(const struct conf *conf)
+{
+ struct pl path;
+ int err;
+
+ if (!conf)
+ return EINVAL;
+
+ if (conf_get(conf, "module_path", &path))
+ pl_set_str(&path, ".");
+
+ err = conf_apply(conf, "module", module_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_tmp", module_tmp_handler, &path);
+ if (err)
+ return err;
+
+ err = conf_apply(conf, "module_app", module_app_handler, &path);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void module_app_unload(void)
+{
+ struct le *le = list_tail(mod_list());
+
+ /* unload in reverse order */
+ while (le) {
+ struct mod *mod = le->data;
+ const struct mod_export *me = mod_export(mod);
+
+ le = le->prev;
+
+ if (me && 0 == str_casecmp(me->type, "application")) {
+ debug("module: unloading app %s\n", me->name);
+ mem_deref(mod);
+ }
+ }
+}
+
+
+int module_preload(const char *module)
+{
+ struct pl path, name;
+
+ if (!module)
+ return EINVAL;
+
+ pl_set_str(&path, ".");
+ pl_set_str(&name, module);
+
+ return load_module(NULL, &path, &name);
+}
+
+
+/**
+ * Load a module by name or by filename
+ *
+ * @param name Module name incl/excl extension, excluding module path
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * example: "foo"
+ * example: "foo.so"
+ */
+int module_load(const char *name)
+{
+ char filename[256];
+ struct pl path, pl_name;
+ int err;
+
+ if (!str_isset(name))
+ return EINVAL;
+
+ append_extension(filename, sizeof(filename), name);
+
+ pl_set_str(&pl_name, filename);
+
+ if (conf_get(conf_cur(), "module_path", &path))
+ pl_set_str(&path, ".");
+
+ err = load_module(NULL, &path, &pl_name);
+
+ return err;
+}
+
+
+/**
+ * Unload a module by name or by filename
+ *
+ * @param name module name incl/excl extension, excluding module path
+ *
+ * example: "foo"
+ * example: "foo.so"
+ */
+void module_unload(const char *name)
+{
+ char filename[256];
+ struct mod *mod;
+
+ if (!str_isset(name))
+ return;
+
+ append_extension(filename, sizeof(filename), name);
+
+ mod = mod_find(filename);
+ if (mod) {
+ info("unloading module: %s\n", filename);
+ mem_deref(mod);
+ return;
+ }
+
+ info("ERROR: Module %s is not currently loaded\n", name);
+}
diff --git a/src/mos.c b/src/mos.c
new file mode 100644
index 0000000..d86bbc1
--- /dev/null
+++ b/src/mos.c
@@ -0,0 +1,63 @@
+/**
+ * @file src/mos.c MOS (Mean Opinion Score)
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static double rfactor_to_mos(double r)
+{
+ double mos;
+
+ mos = 1 + (0.035) * (r) + (0.000007) * (r) * ((r) - 60) * (100 - (r));
+
+ if (mos > 5)
+ mos = 5;
+
+ return mos;
+}
+
+
+/**
+ * Calculate Pseudo-MOS (Mean Opinion Score)
+ *
+ * @param r_factor Pointer to where R-factor is written (optional)
+ * @param rtt Average roundtrip time
+ * @param jitter Jitter
+ * @param num_packets_lost Number of packets lost
+ *
+ * @return The calculated MOS value from 1 to 5
+ *
+ * Reference: https://metacpan.org/pod/Algorithm::MOS
+ */
+double mos_calculate(double *r_factor, double rtt,
+ double jitter, uint32_t num_packets_lost)
+{
+ double effective_latency = rtt + (jitter * 2) + 10;
+ double mos_val;
+ double r;
+
+ if (effective_latency < 160) {
+ r = 93.2 - (effective_latency / 40);
+ }
+ else {
+ r = 93.2 - (effective_latency - 120) / 10;
+ }
+
+ r = r - (num_packets_lost * 2.5);
+
+ if (r > 100)
+ r = 100;
+ else if (r < 0)
+ r = 0;
+
+ mos_val = rfactor_to_mos(r);
+
+ if (r_factor)
+ *r_factor = r;
+
+ return mos_val;
+}
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..dff8444
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,561 @@
+/**
+ * @file src/net.c Networking code
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+struct network {
+ struct config_net cfg;
+ struct sa laddr;
+ char ifname[16];
+#ifdef HAVE_INET6
+ struct sa laddr6;
+ char ifname6[16];
+#endif
+ struct tmr tmr;
+ struct dnsc *dnsc;
+ struct sa nsv[NET_MAX_NS];/**< Configured name servers */
+ uint32_t nsn; /**< Number of configured name servers */
+ uint32_t interval;
+ int af; /**< Preferred address family */
+ char domain[64]; /**< DNS domain from network */
+ net_change_h *ch;
+ void *arg;
+};
+
+
+static int net_dnssrv_add(struct network *net, const struct sa *sa)
+{
+ if (!net)
+ return EINVAL;
+
+ if (net->nsn >= ARRAY_SIZE(net->nsv))
+ return E2BIG;
+
+ sa_cpy(&net->nsv[net->nsn++], sa);
+
+ return 0;
+}
+
+
+static int net_dns_srv_get(const struct network *net,
+ struct sa *srvv, uint32_t *n, bool *from_sys)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t i, nsn = ARRAY_SIZE(nsv);
+ int err;
+
+ err = dns_srv_get(NULL, 0, nsv, &nsn);
+ if (err) {
+ nsn = 0;
+ }
+
+ if (net->nsn) {
+
+ if (net->nsn > *n)
+ return E2BIG;
+
+ /* Use any configured nameservers */
+ for (i=0; i<net->nsn; i++) {
+ srvv[i] = net->nsv[i];
+ }
+
+ *n = net->nsn;
+
+ if (from_sys)
+ *from_sys = false;
+ }
+ else {
+ if (nsn > *n)
+ return E2BIG;
+
+ for (i=0; i<nsn; i++)
+ srvv[i] = nsv[i];
+
+ *n = nsn;
+
+ if (from_sys)
+ *from_sys = true;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Check for DNS Server updates
+ */
+static void dns_refresh(struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn;
+ int err;
+
+ nsn = ARRAY_SIZE(nsv);
+
+ err = net_dns_srv_get(net, nsv, &nsn, NULL);
+ if (err)
+ return;
+
+ (void)dnsc_srv_set(net->dnsc, nsv, nsn);
+}
+
+
+/**
+ * Detect changes in IP address(es)
+ */
+static void ipchange_handler(void *arg)
+{
+ struct network *net = arg;
+ bool change;
+
+ tmr_start(&net->tmr, net->interval * 1000, ipchange_handler, net);
+
+ dns_refresh(net);
+
+ change = net_check(net);
+ if (change && net->ch) {
+ net->ch(net->arg);
+ }
+}
+
+
+/**
+ * Check if local IP address(es) changed
+ *
+ * @param net Network instance
+ *
+ * @return True if changed, otherwise false
+ */
+bool net_check(struct network *net)
+{
+ struct sa laddr = net->laddr;
+#ifdef HAVE_INET6
+ struct sa laddr6 = net->laddr6;
+#endif
+ bool change = false;
+
+ if (!net)
+ return false;
+
+ if (str_isset(net->cfg.ifname)) {
+
+ (void)net_if_getaddr(net->cfg.ifname, AF_INET, &net->laddr);
+
+#ifdef HAVE_INET6
+ (void)net_if_getaddr(net->cfg.ifname, AF_INET6, &net->laddr6);
+#endif
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net->laddr);
+ (void)net_rt_default_get(AF_INET, net->ifname,
+ sizeof(net->ifname));
+
+#ifdef HAVE_INET6
+ (void)net_default_source_addr_get(AF_INET6, &net->laddr6);
+ (void)net_rt_default_get(AF_INET6, net->ifname6,
+ sizeof(net->ifname6));
+#endif
+ }
+
+ if (sa_isset(&net->laddr, SA_ADDR) &&
+ !sa_cmp(&laddr, &net->laddr, SA_ADDR)) {
+ change = true;
+ info("net: local IPv4 address changed: %j -> %j\n",
+ &laddr, &net->laddr);
+ }
+
+#ifdef HAVE_INET6
+ if (sa_isset(&net->laddr6, SA_ADDR) &&
+ !sa_cmp(&laddr6, &net->laddr6, SA_ADDR)) {
+ change = true;
+ info("net: local IPv6 address changed: %j -> %j\n",
+ &laddr6, &net->laddr6);
+ }
+#endif
+
+ return change;
+}
+
+
+static int dns_init(struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn = ARRAY_SIZE(nsv);
+ int err;
+
+ err = net_dns_srv_get(net, nsv, &nsn, NULL);
+ if (err)
+ return err;
+
+ return dnsc_alloc(&net->dnsc, NULL, nsv, nsn);
+}
+
+
+/**
+ * Return TRUE if libre supports IPv6
+ */
+static bool check_ipv6(void)
+{
+ struct sa sa;
+
+ return 0 == sa_set_str(&sa, "::1", 2000);
+}
+
+
+static void net_destructor(void *data)
+{
+ struct network *net = data;
+
+ tmr_cancel(&net->tmr);
+ mem_deref(net->dnsc);
+}
+
+
+/**
+ * Initialise networking
+ *
+ * @param netp Pointer to allocated network instance
+ * @param cfg Network configuration
+ * @param af Preferred address family
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_alloc(struct network **netp, const struct config_net *cfg, int af)
+{
+ struct network *net;
+ struct sa nsv[NET_MAX_NS];
+ uint32_t nsn = ARRAY_SIZE(nsv);
+ char buf4[128] = "", buf6[128] = "";
+ int err;
+
+ if (!netp || !cfg)
+ return EINVAL;
+
+ /*
+ * baresip/libre must be built with matching HAVE_INET6 value.
+ * if different the size of `struct sa' will not match and the
+ * application is very likely to crash.
+ */
+#ifdef HAVE_INET6
+ if (!check_ipv6()) {
+ error_msg("libre was compiled without IPv6-support"
+ ", but baresip was compiled with\n");
+ return EAFNOSUPPORT;
+ }
+#else
+ if (check_ipv6()) {
+ error_msg("libre was compiled with IPv6-support"
+ ", but baresip was compiled without\n");
+ return EAFNOSUPPORT;
+ }
+#endif
+
+ net = mem_zalloc(sizeof(*net), net_destructor);
+ if (!net)
+ return ENOMEM;
+
+ net->cfg = *cfg;
+ net->af = af;
+
+ tmr_init(&net->tmr);
+
+ if (cfg->nsc) {
+ size_t i;
+
+ for (i=0; i<cfg->nsc; i++) {
+
+ const char *ns = cfg->nsv[i].addr;
+ struct sa sa;
+
+ err = sa_decode(&sa, ns, str_len(ns));
+ if (err) {
+ warning("net: dns_server:"
+ " could not decode `%s' (%m)\n",
+ ns, err);
+ goto out;
+ }
+
+ err = net_dnssrv_add(net, &sa);
+ if (err) {
+ warning("net: failed to add nameserver: %m\n",
+ err);
+ goto out;
+ }
+ }
+ }
+
+ /* Initialise DNS resolver */
+ err = dns_init(net);
+ if (err) {
+ warning("net: dns_init: %m\n", err);
+ goto out;
+ }
+
+ sa_init(&net->laddr, AF_INET);
+ (void)sa_set_str(&net->laddr, "127.0.0.1", 0);
+
+ if (str_isset(cfg->ifname)) {
+
+ bool got_it = false;
+
+ info("Binding to interface '%s'\n", cfg->ifname);
+
+ str_ncpy(net->ifname, cfg->ifname, sizeof(net->ifname));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET, &net->laddr);
+ if (err) {
+ info("net: %s: could not get IPv4 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+
+#ifdef HAVE_INET6
+ str_ncpy(net->ifname6, cfg->ifname,
+ sizeof(net->ifname6));
+
+ err = net_if_getaddr(cfg->ifname,
+ AF_INET6, &net->laddr6);
+ if (err) {
+ info("net: %s: could not get IPv6 address (%m)\n",
+ cfg->ifname, err);
+ }
+ else
+ got_it = true;
+#endif
+ if (got_it)
+ err = 0;
+ else {
+ warning("net: %s: could not get network address\n",
+ cfg->ifname);
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+ }
+ else {
+ (void)net_default_source_addr_get(AF_INET, &net->laddr);
+ (void)net_rt_default_get(AF_INET, net->ifname,
+ sizeof(net->ifname));
+
+#ifdef HAVE_INET6
+ sa_init(&net->laddr6, AF_INET6);
+
+ (void)net_default_source_addr_get(AF_INET6, &net->laddr6);
+ (void)net_rt_default_get(AF_INET6, net->ifname6,
+ sizeof(net->ifname6));
+#endif
+ }
+
+ if (sa_isset(&net->laddr, SA_ADDR)) {
+ re_snprintf(buf4, sizeof(buf4), " IPv4=%s:%j",
+ net->ifname, &net->laddr);
+ }
+#ifdef HAVE_INET6
+ if (sa_isset(&net->laddr6, SA_ADDR)) {
+ re_snprintf(buf6, sizeof(buf6), " IPv6=%s:%j",
+ net->ifname6, &net->laddr6);
+ }
+#endif
+
+ (void)dns_srv_get(net->domain, sizeof(net->domain), nsv, &nsn);
+
+ info("Local network address: %s %s\n",
+ buf4, buf6);
+
+ out:
+ if (err)
+ mem_deref(net);
+ else
+ *netp = net;
+
+ return err;
+}
+
+
+/**
+ * Use a specific DNS server
+ *
+ * @param net Network instance
+ * @param ns DNS Server IP address and port
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_use_nameserver(struct network *net, const struct sa *ns)
+{
+ struct dnsc *dnsc;
+ int err;
+
+ if (!net || !ns)
+ return EINVAL;
+
+ err = dnsc_alloc(&dnsc, NULL, ns, 1);
+ if (err)
+ return err;
+
+ mem_deref(net->dnsc);
+ net->dnsc = dnsc;
+
+ return 0;
+}
+
+
+/**
+ * Check for networking changes with a regular interval
+ *
+ * @param net Network instance
+ * @param interval Interval in seconds
+ * @param ch Handler called when a change was detected
+ * @param arg Handler argument
+ */
+void net_change(struct network *net, uint32_t interval,
+ net_change_h *ch, void *arg)
+{
+ if (!net)
+ return;
+
+ net->interval = interval;
+ net->ch = ch;
+ net->arg = arg;
+
+ if (interval)
+ tmr_start(&net->tmr, interval * 1000, ipchange_handler, net);
+ else
+ tmr_cancel(&net->tmr);
+}
+
+
+void net_force_change(struct network *net)
+{
+ if (net && net->ch) {
+ net->ch(net->arg);
+ }
+}
+
+
+static int dns_debug(struct re_printf *pf, const struct network *net)
+{
+ struct sa nsv[NET_MAX_NS];
+ uint32_t i, nsn = ARRAY_SIZE(nsv);
+ bool from_sys = false;
+ int err;
+
+ if (!net)
+ return 0;
+
+ err = net_dns_srv_get(net, nsv, &nsn, &from_sys);
+ if (err)
+ nsn = 0;
+
+ err = re_hprintf(pf, " DNS Servers from %s: (%u)\n",
+ from_sys ? "System" : "Config", nsn);
+ for (i=0; i<nsn; i++)
+ err |= re_hprintf(pf, " %u: %J\n", i, &nsv[i]);
+
+ return err;
+}
+
+
+int net_af(const struct network *net)
+{
+ if (!net)
+ return AF_UNSPEC;
+
+ return net->af;
+}
+
+
+/**
+ * Print networking debug information
+ *
+ * @param pf Print handler for debug output
+ * @param net Network instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_debug(struct re_printf *pf, const struct network *net)
+{
+ int err;
+
+ if (!net)
+ return 0;
+
+ err = re_hprintf(pf, "--- Network debug ---\n");
+ err |= re_hprintf(pf, " Preferred AF: %s\n", net_af2name(net->af));
+ err |= re_hprintf(pf, " Local IPv4: %9s - %j\n",
+ net->ifname, &net->laddr);
+#ifdef HAVE_INET6
+ err |= re_hprintf(pf, " Local IPv6: %9s - %j\n",
+ net->ifname6, &net->laddr6);
+#endif
+ err |= re_hprintf(pf, " Domain: %s\n", net->domain);
+
+ err |= net_if_debug(pf, NULL);
+
+ err |= net_rt_debug(pf, NULL);
+
+ err |= dns_debug(pf, net);
+
+ return err;
+}
+
+
+/**
+ * Get the local IP Address for a specific Address Family (AF)
+ *
+ * @param net Network instance
+ * @param af Address Family
+ *
+ * @return Local IP Address
+ */
+const struct sa *net_laddr_af(const struct network *net, int af)
+{
+ if (!net)
+ return NULL;
+
+ switch (af) {
+
+ case AF_INET: return &net->laddr;
+#ifdef HAVE_INET6
+ case AF_INET6: return &net->laddr6;
+#endif
+ default: return NULL;
+ }
+}
+
+
+/**
+ * Get the DNS Client
+ *
+ * @param net Network instance
+ *
+ * @return DNS Client
+ */
+struct dnsc *net_dnsc(const struct network *net)
+{
+ if (!net)
+ return NULL;
+
+ return net->dnsc;
+}
+
+
+/**
+ * Get the network domain name
+ *
+ * @param net Network instance
+ *
+ * @return Network domain
+ */
+const char *net_domain(const struct network *net)
+{
+ if (!net)
+ return NULL;
+
+ return net->domain[0] ? net->domain : NULL;
+}
diff --git a/src/play.c b/src/play.c
new file mode 100644
index 0000000..aa0b59f
--- /dev/null
+++ b/src/play.c
@@ -0,0 +1,352 @@
+/**
+ * @file src/play.c Audio-file player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {PTIME = 40};
+
+/** Audio file player */
+struct play {
+ struct le le;
+ struct play **playp;
+ struct lock *lock;
+ struct mbuf *mb;
+ struct auplay_st *auplay;
+ struct tmr tmr;
+ int repeat;
+ bool eof;
+};
+
+
+#ifndef PREFIX
+#define PREFIX "/usr"
+#endif
+static const char default_play_path[FS_PATH_MAX] = PREFIX "/share/baresip";
+
+
+struct player {
+ struct list playl;
+ char play_path[FS_PATH_MAX];
+};
+
+
+static void tmr_polling(void *arg);
+
+
+static void tmr_stop(void *arg)
+{
+ struct play *play = arg;
+ debug("play: player complete.\n");
+ mem_deref(play);
+}
+
+
+static void tmr_polling(void *arg)
+{
+ struct play *play = arg;
+
+ lock_write_get(play->lock);
+
+ tmr_start(&play->tmr, 1000, tmr_polling, arg);
+
+ if (play->eof) {
+ if (play->repeat == 0)
+ tmr_start(&play->tmr, 1, tmr_stop, arg);
+ }
+
+ lock_rel(play->lock);
+}
+
+
+/**
+ * NOTE: DSP cannot be destroyed inside handler
+ */
+static void write_handler(void *sampv, size_t sampc, void *arg)
+{
+ struct play *play = arg;
+ size_t sz = sampc * 2;
+ size_t pos = 0;
+ size_t left;
+ size_t count;
+
+ lock_write_get(play->lock);
+
+ if (play->eof)
+ goto silence;
+
+ while (pos < sz) {
+ left = mbuf_get_left(play->mb);
+ count = (left > sz - pos) ? sz - pos : left;
+
+ (void)mbuf_read_mem(play->mb, (uint8_t *)sampv + pos, count);
+
+ pos += count;
+
+ if (pos < sz) {
+ if (play->repeat > 0)
+ play->repeat--;
+
+ if (play->repeat == 0) {
+ play->eof = true;
+ goto silence;
+ }
+
+ play->mb->pos = 0;
+ }
+ }
+
+ silence:
+ if (play->eof)
+ memset((uint8_t *)sampv + pos, 0, sz - pos);
+
+ lock_rel(play->lock);
+}
+
+
+static void destructor(void *arg)
+{
+ struct play *play = arg;
+
+ list_unlink(&play->le);
+ tmr_cancel(&play->tmr);
+
+ lock_write_get(play->lock);
+ play->eof = true;
+ lock_rel(play->lock);
+
+ mem_deref(play->auplay);
+ mem_deref(play->mb);
+ mem_deref(play->lock);
+
+ if (play->playp)
+ *play->playp = NULL;
+}
+
+
+static int aufile_load(struct mbuf *mb, const char *filename,
+ uint32_t *srate, uint8_t *channels)
+{
+ struct aufile_prm prm;
+ struct aufile *af;
+ int err;
+
+ err = aufile_open(&af, &prm, filename, AUFILE_READ);
+ if (err)
+ return err;
+
+ while (!err) {
+ uint8_t buf[4096];
+ size_t i, n;
+ int16_t *p = (void *)buf;
+
+ n = sizeof(buf);
+
+ err = aufile_read(af, buf, &n);
+ if (err || !n)
+ break;
+
+ switch (prm.fmt) {
+
+ case AUFMT_S16LE:
+ /* convert from Little-Endian to Native-Endian */
+ for (i=0; i<n/2; i++) {
+ int16_t s = sys_ltohs(*p++);
+ err |= mbuf_write_u16(mb, s);
+ }
+
+ break;
+
+ case AUFMT_PCMA:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_alaw2pcm(buf[i]));
+ }
+ break;
+
+ case AUFMT_PCMU:
+ for (i=0; i<n; i++) {
+ err |= mbuf_write_u16(mb,
+ g711_ulaw2pcm(buf[i]));
+ }
+ break;
+
+ default:
+ err = ENOSYS;
+ break;
+ }
+ }
+
+ mem_deref(af);
+
+ if (!err) {
+ mb->pos = 0;
+
+ *srate = prm.srate;
+ *channels = prm.channels;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play a tone from a PCM buffer
+ *
+ * @param playp Pointer to allocated player object
+ * @param player Audio-file player
+ * @param tone PCM buffer to play
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_tone(struct play **playp, struct player *player,
+ struct mbuf *tone, uint32_t srate,
+ uint8_t ch, int repeat)
+{
+ struct auplay_prm wprm;
+ struct play *play;
+ struct config *cfg;
+ int err;
+
+ if (!player)
+ return EINVAL;
+ if (playp && *playp)
+ return EALREADY;
+
+ cfg = conf_config();
+ if (!cfg)
+ return ENOENT;
+
+ play = mem_zalloc(sizeof(*play), destructor);
+ if (!play)
+ return ENOMEM;
+
+ tmr_init(&play->tmr);
+ play->repeat = repeat;
+ play->mb = mem_ref(tone);
+
+ err = lock_alloc(&play->lock);
+ if (err)
+ goto out;
+
+ wprm.ch = ch;
+ wprm.srate = srate;
+ wprm.ptime = PTIME;
+ wprm.fmt = AUFMT_S16LE;
+
+ err = auplay_alloc(&play->auplay, baresip_auplayl(),
+ cfg->audio.alert_mod, &wprm,
+ cfg->audio.alert_dev, write_handler, play);
+ if (err)
+ goto out;
+
+ list_append(&player->playl, &play->le, play);
+ tmr_start(&play->tmr, 1000, tmr_polling, play);
+
+ out:
+ if (err) {
+ mem_deref(play);
+ }
+ else if (playp) {
+ play->playp = playp;
+ *playp = play;
+ }
+
+ return err;
+}
+
+
+/**
+ * Play an audio file in WAV format
+ *
+ * @param playp Pointer to allocated player object
+ * @param player Audio-file player
+ * @param filename Name of WAV file to play
+ * @param repeat Number of times to repeat
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int play_file(struct play **playp, struct player *player,
+ const char *filename, int repeat)
+{
+ struct mbuf *mb;
+ char path[FS_PATH_MAX];
+ uint32_t srate = 0;
+ uint8_t ch = 0;
+ int err;
+
+ if (!player)
+ return EINVAL;
+ if (playp && *playp)
+ return EALREADY;
+
+ if (re_snprintf(path, sizeof(path), "%s/%s",
+ player->play_path, filename) < 0)
+ return ENOMEM;
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ err = aufile_load(mb, path, &srate, &ch);
+ if (err) {
+ warning("play: %s: %m\n", path, err);
+ goto out;
+ }
+
+ err = play_tone(playp, player, mb, srate, ch, repeat);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static void player_destructor(void *data)
+{
+ struct player *player = data;
+
+ list_flush(&player->playl);
+}
+
+
+int play_init(struct player **playerp)
+{
+ struct player *player;
+
+ if (!playerp)
+ return EINVAL;
+
+ player = mem_zalloc(sizeof(*player), player_destructor);
+ if (!player)
+ return ENOMEM;
+
+ list_init(&player->playl);
+
+ str_ncpy(player->play_path, default_play_path,
+ sizeof(player->play_path));
+
+ *playerp = player;
+
+ return 0;
+}
+
+
+void play_set_path(struct player *player, const char *path)
+{
+ if (!player)
+ return;
+
+ str_ncpy(player->play_path, path, sizeof(player->play_path));
+}
diff --git a/src/realtime.c b/src/realtime.c
new file mode 100644
index 0000000..a144e22
--- /dev/null
+++ b/src/realtime.c
@@ -0,0 +1,100 @@
+/**
+ * @file realtime.c Real-Time scheduling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#ifdef DARWIN
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <stdio.h>
+#include <mach/mach.h>
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#endif
+
+
+#ifdef DARWIN
+static int set_realtime(int period, int computation, int constraint)
+{
+ struct thread_time_constraint_policy ttcpolicy;
+ int ret;
+
+ ttcpolicy.period = period; /* HZ/160 */
+ ttcpolicy.computation = computation; /* HZ/3300 */
+ ttcpolicy.constraint = constraint; /* HZ/2200 */
+ ttcpolicy.preemptible = 1;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_TIME_CONSTRAINT_POLICY,
+ (thread_policy_t)&ttcpolicy,
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+ if (ret != KERN_SUCCESS)
+ return ENOSYS;
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Enable real-time scheduling (for selected platforms)
+ *
+ * @param enable True to enable, false to disable
+ * @param fps Wanted video framerate
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int realtime_enable(bool enable, int fps)
+{
+#ifdef DARWIN
+ if (enable) {
+#if TARGET_OS_IPHONE
+ int bus_speed = 100000000;
+#else
+ int ret, bus_speed;
+ int mib[2] = { CTL_HW, HW_BUS_FREQ };
+ size_t len;
+
+ len = sizeof(bus_speed);
+ ret = sysctl (mib, 2, &bus_speed, &len, NULL, 0);
+ if (ret < 0) {
+ return ENOSYS;
+ }
+
+ info("realtime: fps=%d bus_speed=%d\n", fps, bus_speed);
+#endif
+
+ return set_realtime(bus_speed / fps,
+ bus_speed / 3300, bus_speed / 2200);
+ }
+ else {
+ kern_return_t ret;
+ thread_standard_policy_data_t pt;
+ mach_msg_type_number_t cnt = THREAD_STANDARD_POLICY_COUNT;
+ boolean_t get_default = TRUE;
+
+ ret = thread_policy_get(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ &cnt, &get_default);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ ret = thread_policy_set(mach_thread_self(),
+ THREAD_STANDARD_POLICY,
+ (thread_policy_t)&pt,
+ THREAD_STANDARD_POLICY_COUNT);
+ if (KERN_SUCCESS != ret)
+ return ENOSYS;
+
+ return 0;
+ }
+#else
+ (void)enable;
+ (void)fps;
+ return ENOSYS;
+#endif
+}
diff --git a/src/reg.c b/src/reg.c
new file mode 100644
index 0000000..23bb337
--- /dev/null
+++ b/src/reg.c
@@ -0,0 +1,265 @@
+/**
+ * @file reg.c Register Client
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Register client */
+struct reg {
+ struct le le; /**< Linked list element */
+ struct ua *ua; /**< Pointer to parent UA object */
+ struct sipreg *sipreg; /**< SIP Register client */
+ int id; /**< Registration ID (for SIP outbound) */
+
+ /* status: */
+ uint16_t scode; /**< Registration status code */
+ char *srv; /**< SIP Server id */
+ int af; /**< Cached address family for SIP conn */
+};
+
+
+static void destructor(void *arg)
+{
+ struct reg *reg = arg;
+
+ list_unlink(&reg->le);
+ mem_deref(reg->sipreg);
+ mem_deref(reg->srv);
+}
+
+
+static int sipmsg_af(const struct sip_msg *msg)
+{
+ struct sa laddr;
+ int err = 0;
+
+ if (!msg)
+ return AF_UNSPEC;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ err = udp_local_get(msg->sock, &laddr);
+ break;
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ err = tcp_conn_local_get(sip_msg_tcpconn(msg), &laddr);
+ break;
+
+ default:
+ return AF_UNSPEC;
+ }
+
+ return err ? AF_UNSPEC : sa_af(&laddr);
+}
+
+
+static const char *af_name(int af)
+{
+ switch (af) {
+
+ case AF_INET: return "v4";
+ case AF_INET6: return "v6";
+ default: return "v?";
+ }
+}
+
+
+static int sip_auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+ return account_auth(acc, username, password, realm);
+}
+
+
+static bool contact_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct reg *reg = arg;
+ struct sip_addr addr;
+ (void)msg;
+
+ if (sip_addr_decode(&addr, &hdr->val))
+ return false;
+
+ /* match our contact */
+ return 0 == pl_strcasecmp(&addr.uri.user, ua_local_cuser(reg->ua));
+}
+
+
+static void register_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct reg *reg = arg;
+ const struct sip_hdr *hdr;
+
+ if (err) {
+ warning("reg: %s: Register: %m\n", ua_aor(reg->ua), err);
+
+ reg->scode = 999;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%m", err);
+ return;
+ }
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_SERVER);
+ if (hdr) {
+ reg->srv = mem_deref(reg->srv);
+ (void)pl_strdup(&reg->srv, &hdr->val);
+ }
+
+ if (200 <= msg->scode && msg->scode <= 299) {
+
+ uint32_t n_bindings;
+
+ n_bindings = sip_msg_hdr_count(msg, SIP_HDR_CONTACT);
+ reg->af = sipmsg_af(msg);
+
+ if (msg->scode != reg->scode) {
+ ua_printf(reg->ua, "{%d/%s/%s} %u %r (%s)"
+ " [%u binding%s]\n",
+ reg->id, sip_transp_name(msg->tp),
+ af_name(reg->af), msg->scode, &msg->reason,
+ reg->srv, n_bindings,
+ 1==n_bindings?"":"s");
+ }
+
+ reg->scode = msg->scode;
+
+ hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT,
+ contact_handler, reg);
+ if (hdr) {
+ struct sip_addr addr;
+ struct pl pval;
+
+ if (0 == sip_addr_decode(&addr, &hdr->val) &&
+ 0 == msg_param_decode(&addr.params, "pub-gruu",
+ &pval)) {
+ ua_pub_gruu_set(reg->ua, &pval);
+ }
+ }
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_OK, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+ else if (msg->scode >= 300) {
+
+ warning("reg: %s: %u %r (%s)\n", ua_aor(reg->ua),
+ msg->scode, &msg->reason, reg->srv);
+
+ reg->scode = msg->scode;
+
+ ua_event(reg->ua, UA_EVENT_REGISTER_FAIL, NULL, "%u %r",
+ msg->scode, &msg->reason);
+ }
+}
+
+
+int reg_add(struct list *lst, struct ua *ua, int regid)
+{
+ struct reg *reg;
+
+ if (!lst || !ua)
+ return EINVAL;
+
+ reg = mem_zalloc(sizeof(*reg), destructor);
+ if (!reg)
+ return ENOMEM;
+
+ reg->ua = ua;
+ reg->id = regid;
+
+ list_append(lst, &reg->le, reg);
+
+ return 0;
+}
+
+
+int reg_register(struct reg *reg, const char *reg_uri, const char *params,
+ uint32_t regint, const char *outbound)
+{
+ const char *routev[1];
+ int err;
+
+ if (!reg || !reg_uri)
+ return EINVAL;
+
+ reg->scode = 0;
+ routev[0] = outbound;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+ err = sipreg_register(&reg->sipreg, uag_sip(), reg_uri,
+ ua_aor(reg->ua), ua_aor(reg->ua),
+ regint, ua_local_cuser(reg->ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0,
+ reg->id,
+ sip_auth_handler, ua_account(reg->ua), true,
+ register_handler, reg,
+ params[0] ? &params[1] : NULL,
+ "Allow: %s\r\n", uag_allowed_methods());
+ if (err)
+ return err;
+
+ return 0;
+}
+
+
+void reg_unregister(struct reg *reg)
+{
+ if (!reg)
+ return;
+
+ reg->scode = 0;
+ reg->af = 0;
+
+ reg->sipreg = mem_deref(reg->sipreg);
+}
+
+
+bool reg_isok(const struct reg *reg)
+{
+ if (!reg)
+ return false;
+
+ return 200 <= reg->scode && reg->scode <= 299;
+}
+
+
+static const char *print_scode(uint16_t scode)
+{
+ if (0 == scode) return "\x1b[33m" "zzz" "\x1b[;m";
+ else if (200 == scode) return "\x1b[32m" "OK " "\x1b[;m";
+ else return "\x1b[31m" "ERR" "\x1b[;m";
+}
+
+
+int reg_debug(struct re_printf *pf, const struct reg *reg)
+{
+ int err = 0;
+
+ if (!reg)
+ return 0;
+
+ err |= re_hprintf(pf, "\nRegister client:\n");
+ err |= re_hprintf(pf, " id: %d\n", reg->id);
+ err |= re_hprintf(pf, " scode: %u (%s)\n",
+ reg->scode, print_scode(reg->scode));
+ err |= re_hprintf(pf, " srv: %s\n", reg->srv);
+
+ return err;
+}
+
+
+int reg_status(struct re_printf *pf, const struct reg *reg)
+{
+ if (!reg)
+ return 0;
+
+ return re_hprintf(pf, " %s %s", print_scode(reg->scode), reg->srv);
+}
diff --git a/src/rtpext.c b/src/rtpext.c
new file mode 100644
index 0000000..82a6f6e
--- /dev/null
+++ b/src/rtpext.c
@@ -0,0 +1,112 @@
+/**
+ * @file rtpext.c RTP Header Extensions
+ *
+ * Copyright (C) 2017 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * RFC 5285 A General Mechanism for RTP Header Extensions
+ *
+ * - One-Byte Header: Supported
+ * - Two-Byte Header: Not supported
+ */
+
+
+int rtpext_hdr_encode(struct mbuf *mb, size_t num_bytes)
+{
+ int err = 0;
+
+ if (!mb || !num_bytes)
+ return EINVAL;
+
+ if (num_bytes & 0x3) {
+ warning("rtpext: hdr_encode: num_bytes (%zu) must be multiple"
+ " of 4\n", num_bytes);
+ return EINVAL;
+ }
+
+ err |= mbuf_write_u16(mb, htons(RTPEXT_TYPE_MAGIC));
+ err |= mbuf_write_u16(mb, htons(num_bytes / 4));
+
+ return err;
+}
+
+
+int rtpext_encode(struct mbuf *mb, unsigned id, unsigned len,
+ const uint8_t *data)
+{
+ size_t start;
+ int err;
+
+ if (!mb || !data)
+ return EINVAL;
+
+ if (id < RTPEXT_ID_MIN || id > RTPEXT_ID_MAX)
+ return EINVAL;
+ if (len < RTPEXT_LEN_MIN || len > RTPEXT_LEN_MAX)
+ return EINVAL;
+
+ start = mb->pos;
+
+ err = mbuf_write_u8(mb, id << 4 | (len-1));
+ err |= mbuf_write_mem(mb, data, len);
+ if (err)
+ return err;
+
+ /* padding */
+ while ((mb->pos - start) & 0x03)
+ err |= mbuf_write_u8(mb, 0x00);
+
+ return err;
+}
+
+
+int rtpext_decode(struct rtpext *ext, struct mbuf *mb)
+{
+ uint8_t v;
+ int err;
+
+ if (!ext || !mb)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ memset(ext, 0, sizeof(*ext));
+
+ v = mbuf_read_u8(mb);
+
+ ext->id = v >> 4;
+ ext->len = (v & 0x0f) + 1;
+
+ if (ext->id < RTPEXT_ID_MIN || ext->id > RTPEXT_ID_MAX) {
+ warning("rtpext: invalid ID %u\n", ext->id);
+ return EBADMSG;
+ }
+ if (ext->len > mbuf_get_left(mb)) {
+ warning("rtpext: short read\n");
+ return ENODATA;
+ }
+
+ err = mbuf_read_mem(mb, ext->data, ext->len);
+ if (err)
+ return err;
+
+ /* skip padding */
+ while (mbuf_get_left(mb)) {
+ uint8_t pad = mbuf_buf(mb)[0];
+
+ if (pad != 0x00)
+ break;
+
+ mbuf_advance(mb, 1);
+ }
+
+ return 0;
+}
diff --git a/src/rtpkeep.c b/src/rtpkeep.c
new file mode 100644
index 0000000..6b6cf81
--- /dev/null
+++ b/src/rtpkeep.c
@@ -0,0 +1,165 @@
+/**
+ * @file rtpkeep.c RTP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/*
+ * See draft-ietf-avt-app-rtp-keepalive:
+ *
+ * "zero" 4.1. Transport Packet of 0-byte
+ * "rtcp" 4.3. RTCP Packets Multiplexed with RTP Packets
+ * "stun" 4.4. STUN Indication Packet
+ * "dyna" 4.6. RTP Packet with Unknown Payload Type
+ */
+
+
+enum {
+ Tr_UDP = 15,
+ Tr_TCP = 7200
+};
+
+/** RTP Keepalive */
+struct rtpkeep {
+ struct rtp_sock *rtp;
+ struct sdp_media *sdp;
+ struct tmr tmr;
+ char *method;
+ uint32_t ts;
+ bool flag;
+};
+
+
+static void destructor(void *arg)
+{
+ struct rtpkeep *rk = arg;
+
+ tmr_cancel(&rk->tmr);
+ mem_deref(rk->method);
+}
+
+
+static int send_keepalive(struct rtpkeep *rk)
+{
+ int err = 0;
+
+ if (!str_casecmp(rk->method, "zero")) {
+ struct mbuf *mb = mbuf_alloc(1);
+ if (!mb)
+ return ENOMEM;
+ err = udp_send(rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), mb);
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "stun")) {
+ err = stun_indication(IPPROTO_UDP, rtp_sock(rk->rtp),
+ sdp_media_raddr(rk->sdp), 0,
+ STUN_METHOD_BINDING, NULL, 0, false, 0);
+ }
+ else if (!str_casecmp(rk->method, "dyna")) {
+ struct mbuf *mb = mbuf_alloc(RTP_HEADER_SIZE);
+ int pt = sdp_media_find_unused_pt(rk->sdp);
+ if (!mb)
+ return ENOMEM;
+ if (pt == -1)
+ return ENOENT;
+ mb->pos = mb->end = RTP_HEADER_SIZE;
+
+ err = rtp_send(rk->rtp, sdp_media_raddr(rk->sdp), false,
+ false, pt, rk->ts, mb);
+
+ mem_deref(mb);
+ }
+ else if (!str_casecmp(rk->method, "rtcp")) {
+
+ if (sdp_media_rattr(rk->sdp, "rtcp-mux")) {
+ /* do nothing */
+ ;
+ }
+ else {
+ warning("rtpkeep: rtcp-mux is disabled\n");
+ }
+ }
+ else {
+ warning("rtpkeep: unknown method: %s\n", rk->method);
+ return ENOSYS;
+ }
+
+ return err;
+}
+
+
+/**
+ * Logic:
+ *
+ * We check for RTP activity every 15 seconds, and clear the flag.
+ * The flag is set for every transmitted RTP packet. If the flag
+ * is not set, it means that we have not sent any RTP packet in the
+ * last period of 0 - 15 seconds. Start transmitting RTP keepalives
+ * now and every 15 seconds after that.
+ *
+ * @param arg Handler argument
+ */
+static void timeout(void *arg)
+{
+ struct rtpkeep *rk = arg;
+ int err;
+
+ tmr_start(&rk->tmr, Tr_UDP * 1000, timeout, rk);
+
+ if (rk->flag) {
+ rk->flag = false;
+ return;
+ }
+
+ err = send_keepalive(rk);
+ if (err) {
+ warning("rtpkeep: send keepalive failed: %m\n", err);
+ }
+}
+
+
+int rtpkeep_alloc(struct rtpkeep **rkp, const char *method, int proto,
+ struct rtp_sock *rtp, struct sdp_media *sdp)
+{
+ struct rtpkeep *rk;
+ int err;
+
+ if (!rkp || !method || proto != IPPROTO_UDP || !rtp || !sdp)
+ return EINVAL;
+
+ rk = mem_zalloc(sizeof(*rk), destructor);
+ if (!rk)
+ return ENOMEM;
+
+ rk->rtp = rtp;
+ rk->sdp = sdp;
+
+ err = str_dup(&rk->method, method);
+ if (err)
+ goto out;
+
+ tmr_start(&rk->tmr, 20, timeout, rk);
+
+ out:
+ if (err)
+ mem_deref(rk);
+ else
+ *rkp = rk;
+
+ return err;
+}
+
+
+void rtpkeep_refresh(struct rtpkeep *rk, uint32_t ts)
+{
+ if (!rk)
+ return;
+
+ rk->ts = ts;
+ rk->flag = true;
+}
diff --git a/src/sdp.c b/src/sdp.c
new file mode 100644
index 0000000..da4889c
--- /dev/null
+++ b/src/sdp.c
@@ -0,0 +1,191 @@
+/**
+ * @file src/sdp.c SDP functions
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <stdlib.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+uint32_t sdp_media_rattr_u32(const struct sdp_media *m, const char *name)
+{
+ const char *attr = sdp_media_rattr(m, name);
+ return attr ? atoi(attr) : 0;
+}
+
+
+/*
+ * Get a remote attribute from the SDP. Try the media-level first,
+ * and if it does not exist then try session-level.
+ */
+const char *sdp_rattr(const struct sdp_session *s, const struct sdp_media *m,
+ const char *name)
+{
+ const char *x;
+
+ x = sdp_media_rattr(m, name);
+ if (x)
+ return x;
+
+ x = sdp_session_rattr(s, name);
+ if (x)
+ return x;
+
+ return NULL;
+}
+
+
+/* RFC 4572 */
+int sdp_fingerprint_decode(const char *attr, struct pl *hash,
+ uint8_t *md, size_t *sz)
+{
+ struct pl f;
+ const char *p;
+ int err;
+
+ if (!attr || !hash)
+ return EINVAL;
+
+ err = re_regex(attr, str_len(attr), "[^ ]+ [0-9A-F:]+", hash, &f);
+ if (err)
+ return err;
+
+ if (md && sz) {
+ if (*sz < (f.l+1)/3)
+ return EOVERFLOW;
+
+ for (p = f.p; p < (f.p+f.l); p += 3) {
+ *md++ = ch_hex(p[0]) << 4 | ch_hex(p[1]);
+ }
+
+ *sz = (f.l+1)/3;
+ }
+
+ return 0;
+}
+
+
+bool sdp_media_has_media(const struct sdp_media *m)
+{
+ bool has;
+
+ has = sdp_media_rformat(m, NULL) != NULL;
+ if (has)
+ return sdp_media_rport(m) != 0;
+
+ return false;
+}
+
+
+/**
+ * Find a dynamic payload type that is not used
+ *
+ * @param m SDP Media
+ *
+ * @return Unused payload type, -1 if no found
+ */
+int sdp_media_find_unused_pt(const struct sdp_media *m)
+{
+ int pt;
+
+ for (pt = PT_DYN_MAX; pt>=PT_DYN_MIN; pt--) {
+
+ if (!sdp_media_format(m, false, NULL, pt, NULL, -1, -1))
+ return pt;
+ }
+
+ return -1;
+}
+
+
+const struct sdp_format *sdp_media_format_cycle(struct sdp_media *m)
+{
+ struct sdp_format *sf;
+ struct list *lst;
+
+ again:
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!sf)
+ return NULL;
+
+ lst = sf->le.list;
+
+ /* move top-most codec to end of list */
+ list_unlink(&sf->le);
+ list_append(lst, &sf->le, sf);
+
+ sf = (struct sdp_format *)sdp_media_rformat(m, NULL);
+ if (!str_casecmp(sf->name, telev_rtpfmt))
+ goto again;
+
+ return sf;
+}
+
+
+static void decode_part(const struct pl *part, struct mbuf *mb)
+{
+ struct pl hdrs, body;
+
+ if (re_regex(part->p, part->l, "\r\n\r\n[^]+", &body))
+ return;
+
+ hdrs.p = part->p;
+ hdrs.l = body.p - part->p - 2;
+
+ if (0 == re_regex(hdrs.p, hdrs.l, "application/sdp")) {
+
+ mb->pos += (body.p - (char *)mbuf_buf(mb));
+ mb->end = mb->pos + body.l;
+ }
+}
+
+
+/**
+ * Decode a multipart/mixed message and find the part with application/sdp
+ *
+ * @param ctype_prm Content type parameter
+ * @param mb Mbuffer containing the SDP
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_decode_multipart(const struct pl *ctype_prm, struct mbuf *mb)
+{
+ struct pl bnd, s, e, p;
+ char expr[64];
+ int err;
+
+ if (!ctype_prm || !mb)
+ return EINVAL;
+
+ /* fetch the boundary tag, excluding quotes */
+ err = re_regex(ctype_prm->p, ctype_prm->l,
+ "boundary=[~]+", &bnd);
+ if (err)
+ return err;
+
+ if (re_snprintf(expr, sizeof(expr), "--%r[^]+", &bnd) < 0)
+ return ENOMEM;
+
+ /* find 1st boundary */
+ err = re_regex((char *)mbuf_buf(mb), mbuf_get_left(mb), expr, &s);
+ if (err)
+ return err;
+
+ /* iterate over each part */
+ while (s.l > 2) {
+ if (re_regex(s.p, s.l, expr, &e))
+ return 0;
+
+ p.p = s.p + 2;
+ p.l = e.p - p.p - bnd.l - 2;
+
+ /* valid part in "p" */
+ decode_part(&p, mb);
+
+ s = e;
+ }
+
+ return 0;
+}
diff --git a/src/sipreq.c b/src/sipreq.c
new file mode 100644
index 0000000..314f45c
--- /dev/null
+++ b/src/sipreq.c
@@ -0,0 +1,150 @@
+/**
+ * @file sipreq.c SIP Authenticated Request
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** SIP Authenticated Request */
+struct sip_req {
+ struct sip_loopstate ls;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct sip_request *req;
+ char *method;
+ char *fmt;
+ sip_resp_h *resph;
+ void *arg;
+};
+
+
+static int request(struct sip_req *sr);
+
+
+static void destructor(void *arg)
+{
+ struct sip_req *sr = arg;
+
+ mem_deref(sr->req);
+ mem_deref(sr->auth);
+ mem_deref(sr->dlg);
+ mem_deref(sr->method);
+ mem_deref(sr->fmt);
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sip_req *sr = arg;
+
+ if (err || sip_request_loops(&sr->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ ;
+ }
+ else {
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sr->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = request(sr);
+ if (err)
+ break;
+
+ return;
+
+ case 403:
+ sip_auth_reset(sr->auth);
+ break;
+ }
+ }
+
+ out:
+ if (sr->resph)
+ sr->resph(err, msg, sr->arg);
+
+ /* destroy now */
+ mem_deref(sr);
+}
+
+
+static int auth_handler(char **username, char **password,
+ const char *realm, void *arg)
+{
+ struct account *acc = arg;
+
+ return account_auth(acc, username, password, realm);
+}
+
+
+static int request(struct sip_req *sr)
+{
+ return sip_drequestf(&sr->req, uag_sip(), true, sr->method, sr->dlg,
+ 0, sr->auth, NULL, resp_handler,
+ sr, sr->fmt ? "%s" : NULL, sr->fmt);
+}
+
+
+int sip_req_send(struct ua *ua, const char *method, const char *uri,
+ sip_resp_h *resph, void *arg, const char *fmt, ...)
+{
+ const char *routev[1];
+ struct sip_req *sr;
+ int err;
+
+ if (!ua || !method || !uri || !fmt)
+ return EINVAL;
+
+ routev[0] = ua_outbound(ua);
+
+ sr = mem_zalloc(sizeof(*sr), destructor);
+ if (!sr)
+ return ENOMEM;
+
+ sr->resph = resph;
+ sr->arg = arg;
+
+ err = str_dup(&sr->method, method);
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ err |= re_vsdprintf(&sr->fmt, fmt, ap);
+ va_end(ap);
+ }
+
+ if (err)
+ goto out;
+
+ err = sip_dialog_alloc(&sr->dlg, uri, uri, NULL, ua_aor(ua),
+ routev[0] ? routev : NULL,
+ routev[0] ? 1 : 0);
+ if (err)
+ goto out;
+
+ err = sip_auth_alloc(&sr->auth, auth_handler, ua_account(ua), true);
+ if (err)
+ goto out;
+
+ err = request(sr);
+
+ out:
+ if (err)
+ mem_deref(sr);
+
+ return err;
+}
diff --git a/src/srcs.mk b/src/srcs.mk
new file mode 100644
index 0000000..a35e0d8
--- /dev/null
+++ b/src/srcs.mk
@@ -0,0 +1,55 @@
+#
+# srcs.mk All application source files.
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += account.c
+SRCS += aucodec.c
+SRCS += audio.c
+SRCS += aufilt.c
+SRCS += aulevel.c
+SRCS += auplay.c
+SRCS += ausrc.c
+SRCS += baresip.c
+SRCS += call.c
+SRCS += cmd.c
+SRCS += conf.c
+SRCS += config.c
+SRCS += contact.c
+SRCS += event.c
+SRCS += log.c
+SRCS += menc.c
+SRCS += message.c
+SRCS += metric.c
+SRCS += mnat.c
+SRCS += module.c
+SRCS += mos.c
+SRCS += net.c
+SRCS += play.c
+SRCS += realtime.c
+SRCS += reg.c
+SRCS += rtpext.c
+SRCS += rtpkeep.c
+SRCS += sdp.c
+SRCS += sipreq.c
+SRCS += stream.c
+SRCS += ua.c
+SRCS += ui.c
+
+ifneq ($(USE_VIDEO),)
+SRCS += bfcp.c
+SRCS += h264.c
+SRCS += mctrl.c
+SRCS += video.c
+SRCS += vidcodec.c
+SRCS += vidfilt.c
+SRCS += vidisp.c
+SRCS += vidsrc.c
+endif
+
+ifneq ($(STATIC),)
+SRCS += static.c
+endif
+
+APP_SRCS += main.c
diff --git a/src/stream.c b/src/stream.c
new file mode 100644
index 0000000..38db2d6
--- /dev/null
+++ b/src/stream.c
@@ -0,0 +1,733 @@
+/**
+ * @file stream.c Generic Media Stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+enum {
+ RTP_RECV_SIZE = 8192,
+ RTP_CHECK_INTERVAL = 1000 /* how often to check for RTP [ms] */
+};
+
+
+static void stream_close(struct stream *strm, int err)
+{
+ stream_error_h *errorh = strm->errorh;
+
+ strm->terminated = true;
+ strm->errorh = NULL;
+
+ if (errorh) {
+ errorh(strm, err, strm->errorh_arg);
+ }
+}
+
+
+static void check_rtp_handler(void *arg)
+{
+ struct stream *strm = arg;
+ const uint64_t now = tmr_jiffies();
+ int diff_ms;
+
+ tmr_start(&strm->tmr_rtp, RTP_CHECK_INTERVAL,
+ check_rtp_handler, strm);
+
+ /* If no RTP was received at all, check later */
+ if (!strm->ts_last)
+ return;
+
+ /* We are in sendrecv mode, check when the last RTP packet
+ * was received.
+ */
+ if (sdp_media_dir(strm->sdp) == SDP_SENDRECV) {
+
+ diff_ms = (int)(now - strm->ts_last);
+
+ debug("stream: last \"%s\" RTP packet: %d milliseconds\n",
+ sdp_media_name(strm->sdp), diff_ms);
+
+ /* check for large jumps in time */
+ if (diff_ms > (3600 * 1000)) {
+ strm->ts_last = 0;
+ return;
+ }
+
+ if (diff_ms > (int)strm->rtp_timeout_ms) {
+
+ info("stream: no %s RTP packets received for"
+ " %d milliseconds\n",
+ sdp_media_name(strm->sdp), diff_ms);
+
+ stream_close(strm, ETIMEDOUT);
+ }
+ }
+ else {
+ re_printf("check_rtp: not checking (dir=%s)\n",
+ sdp_dir_name(sdp_media_dir(strm->sdp)));
+ }
+}
+
+
+static inline int lostcalc(struct stream *s, uint16_t seq)
+{
+ const uint16_t delta = seq - s->pseq;
+ int lostc;
+
+ if (s->pseq == (uint32_t)-1)
+ lostc = 0;
+ else if (delta == 0)
+ return -1;
+ else if (delta < 3000)
+ lostc = delta - 1;
+ else if (delta < 0xff9c)
+ lostc = 0;
+ else
+ return -2;
+
+ s->pseq = seq;
+
+ return lostc;
+}
+
+
+static void print_rtp_stats(const struct stream *s)
+{
+ bool started = s->metric_tx.n_packets>0 || s->metric_rx.n_packets>0;
+
+ if (!started)
+ return;
+
+ info("\n%-9s Transmit: Receive:\n"
+ "packets: %7u %7u\n"
+ "avg. bitrate: %7.1f %7.1f (kbit/s)\n"
+ "errors: %7d %7d\n"
+ ,
+ sdp_media_name(s->sdp),
+ s->metric_tx.n_packets, s->metric_rx.n_packets,
+ 1.0*metric_avg_bitrate(&s->metric_tx)/1000,
+ 1.0*metric_avg_bitrate(&s->metric_rx)/1000,
+ s->metric_tx.n_err, s->metric_rx.n_err
+ );
+
+ if (s->rtcp_stats.tx.sent || s->rtcp_stats.rx.sent) {
+
+ info("pkt.report: %7u %7u\n"
+ "lost: %7d %7d\n"
+ "jitter: %7.1f %7.1f (ms)\n",
+ s->rtcp_stats.tx.sent, s->rtcp_stats.rx.sent,
+ s->rtcp_stats.tx.lost, s->rtcp_stats.rx.lost,
+ 1.0*s->rtcp_stats.tx.jit/1000,
+ 1.0*s->rtcp_stats.rx.jit/1000);
+ }
+}
+
+
+static void stream_destructor(void *arg)
+{
+ struct stream *s = arg;
+
+ if (s->cfg.rtp_stats)
+ print_rtp_stats(s);
+
+ metric_reset(&s->metric_tx);
+ metric_reset(&s->metric_rx);
+
+ tmr_cancel(&s->tmr_rtp);
+ list_unlink(&s->le);
+ mem_deref(s->rtpkeep);
+ mem_deref(s->sdp);
+ mem_deref(s->mes);
+ mem_deref(s->mencs);
+ mem_deref(s->mns);
+ mem_deref(s->jbuf);
+ mem_deref(s->rtp);
+ mem_deref(s->cname);
+}
+
+
+static void handle_rtp(struct stream *s, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct rtpext extv[8];
+ size_t extc = 0;
+
+ /* RFC 5285 -- A General Mechanism for RTP Header Extensions */
+ if (hdr->ext && hdr->x.len && mb) {
+
+ const size_t pos = mb->pos;
+ const size_t end = mb->end;
+ const size_t ext_stop = mb->pos;
+ size_t ext_len;
+ size_t i;
+ int err;
+
+ if (hdr->x.type != RTPEXT_TYPE_MAGIC) {
+ info("stream: unknown ext type ignored (0x%04x)\n",
+ hdr->x.type);
+ goto handler;
+ }
+
+ ext_len = hdr->x.len*sizeof(uint32_t);
+ if (mb->pos < ext_len) {
+ warning("stream: corrupt rtp packet,"
+ " not enough space for rtpext of %zu bytes\n",
+ ext_len);
+ return;
+ }
+
+ mb->pos = mb->pos - ext_len;
+ mb->end = ext_stop;
+
+ for (i=0; i<ARRAY_SIZE(extv) && mbuf_get_left(mb); i++) {
+
+ err = rtpext_decode(&extv[i], mb);
+ if (err) {
+ warning("stream: rtpext_decode failed (%m)\n",
+ err);
+ return;
+ }
+ }
+
+ extc = i;
+
+ mb->pos = pos;
+ mb->end = end;
+ }
+
+ handler:
+ s->rtph(hdr, extv, extc, mb, s->arg);
+
+}
+
+
+static void rtp_handler(const struct sa *src, const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct stream *s = arg;
+ bool flush = false;
+ int err;
+
+ s->ts_last = tmr_jiffies();
+
+ if (!mbuf_get_left(mb))
+ return;
+
+ if (!(sdp_media_ldir(s->sdp) & SDP_RECVONLY))
+ return;
+
+ metric_add_packet(&s->metric_rx, mbuf_get_left(mb));
+
+ if (hdr->ssrc != s->ssrc_rx) {
+ if (s->ssrc_rx) {
+ flush = true;
+ info("stream: %s: SSRC changed %x -> %x"
+ " (%u bytes from %J)\n",
+ sdp_media_name(s->sdp), s->ssrc_rx, hdr->ssrc,
+ mbuf_get_left(mb), src);
+ }
+ s->ssrc_rx = hdr->ssrc;
+ }
+
+ if (s->jbuf) {
+
+ struct rtp_header hdr2;
+ void *mb2 = NULL;
+
+ /* Put frame in Jitter Buffer */
+ if (flush)
+ jbuf_flush(s->jbuf);
+
+ err = jbuf_put(s->jbuf, hdr, mb);
+ if (err) {
+ info("%s: dropping %u bytes from %J (%m)\n",
+ sdp_media_name(s->sdp), mb->end,
+ src, err);
+ s->metric_rx.n_err++;
+ }
+
+ if (jbuf_get(s->jbuf, &hdr2, &mb2)) {
+
+ if (!s->jbuf_started)
+ return;
+
+ memset(&hdr2, 0, sizeof(hdr2));
+ }
+
+ s->jbuf_started = true;
+
+ if (lostcalc(s, hdr2.seq) > 0)
+ handle_rtp(s, hdr, NULL);
+
+ handle_rtp(s, &hdr2, mb2);
+
+ mem_deref(mb2);
+ }
+ else {
+ if (lostcalc(s, hdr->seq) > 0)
+ handle_rtp(s, hdr, NULL);
+
+ handle_rtp(s, hdr, mb);
+ }
+}
+
+
+static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg)
+{
+ struct stream *s = arg;
+ (void)src;
+
+ s->ts_last = tmr_jiffies();
+
+ if (s->rtcph)
+ s->rtcph(msg, s->arg);
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ (void)rtcp_stats(s->rtp, msg->r.sr.ssrc, &s->rtcp_stats);
+
+ if (s->cfg.rtp_stats)
+ call_set_xrtpstat(s->call);
+
+ ua_event(call_get_ua(s->call), UA_EVENT_CALL_RTCP, s->call,
+ "%s", sdp_media_name(stream_sdpmedia(s)));
+ break;
+ }
+}
+
+
+static int stream_sock_alloc(struct stream *s, int af)
+{
+ struct sa laddr;
+ int tos, err;
+
+ if (!s)
+ return EINVAL;
+
+ /* we listen on all interfaces */
+ sa_init(&laddr, af);
+
+ err = rtp_listen(&s->rtp, IPPROTO_UDP, &laddr,
+ s->cfg.rtp_ports.min, s->cfg.rtp_ports.max,
+ s->rtcp, rtp_handler, rtcp_handler, s);
+ if (err) {
+ warning("stream: rtp_listen failed: af=%s ports=%u-%u"
+ " (%m)\n", net_af2name(af),
+ s->cfg.rtp_ports.min, s->cfg.rtp_ports.max, err);
+ return err;
+ }
+
+ tos = s->cfg.rtp_tos;
+ (void)udp_setsockopt(rtp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+ (void)udp_setsockopt(rtcp_sock(s->rtp), IPPROTO_IP, IP_TOS,
+ &tos, sizeof(tos));
+
+ udp_rxsz_set(rtp_sock(s->rtp), RTP_RECV_SIZE);
+
+ return 0;
+}
+
+
+int stream_alloc(struct stream **sp, const struct stream_param *prm,
+ const struct config_avt *cfg,
+ struct call *call, struct sdp_session *sdp_sess,
+ const char *name, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *cname,
+ stream_rtp_h *rtph, stream_rtcp_h *rtcph, void *arg)
+{
+ struct stream *s;
+ int err;
+
+ if (!sp || !prm || !cfg || !call || !rtph)
+ return EINVAL;
+
+ s = mem_zalloc(sizeof(*s), stream_destructor);
+ if (!s)
+ return ENOMEM;
+
+ s->cfg = *cfg;
+ s->call = call;
+ s->rtph = rtph;
+ s->rtcph = rtcph;
+ s->arg = arg;
+ s->pseq = -1;
+ s->rtcp = s->cfg.rtcp_enable;
+
+ if (prm->use_rtp) {
+ err = stream_sock_alloc(s, call_af(call));
+ if (err) {
+ warning("stream: failed to create socket"
+ " for media '%s' (%m)\n", name, err);
+ goto out;
+ }
+ }
+
+ err = str_dup(&s->cname, cname);
+ if (err)
+ goto out;
+
+ /* Jitter buffer */
+ if (cfg->jbuf_del.min && cfg->jbuf_del.max) {
+
+ err = jbuf_alloc(&s->jbuf, cfg->jbuf_del.min,
+ cfg->jbuf_del.max);
+ if (err)
+ goto out;
+ }
+
+ err = sdp_media_add(&s->sdp, sdp_sess, name,
+ s->rtp ? sa_port(rtp_local(s->rtp)) : 9,
+ (menc && menc->sdp_proto) ? menc->sdp_proto :
+ sdp_proto_rtpavp);
+ if (err)
+ goto out;
+
+ if (label) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "label", "%d", label);
+ }
+
+ /* RFC 5506 */
+ if (s->rtcp)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-rsize", NULL);
+
+ /* RFC 5576 */
+ if (s->rtcp) {
+ err |= sdp_media_set_lattr(s->sdp, true,
+ "ssrc", "%u cname:%s",
+ rtp_sess_ssrc(s->rtp), cname);
+ }
+
+ /* RFC 5761 */
+ if (cfg->rtcp_mux)
+ err |= sdp_media_set_lattr(s->sdp, true, "rtcp-mux", NULL);
+
+ if (err)
+ goto out;
+
+ if (mnat && s->rtp) {
+ err = mnat->mediah(&s->mns, mnat_sess, IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (menc && s->rtp) {
+ s->menc = menc;
+ s->mencs = mem_ref(menc_sess);
+ err = menc->mediah(&s->mes, menc_sess,
+ s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err)
+ goto out;
+ }
+
+ if (err)
+ goto out;
+
+ s->pt_enc = -1;
+
+ metric_init(&s->metric_tx);
+ metric_init(&s->metric_rx);
+
+ list_append(call_streaml(call), &s->le, s);
+
+ out:
+ if (err)
+ mem_deref(s);
+ else
+ *sp = s;
+
+ return err;
+}
+
+
+struct sdp_media *stream_sdpmedia(const struct stream *s)
+{
+ return s ? s->sdp : NULL;
+}
+
+
+static void stream_start_keepalive(struct stream *s)
+{
+ const char *rtpkeep;
+
+ if (!s)
+ return;
+
+ rtpkeep = call_account(s->call)->rtpkeep;
+
+ s->rtpkeep = mem_deref(s->rtpkeep);
+
+ if (rtpkeep && sdp_media_rformat(s->sdp, NULL)) {
+ int err;
+ err = rtpkeep_alloc(&s->rtpkeep, rtpkeep,
+ IPPROTO_UDP, s->rtp, s->sdp);
+ if (err) {
+ warning("stream: rtpkeep_alloc failed: %m\n", err);
+ }
+ }
+}
+
+
+int stream_send(struct stream *s, bool ext, bool marker, int pt, uint32_t ts,
+ struct mbuf *mb)
+{
+ int err = 0;
+
+ if (!s)
+ return EINVAL;
+
+ if (!sa_isset(sdp_media_raddr(s->sdp), SA_ALL))
+ return 0;
+ if (sdp_media_dir(s->sdp) != SDP_SENDRECV)
+ return 0;
+
+ metric_add_packet(&s->metric_tx, mbuf_get_left(mb));
+
+ if (pt < 0)
+ pt = s->pt_enc;
+
+ if (pt >= 0) {
+ err = rtp_send(s->rtp, sdp_media_raddr(s->sdp), ext,
+ marker, pt, ts, mb);
+ if (err)
+ s->metric_tx.n_err++;
+ }
+
+ rtpkeep_refresh(s->rtpkeep, ts);
+
+ return err;
+}
+
+
+static void stream_remote_set(struct stream *s)
+{
+ struct sa rtcp;
+
+ if (!s)
+ return;
+
+ /* RFC 5761 */
+ if (s->cfg.rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) {
+
+ if (!s->rtcp_mux)
+ info("%s: RTP/RTCP multiplexing enabled\n",
+ sdp_media_name(s->sdp));
+ s->rtcp_mux = true;
+ }
+
+ rtcp_enable_mux(s->rtp, s->rtcp_mux);
+
+ sdp_media_raddr_rtcp(s->sdp, &rtcp);
+
+ rtcp_start(s->rtp, s->cname,
+ s->rtcp_mux ? sdp_media_raddr(s->sdp): &rtcp);
+}
+
+
+void stream_update(struct stream *s)
+{
+ const struct sdp_format *fmt;
+ int err = 0;
+
+ if (!s)
+ return;
+
+ fmt = sdp_media_rformat(s->sdp, NULL);
+
+ s->pt_enc = fmt ? fmt->pt : -1;
+
+ if (sdp_media_has_media(s->sdp))
+ stream_remote_set(s);
+
+ if (s->menc && s->menc->mediah) {
+ err = s->menc->mediah(&s->mes, s->mencs, s->rtp,
+ IPPROTO_UDP,
+ rtp_sock(s->rtp),
+ s->rtcp ? rtcp_sock(s->rtp) : NULL,
+ s->sdp);
+ if (err) {
+ warning("stream: mediaenc update: %m\n", err);
+ }
+ }
+}
+
+
+void stream_update_encoder(struct stream *s, int pt_enc)
+{
+ if (!s)
+ return;
+
+ if (pt_enc >= 0)
+ s->pt_enc = pt_enc;
+}
+
+
+int stream_jbuf_stat(struct re_printf *pf, const struct stream *s)
+{
+ struct jbuf_stat stat;
+ int err;
+
+ if (!s)
+ return EINVAL;
+
+ err = re_hprintf(pf, " %s:", sdp_media_name(s->sdp));
+
+ err |= jbuf_stats(s->jbuf, &stat);
+ if (err) {
+ err = re_hprintf(pf, "Jbuf stat: (not available)");
+ }
+ else {
+ err = re_hprintf(pf, "Jbuf stat: put=%u get=%u or=%u ur=%u",
+ stat.n_put, stat.n_get,
+ stat.n_overflow, stat.n_underflow);
+ }
+
+ return err;
+}
+
+
+void stream_hold(struct stream *s, bool hold)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_ldir(s->sdp, hold ? SDP_SENDONLY : SDP_SENDRECV);
+}
+
+
+void stream_set_srate(struct stream *s, uint32_t srate_tx, uint32_t srate_rx)
+{
+ if (!s)
+ return;
+
+ rtcp_set_srate(s->rtp, srate_tx, srate_rx);
+}
+
+
+void stream_send_fir(struct stream *s, bool pli)
+{
+ int err;
+
+ if (!s)
+ return;
+
+ if (pli)
+ err = rtcp_send_pli(s->rtp, s->ssrc_rx);
+ else
+ err = rtcp_send_fir(s->rtp, rtp_sess_ssrc(s->rtp));
+
+ if (err) {
+ s->metric_tx.n_err++;
+
+ warning("stream: failed to send RTCP %s: %m\n",
+ pli ? "PLI" : "FIR", err);
+ }
+}
+
+
+void stream_reset(struct stream *s)
+{
+ if (!s)
+ return;
+
+ jbuf_flush(s->jbuf);
+
+ stream_start_keepalive(s);
+}
+
+
+void stream_set_bw(struct stream *s, uint32_t bps)
+{
+ if (!s)
+ return;
+
+ sdp_media_set_lbandwidth(s->sdp, SDP_BANDWIDTH_AS, bps / 1000);
+}
+
+
+void stream_enable_rtp_timeout(struct stream *strm, uint32_t timeout_ms)
+{
+ if (!strm)
+ return;
+
+ strm->rtp_timeout_ms = timeout_ms;
+
+ tmr_cancel(&strm->tmr_rtp);
+
+ if (timeout_ms) {
+
+ info("stream: Enable RTP timeout (%u milliseconds)\n",
+ timeout_ms);
+
+ strm->ts_last = tmr_jiffies();
+ tmr_start(&strm->tmr_rtp, 10, check_rtp_handler, strm);
+ }
+}
+
+
+void stream_set_error_handler(struct stream *strm,
+ stream_error_h *errorh, void *arg)
+{
+ if (!strm)
+ return;
+
+ strm->errorh = errorh;
+ strm->errorh_arg = arg;
+}
+
+
+int stream_debug(struct re_printf *pf, const struct stream *s)
+{
+ struct sa rrtcp;
+ int err;
+
+ if (!s)
+ return 0;
+
+ err = re_hprintf(pf, " %s dir=%s pt_enc=%d\n", sdp_media_name(s->sdp),
+ sdp_dir_name(sdp_media_dir(s->sdp)),
+ s->pt_enc);
+
+ sdp_media_raddr_rtcp(s->sdp, &rrtcp);
+ err |= re_hprintf(pf, " local: %J, remote: %J/%J\n",
+ sdp_media_laddr(s->sdp),
+ sdp_media_raddr(s->sdp), &rrtcp);
+
+ err |= rtp_debug(pf, s->rtp);
+ err |= jbuf_debug(pf, s->jbuf);
+
+ return err;
+}
+
+
+int stream_print(struct re_printf *pf, const struct stream *s)
+{
+ if (!s)
+ return 0;
+
+ return re_hprintf(pf, " %s=%u/%u", sdp_media_name(s->sdp),
+ s->metric_tx.cur_bitrate,
+ s->metric_rx.cur_bitrate);
+}
+
+
+const struct rtcp_stats *stream_rtcp_stats(const struct stream *strm)
+{
+ return strm ? &strm->rtcp_stats : NULL;
+}
diff --git a/src/ua.c b/src/ua.c
new file mode 100644
index 0000000..0467e35
--- /dev/null
+++ b/src/ua.c
@@ -0,0 +1,1906 @@
+/**
+ * @file src/ua.c User-Agent
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+#include <ctype.h>
+
+
+/** Magic number */
+#define MAGIC 0x0a0a0a0a
+#include "magic.h"
+
+
+/** Defines a SIP User Agent object */
+struct ua {
+ MAGIC_DECL /**< Magic number for struct ua */
+ struct ua **uap; /**< Pointer to application's ua */
+ struct le le; /**< Linked list element */
+ struct account *acc; /**< Account Parameters */
+ struct list regl; /**< List of Register clients */
+ struct list calls; /**< List of active calls (struct call) */
+ struct pl extensionv[8]; /**< Vector of SIP extensions */
+ size_t extensionc; /**< Number of SIP extensions */
+ char *cuser; /**< SIP Contact username */
+ char *pub_gruu; /**< SIP Public GRUU */
+ int af; /**< Preferred Address Family */
+ int af_media; /**< Preferred Address Family for media */
+ enum presence_status my_status; /**< Presence Status */
+};
+
+struct ua_eh {
+ struct le le;
+ ua_event_h *h;
+ void *arg;
+};
+
+static struct {
+ struct config_sip *cfg; /**< SIP configuration */
+ struct list ual; /**< List of User-Agents (struct ua) */
+ struct list ehl; /**< Event handlers (struct ua_eh) */
+ struct sip *sip; /**< SIP Stack */
+ struct sip_lsnr *lsnr; /**< SIP Listener */
+ struct sipsess_sock *sock; /**< SIP Session socket */
+ struct sipevent_sock *evsock; /**< SIP Event socket */
+ struct ua *ua_cur; /**< Current User-Agent */
+ bool use_udp; /**< Use UDP transport */
+ bool use_tcp; /**< Use TCP transport */
+ bool use_tls; /**< Use TLS transport */
+ bool prefer_ipv6; /**< Force IPv6 transport */
+ sip_msg_h *subh; /**< Subscribe handler */
+ ua_exit_h *exith; /**< UA Exit handler */
+ void *arg; /**< UA Exit handler argument */
+ char *eprm; /**< Extra UA parameters */
+#ifdef USE_TLS
+ struct tls *tls; /**< TLS Context */
+#endif
+} uag = {
+ NULL,
+ LIST_INIT,
+ LIST_INIT,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ true,
+ false,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+#ifdef USE_TLS
+ NULL,
+#endif
+};
+
+
+/* prototypes */
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri,
+ bool use_rtp);
+
+
+/* This function is called when all SIP transactions are done */
+static void exit_handler(void *arg)
+{
+ (void)arg;
+
+ ua_event(NULL, UA_EVENT_EXIT, NULL, NULL);
+
+ debug("ua: sip-stack exit\n");
+
+ if (uag.exith)
+ uag.exith(uag.arg);
+}
+
+
+void ua_printf(const struct ua *ua, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!ua)
+ return;
+
+ va_start(ap, fmt);
+ info("%r@%r: %v", &ua->acc->luri.user, &ua->acc->luri.host, fmt, &ap);
+ va_end(ap);
+}
+
+
+void ua_event(struct ua *ua, enum ua_event ev, struct call *call,
+ const char *fmt, ...)
+{
+ struct le *le;
+ char buf[256];
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* send event to all clients */
+ le = uag.ehl.head;
+ while (le) {
+ struct ua_eh *eh = le->data;
+ le = le->next;
+
+ eh->h(ua, ev, call, buf, eh->arg);
+ }
+}
+
+
+/**
+ * Start registration of a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_register(struct ua *ua)
+{
+ struct account *acc;
+ struct le *le;
+ struct uri uri;
+ char *reg_uri = NULL;
+ char params[256] = "";
+ unsigned i;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ acc = ua->acc;
+ uri = ua->acc->luri;
+ uri.user = uri.password = pl_null;
+
+ err = re_sdprintf(&reg_uri, "%H", uri_encode, &uri);
+ if (err)
+ goto out;
+
+ if (uag.cfg && str_isset(uag.cfg->uuid)) {
+ if (re_snprintf(params, sizeof(params),
+ ";+sip.instance=\"<urn:uuid:%s>\"",
+ uag.cfg->uuid) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ if (acc->regq) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";q=%s", acc->regq) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ if (acc->mnat && acc->mnat->ftag) {
+ if (re_snprintf(&params[strlen(params)],
+ sizeof(params) - strlen(params),
+ ";%s", acc->mnat->ftag) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+ }
+
+ ua_event(ua, UA_EVENT_REGISTERING, NULL, NULL);
+
+ for (le = ua->regl.head, i=0; le; le = le->next, i++) {
+ struct reg *reg = le->data;
+
+ err = reg_register(reg, reg_uri, params,
+ acc->regint, acc->outboundv[i]);
+ if (err) {
+ warning("ua: SIP register failed: %m\n", err);
+ goto out;
+ }
+ }
+
+ out:
+ mem_deref(reg_uri);
+
+ return err;
+}
+
+
+/**
+ * Unregister all Register clients of a User-Agent
+ *
+ * @param ua User-Agent
+ */
+void ua_unregister(struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return;
+
+ if (!list_isempty(&ua->regl))
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ for (le = ua->regl.head; le; le = le->next) {
+ struct reg *reg = le->data;
+
+ reg_unregister(reg);
+ }
+}
+
+
+bool ua_isregistered(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return false;
+
+ for (le = ua->regl.head; le; le = le->next) {
+
+ const struct reg *reg = le->data;
+
+ /* it is enough if one of the registrations work */
+ if (reg_isok(reg))
+ return true;
+ }
+
+ return false;
+}
+
+
+static struct call *ua_find_call_onhold(const struct ua *ua)
+{
+ struct le *le;
+
+ if (!ua)
+ return NULL;
+
+ for (le = ua->calls.tail; le; le = le->prev) {
+
+ struct call *call = le->data;
+
+ if (call_is_onhold(call))
+ return call;
+ }
+
+ return NULL;
+}
+
+
+static void resume_call(struct ua *ua)
+{
+ struct call *call;
+
+ call = ua_find_call_onhold(ua);
+ if (call) {
+ ua_printf(ua, "resuming previous call with '%s'\n",
+ call_peeruri(call));
+ call_hold(call, false);
+ }
+}
+
+
+static void call_event_handler(struct call *call, enum call_event ev,
+ const char *str, void *arg)
+{
+ struct ua *ua = arg;
+ const char *peeruri;
+ struct call *call2 = NULL;
+ int err;
+
+ MAGIC_CHECK(ua);
+
+ peeruri = call_peeruri(call);
+
+ switch (ev) {
+
+ case CALL_EVENT_INCOMING:
+
+ if (contact_block_access(baresip_contacts(),
+ peeruri)) {
+
+ info("ua: blocked access: \"%s\"\n", peeruri);
+
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+ break;
+ }
+
+ switch (ua->acc->answermode) {
+
+ case ANSWERMODE_EARLY:
+ (void)call_progress(call);
+ break;
+
+ case ANSWERMODE_AUTO:
+ (void)call_answer(call, 200);
+ break;
+
+ case ANSWERMODE_MANUAL:
+ default:
+ ua_event(ua, UA_EVENT_CALL_INCOMING, call, peeruri);
+ break;
+ }
+ break;
+
+ case CALL_EVENT_RINGING:
+ ua_event(ua, UA_EVENT_CALL_RINGING, call, peeruri);
+ break;
+
+ case CALL_EVENT_PROGRESS:
+ ua_printf(ua, "Call in-progress: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_PROGRESS, call, peeruri);
+ break;
+
+ case CALL_EVENT_ESTABLISHED:
+ ua_printf(ua, "Call established: %s\n", peeruri);
+ ua_event(ua, UA_EVENT_CALL_ESTABLISHED, call, peeruri);
+ break;
+
+ case CALL_EVENT_CLOSED:
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, str);
+ mem_deref(call);
+
+ resume_call(ua);
+ break;
+
+ case CALL_EVENT_TRANSFER:
+
+ /*
+ * Create a new call to transfer target.
+ *
+ * NOTE: we will automatically connect a new call to the
+ * transfer target
+ */
+
+ ua_printf(ua, "transferring call to %s\n", str);
+
+ err = ua_call_alloc(&call2, ua, VIDMODE_ON, NULL, call,
+ call_localuri(call), true);
+ if (!err) {
+ struct pl pl;
+
+ pl_set_str(&pl, str);
+
+ err = call_connect(call2, &pl);
+ if (err) {
+ warning("ua: transfer: connect error: %m\n",
+ err);
+ }
+ }
+
+ if (err) {
+ (void)call_notify_sipfrag(call, 500, "Call Error");
+ mem_deref(call2);
+ }
+ break;
+
+ case CALL_EVENT_TRANSFER_FAILED:
+ ua_event(ua, UA_EVENT_CALL_TRANSFER_FAILED, call, str);
+ break;
+ }
+}
+
+
+static void call_dtmf_handler(struct call *call, char key, void *arg)
+{
+ struct ua *ua = arg;
+ char key_str[2];
+
+ MAGIC_CHECK(ua);
+
+ if (key != '\0') {
+
+ key_str[0] = key;
+ key_str[1] = '\0';
+
+ ua_event(ua, UA_EVENT_CALL_DTMF_START, call, key_str);
+ }
+ else {
+ ua_event(ua, UA_EVENT_CALL_DTMF_END, call, NULL);
+ }
+}
+
+
+static int ua_call_alloc(struct call **callp, struct ua *ua,
+ enum vidmode vidmode, const struct sip_msg *msg,
+ struct call *xcall, const char *local_uri,
+ bool use_rtp)
+{
+ const struct network *net = baresip_network();
+ struct call_prm cprm;
+ int af = AF_UNSPEC;
+ int err;
+
+ if (*callp) {
+ warning("ua: call_alloc: call is already allocated\n");
+ return EALREADY;
+ }
+
+ /* 1. if AF_MEDIA is set, we prefer it
+ * 2. otherwise fall back to SIP AF
+ */
+ if (ua->af_media) {
+ af = ua->af_media;
+ }
+ else if (ua->af) {
+ af = ua->af;
+ }
+
+ memset(&cprm, 0, sizeof(cprm));
+
+ sa_cpy(&cprm.laddr, net_laddr_af(net, af));
+ cprm.vidmode = vidmode;
+ cprm.af = af;
+ cprm.use_rtp = use_rtp;
+
+ err = call_alloc(callp, conf_config(), &ua->calls,
+ ua->acc->dispname,
+ local_uri ? local_uri : ua->acc->aor,
+ ua->acc, ua, &cprm,
+ msg, xcall,
+ net_dnsc(net),
+ call_event_handler, ua);
+ if (err)
+ return err;
+
+ call_set_handlers(*callp, NULL, call_dtmf_handler, ua);
+
+ return 0;
+}
+
+
+static void handle_options(struct ua *ua, const struct sip_msg *msg)
+{
+ struct sip_contact contact;
+ struct call *call = NULL;
+ struct mbuf *desc = NULL;
+ const struct sip_hdr *hdr;
+ bool accept_sdp = true;
+ int err;
+
+ debug("ua: incoming OPTIONS message from %r (%J)\n",
+ &msg->from.auri, &msg->src);
+
+ /* application/sdp is the default if the
+ Accept header field is not present */
+ hdr = sip_msg_hdr(msg, SIP_HDR_ACCEPT);
+ if (hdr) {
+ accept_sdp = 0==pl_strcasecmp(&hdr->val, "application/sdp");
+ }
+
+ if (accept_sdp) {
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, NULL, NULL, NULL,
+ false);
+ if (err) {
+ (void)sip_treply(NULL, uag.sip, msg,
+ 500, "Call Error");
+ return;
+ }
+
+ err = call_sdp_get(call, &desc, true);
+ if (err)
+ goto out;
+ }
+
+ sip_contact_set(&contact, ua_cuser(ua), &msg->dst, msg->tp);
+
+ err = sip_treplyf(NULL, NULL, uag.sip,
+ msg, true, 200, "OK",
+ "Allow: %s\r\n"
+ "%H"
+ "%H"
+ "%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ uag_allowed_methods(),
+ ua_print_supported, ua,
+ sip_contact_print, &contact,
+ desc ? "Content-Type: application/sdp\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+ if (err) {
+ warning("ua: options: sip_treplyf: %m\n", err);
+ }
+
+ out:
+ mem_deref(desc);
+ mem_deref(call);
+}
+
+
+static void ua_destructor(void *arg)
+{
+ struct ua *ua = arg;
+
+ if (ua->uap) {
+ *ua->uap = NULL;
+ ua->uap = NULL;
+ }
+
+ list_unlink(&ua->le);
+
+ if (!list_isempty(&ua->regl))
+ ua_event(ua, UA_EVENT_UNREGISTERING, NULL, NULL);
+
+ list_flush(&ua->calls);
+ list_flush(&ua->regl);
+ mem_deref(ua->cuser);
+ mem_deref(ua->pub_gruu);
+ mem_deref(ua->acc);
+
+ if (list_isempty(&uag.ual)) {
+ sip_close(uag.sip, false);
+ }
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ if (pl_strcmp(&msg->met, "OPTIONS"))
+ return false;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ handle_options(ua, msg);
+
+ return true;
+}
+
+
+static void add_extension(struct ua *ua, const char *extension)
+{
+ struct pl e;
+
+ if (ua->extensionc >= ARRAY_SIZE(ua->extensionv)) {
+ warning("ua: maximum %u number of SIP extensions\n");
+ return;
+ }
+
+ pl_set_str(&e, extension);
+
+ ua->extensionv[ua->extensionc++] = e;
+}
+
+
+/**
+ * Allocate a SIP User-Agent
+ *
+ * @param uap Pointer to allocated User-Agent object
+ * @param aor SIP Address-of-Record (AOR)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_alloc(struct ua **uap, const char *aor)
+{
+ struct ua *ua;
+ char *buf = NULL;
+ int err;
+
+ if (!aor)
+ return EINVAL;
+
+ ua = mem_zalloc(sizeof(*ua), ua_destructor);
+ if (!ua)
+ return ENOMEM;
+
+ MAGIC_INIT(ua);
+
+ list_init(&ua->calls);
+
+#if HAVE_INET6
+ ua->af = uag.prefer_ipv6 ? AF_INET6 : AF_INET;
+#else
+ ua->af = AF_INET;
+#endif
+
+ /* Decode SIP address */
+ if (uag.eprm) {
+ err = re_sdprintf(&buf, "%s;%s", aor, uag.eprm);
+ if (err)
+ goto out;
+ aor = buf;
+ }
+
+ err = account_alloc(&ua->acc, aor);
+ if (err)
+ goto out;
+
+ /* generate a unique contact-user, this is needed to route
+ incoming requests when using multiple useragents */
+ err = re_sdprintf(&ua->cuser, "%r-%p", &ua->acc->luri.user, ua);
+ if (err)
+ goto out;
+
+ if (ua->acc->sipnat) {
+ ua_printf(ua, "Using sipnat: `%s'\n", ua->acc->sipnat);
+ }
+
+ if (ua->acc->mnat) {
+ ua_printf(ua, "Using medianat `%s'\n",
+ ua->acc->mnat->id);
+
+ if (0 == str_casecmp(ua->acc->mnat->id, "ice"))
+ add_extension(ua, "ice");
+ }
+
+ if (ua->acc->menc) {
+ ua_printf(ua, "Using media encryption `%s'\n",
+ ua->acc->menc->id);
+ }
+
+ /* Register clients */
+ if (uag.cfg && str_isset(uag.cfg->uuid))
+ add_extension(ua, "gruu");
+
+ if (0 == str_casecmp(ua->acc->sipnat, "outbound")) {
+
+ size_t i;
+
+ add_extension(ua, "path");
+ add_extension(ua, "outbound");
+
+ if (!str_isset(uag.cfg->uuid)) {
+
+ warning("ua: outbound requires valid UUID!\n");
+ err = ENOSYS;
+ goto out;
+ }
+
+ for (i=0; i<ARRAY_SIZE(ua->acc->outboundv); i++) {
+
+ if (ua->acc->outboundv[i] && ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, (int)i+1);
+ if (err)
+ break;
+ }
+ }
+ }
+ else if (ua->acc->regint) {
+ err = reg_add(&ua->regl, ua, 0);
+ }
+ if (err)
+ goto out;
+
+ list_append(&uag.ual, &ua->le, ua);
+
+ if (ua->acc->regint) {
+ err = ua_register(ua);
+ }
+
+ if (!uag_current())
+ uag_current_set(ua);
+
+ out:
+ mem_deref(buf);
+ if (err)
+ mem_deref(ua);
+ else if (uap) {
+ *uap = ua;
+
+ ua->uap = uap;
+ }
+
+ return err;
+}
+
+
+static int uri_complete(struct ua *ua, struct mbuf *buf, const char *uri)
+{
+ size_t len;
+ int err = 0;
+
+ /* Skip initial whitespace */
+ while (isspace(*uri))
+ ++uri;
+
+ len = str_len(uri);
+
+ /* Append sip: scheme if missing */
+ if (0 != re_regex(uri, len, "sip:"))
+ err |= mbuf_printf(buf, "sip:");
+
+ err |= mbuf_write_str(buf, uri);
+
+ /* Append domain if missing */
+ if (0 != re_regex(uri, len, "[^@]+@[^]+", NULL, NULL)) {
+#if HAVE_INET6
+ if (AF_INET6 == ua->acc->luri.af)
+ err |= mbuf_printf(buf, "@[%r]",
+ &ua->acc->luri.host);
+ else
+#endif
+ err |= mbuf_printf(buf, "@%r",
+ &ua->acc->luri.host);
+
+ /* Also append port if specified and not 5060 */
+ switch (ua->acc->luri.port) {
+
+ case 0:
+ case SIP_PORT:
+ break;
+
+ default:
+ err |= mbuf_printf(buf, ":%u", ua->acc->luri.port);
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Connect an outgoing call to a given SIP uri
+ *
+ * @param ua User-Agent
+ * @param callp Optional pointer to allocated call object
+ * @param from_uri Optional From uri, or NULL for default AOR
+ * @param uri SIP uri to connect to
+ * @param params Optional URI parameters
+ * @param vmode Video mode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_connect(struct ua *ua, struct call **callp,
+ const char *from_uri, const char *uri,
+ const char *params, enum vidmode vmode)
+{
+ struct call *call = NULL;
+ struct mbuf *dialbuf;
+ struct pl pl;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ if (params)
+ err |= mbuf_printf(dialbuf, "<");
+
+ err |= uri_complete(ua, dialbuf, uri);
+
+ if (params) {
+ err |= mbuf_printf(dialbuf, ";%s", params);
+ }
+
+ /* Append any optional URI parameters */
+ err |= mbuf_write_pl(dialbuf, &ua->acc->luri.params);
+
+ if (params)
+ err |= mbuf_printf(dialbuf, ">");
+
+ if (err)
+ goto out;
+
+ err = ua_call_alloc(&call, ua, vmode, NULL, NULL, from_uri, true);
+ if (err)
+ goto out;
+
+ pl.p = (char *)dialbuf->buf;
+ pl.l = dialbuf->end;
+
+ err = call_connect(call, &pl);
+
+ if (err)
+ mem_deref(call);
+ else if (callp)
+ *callp = call;
+
+ out:
+ mem_deref(dialbuf);
+
+ return err;
+}
+
+
+/**
+ * Hangup the current call
+ *
+ * @param ua User-Agent
+ * @param call Call to hangup, or NULL for current call
+ * @param scode Optional status code
+ * @param reason Optional reason
+ */
+void ua_hangup(struct ua *ua, struct call *call,
+ uint16_t scode, const char *reason)
+{
+ if (!ua)
+ return;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return;
+ }
+
+ (void)call_hangup(call, scode, reason);
+
+ ua_event(ua, UA_EVENT_CALL_CLOSED, call, reason);
+
+ mem_deref(call);
+
+ resume_call(ua);
+}
+
+
+/**
+ * Answer an incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to answer, or NULL for current call
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_answer(struct ua *ua, struct call *call)
+{
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ return call_answer(call, 200);
+}
+
+
+int ua_progress(struct ua *ua, struct call *call)
+{
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ return call_progress(call);
+}
+
+
+/**
+ * Put the current call on hold and answer the incoming call
+ *
+ * @param ua User-Agent
+ * @param call Call to answer, or NULL for current call
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_hold_answer(struct ua *ua, struct call *call)
+{
+ struct call *pcall;
+ int err;
+
+ if (!ua)
+ return EINVAL;
+
+ if (!call) {
+ call = ua_call(ua);
+ if (!call)
+ return ENOENT;
+ }
+
+ /* put previous call on-hold */
+ pcall = ua_prev_call(ua);
+ if (pcall) {
+ ua_printf(ua, "putting call with '%s' on hold\n",
+ call_peeruri(pcall));
+
+ err = call_hold(pcall, true);
+ if (err)
+ return err;
+ }
+
+ return ua_answer(ua, call);
+}
+
+
+int ua_print_status(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "%-42s", ua->acc->aor);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_status(pf, le->data);
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Send SIP OPTIONS message to a peer
+ *
+ * @param ua User-Agent object
+ * @param uri Peer SIP Address
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_options_send(struct ua *ua, const char *uri,
+ options_resp_h *resph, void *arg)
+{
+ struct mbuf *dialbuf;
+ int err = 0;
+
+ if (!ua || !str_isset(uri))
+ return EINVAL;
+
+ dialbuf = mbuf_alloc(64);
+ if (!dialbuf)
+ return ENOMEM;
+
+ err = uri_complete(ua, dialbuf, uri);
+ if (err)
+ goto out;
+
+ dialbuf->buf[dialbuf->end] = '\0';
+
+ err = sip_req_send(ua, "OPTIONS", (char *)dialbuf->buf, resph, arg,
+ "Accept: application/sdp\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ if (err) {
+ warning("ua: send options: (%m)\n", err);
+ }
+
+ out:
+ mem_deref(dialbuf);
+
+ return err;
+}
+
+
+/**
+ * Get the AOR of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return AOR
+ */
+const char *ua_aor(const struct ua *ua)
+{
+ return ua ? account_aor(ua->acc) : NULL;
+}
+
+
+/**
+ * Get presence status of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return presence status
+ */
+enum presence_status ua_presence_status(const struct ua *ua)
+{
+ return ua ? ua->my_status : PRESENCE_UNKNOWN;
+}
+
+
+/**
+ * Set presence status of a User-Agent
+ *
+ * @param ua User-Agent object
+ * @param status Presence status
+ */
+void ua_presence_status_set(struct ua *ua, const enum presence_status status)
+{
+ if (!ua)
+ return;
+
+ ua->my_status = status;
+}
+
+
+/**
+ * Get the outbound SIP proxy of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Outbound SIP proxy uri
+ */
+const char *ua_outbound(const struct ua *ua)
+{
+ /* NOTE: we pick the first outbound server, should be rotated? */
+ return ua ? ua->acc->outboundv[0] : NULL;
+}
+
+
+/**
+ * Get the current call object of a User-Agent
+ *
+ * @param ua User-Agent object
+ *
+ * @return Current call, NULL if no active calls
+ *
+ *
+ * Current call strategy:
+ *
+ * We can only have 1 current call. The current call is the one that was
+ * added last (end of the list).
+ */
+struct call *ua_call(const struct ua *ua)
+{
+ if (!ua)
+ return NULL;
+
+ return list_ledata(list_tail(&ua->calls));
+}
+
+
+struct call *ua_prev_call(const struct ua *ua)
+{
+ struct le *le;
+ int prev = 0;
+
+ if (!ua)
+ return NULL;
+
+ for (le = ua->calls.tail; le; le = le->prev) {
+ if ( prev == 1) {
+ struct call *call = le->data;
+ return call;
+ }
+ if ( prev == 0)
+ prev = 1;
+ }
+
+ return NULL;
+}
+
+
+int ua_debug(struct re_printf *pf, const struct ua *ua)
+{
+ struct le *le;
+ int err;
+
+ if (!ua)
+ return 0;
+
+ err = re_hprintf(pf, "--- %s ---\n", ua->acc->aor);
+ err |= re_hprintf(pf, " nrefs: %u\n", mem_nrefs(ua));
+ err |= re_hprintf(pf, " cuser: %s\n", ua->cuser);
+ err |= re_hprintf(pf, " pub-gruu: %s\n", ua->pub_gruu);
+ err |= re_hprintf(pf, " af: %s\n", net_af2name(ua->af));
+ err |= re_hprintf(pf, " %H", ua_print_supported, ua);
+
+ err |= account_debug(pf, ua->acc);
+
+ for (le = ua->regl.head; le; le = le->next)
+ err |= reg_debug(pf, le->data);
+
+ return err;
+}
+
+
+/* One instance */
+
+
+static int add_transp_af(const struct sa *laddr)
+{
+ struct sa local;
+ int err = 0;
+
+ if (str_isset(uag.cfg->local)) {
+ err = sa_decode(&local, uag.cfg->local,
+ str_len(uag.cfg->local));
+ if (err) {
+ err = sa_set_str(&local, uag.cfg->local, 0);
+ if (err) {
+ warning("ua: decode failed: '%s'\n",
+ uag.cfg->local);
+ return err;
+ }
+ }
+
+ if (!sa_isset(&local, SA_ADDR)) {
+ uint16_t port = sa_port(&local);
+ (void)sa_set_sa(&local, &laddr->u.sa);
+ sa_set_port(&local, port);
+ }
+
+ if (sa_af(laddr) != sa_af(&local))
+ return 0;
+ }
+ else {
+ sa_cpy(&local, laddr);
+ sa_set_port(&local, 0);
+ }
+
+ if (uag.use_udp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_UDP, &local);
+ if (uag.use_tcp)
+ err |= sip_transp_add(uag.sip, SIP_TRANSP_TCP, &local);
+ if (err) {
+ warning("ua: SIP Transport failed: %m\n", err);
+ return err;
+ }
+
+#ifdef USE_TLS
+ if (uag.use_tls) {
+ /* Build our SSL context*/
+ if (!uag.tls) {
+ const char *cert = NULL;
+
+ if (str_isset(uag.cfg->cert)) {
+ cert = uag.cfg->cert;
+ info("SIP Certificate: %s\n", cert);
+ }
+
+ err = tls_alloc(&uag.tls, TLS_METHOD_SSLV23,
+ cert, NULL);
+ if (err) {
+ warning("ua: tls_alloc() failed: %m\n", err);
+ return err;
+ }
+ }
+
+ if (sa_isset(&local, SA_PORT))
+ sa_set_port(&local, sa_port(&local) + 1);
+
+ err = sip_transp_add(uag.sip, SIP_TRANSP_TLS, &local, uag.tls);
+ if (err) {
+ warning("ua: SIP/TLS transport failed: %m\n", err);
+ return err;
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+static int ua_add_transp(struct network *net)
+{
+ int err = 0;
+
+ if (!uag.prefer_ipv6) {
+ if (sa_isset(net_laddr_af(net, AF_INET), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(net, AF_INET));
+ }
+
+#if HAVE_INET6
+ if (sa_isset(net_laddr_af(net, AF_INET6), SA_ADDR))
+ err |= add_transp_af(net_laddr_af(net, AF_INET6));
+#endif
+
+ return err;
+}
+
+
+static bool require_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua = arg;
+ bool supported = false;
+ size_t i;
+ (void)msg;
+
+ for (i=0; i<ua->extensionc; i++) {
+
+ if (!pl_casecmp(&hdr->val, &ua->extensionv[i])) {
+ supported = true;
+ break;
+ }
+ }
+
+ return !supported;
+}
+
+
+/* Handle incoming calls */
+static void sipsess_conn_handler(const struct sip_msg *msg, void *arg)
+{
+ struct config *config = conf_config();
+ const struct sip_hdr *hdr;
+ struct ua *ua;
+ struct call *call = NULL;
+ char to_uri[256];
+ int err;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ warning("ua: %r: UA not found: %r\n",
+ &msg->from.auri, &msg->uri.user);
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return;
+ }
+
+ /* handle multiple calls */
+ if (config->call.max_calls &&
+ list_count(&ua->calls) + 1 > config->call.max_calls) {
+
+ info("ua: rejected call from %r (maximum %d calls)\n",
+ &msg->from.auri, config->call.max_calls);
+ (void)sip_treply(NULL, uag.sip, msg, 486, "Max Calls");
+ return;
+ }
+
+ /* Handle Require: header, check for any required extensions */
+ hdr = sip_msg_hdr_apply(msg, true, SIP_HDR_REQUIRE,
+ require_handler, ua);
+ if (hdr) {
+ info("ua: call from %r rejected with 420"
+ " -- option-tag '%r' not supported\n",
+ &msg->from.auri, &hdr->val);
+
+ (void)sip_treplyf(NULL, NULL, uag.sip, msg, false,
+ 420, "Bad Extension",
+ "Unsupported: %r\r\n"
+ "Content-Length: 0\r\n\r\n",
+ &hdr->val);
+ return;
+ }
+
+ (void)pl_strcpy(&msg->to.auri, to_uri, sizeof(to_uri));
+
+ err = ua_call_alloc(&call, ua, VIDMODE_ON, msg, NULL, to_uri, true);
+ if (err) {
+ warning("ua: call_alloc: %m\n", err);
+ goto error;
+ }
+
+ err = call_accept(call, uag.sock, msg);
+ if (err)
+ goto error;
+
+ return;
+
+ error:
+ mem_deref(call);
+ (void)sip_treply(NULL, uag.sip, msg, 500, "Call Error");
+}
+
+
+static void net_change_handler(void *arg)
+{
+ (void)arg;
+
+ info("IP-address changed: %j\n",
+ net_laddr_af(baresip_network(), AF_INET));
+
+ (void)uag_reset_transp(true, true);
+}
+
+
+static bool sub_handler(const struct sip_msg *msg, void *arg)
+{
+ struct ua *ua;
+
+ (void)arg;
+
+ ua = uag_find(&msg->uri.user);
+ if (!ua) {
+ warning("subscribe: no UA found for %r\n", &msg->uri.user);
+ (void)sip_treply(NULL, uag_sip(), msg, 404, "Not Found");
+ return true;
+ }
+
+ if (uag.subh)
+ uag.subh(msg, ua);
+
+ return true;
+}
+
+
+/**
+ * Initialise the User-Agents
+ *
+ * @param software SIP User-Agent string
+ * @param udp Enable UDP transport
+ * @param tcp Enable TCP transport
+ * @param tls Enable TLS transport
+ * @param prefer_ipv6 Prefer IPv6 flag
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_init(const char *software, bool udp, bool tcp, bool tls,
+ bool prefer_ipv6)
+{
+ struct config *cfg = conf_config();
+ struct network *net = baresip_network();
+ uint32_t bsize;
+ int err;
+
+ if (!net) {
+ warning("ua: no network\n");
+ return EINVAL;
+ }
+
+ uag.cfg = &cfg->sip;
+ bsize = cfg->sip.trans_bsize;
+
+ uag.use_udp = udp;
+ uag.use_tcp = tcp;
+ uag.use_tls = tls;
+ uag.prefer_ipv6 = prefer_ipv6;
+
+ list_init(&uag.ual);
+
+ err = sip_alloc(&uag.sip, net_dnsc(net), bsize, bsize, bsize,
+ software, exit_handler, NULL);
+ if (err) {
+ warning("ua: sip stack failed: %m\n", err);
+ goto out;
+ }
+
+ err = ua_add_transp(net);
+ if (err)
+ goto out;
+
+ err = sip_listen(&uag.lsnr, uag.sip, true, request_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipsess_listen(&uag.sock, uag.sip, bsize,
+ sipsess_conn_handler, NULL);
+ if (err)
+ goto out;
+
+ err = sipevent_listen(&uag.evsock, uag.sip, bsize, bsize,
+ sub_handler, NULL);
+ if (err)
+ goto out;
+
+ net_change(net, 60, net_change_handler, NULL);
+
+ out:
+ if (err) {
+ warning("ua: init failed (%m)\n", err);
+ ua_close();
+ }
+ return err;
+}
+
+
+/**
+ * Close all active User-Agents
+ */
+void ua_close(void)
+{
+ uag.evsock = mem_deref(uag.evsock);
+ uag.sock = mem_deref(uag.sock);
+ uag.lsnr = mem_deref(uag.lsnr);
+ uag.sip = mem_deref(uag.sip);
+ uag.eprm = mem_deref(uag.eprm);
+
+#ifdef USE_TLS
+ uag.tls = mem_deref(uag.tls);
+#endif
+
+ list_flush(&uag.ual);
+ list_flush(&uag.ehl);
+
+ /* note: must be done before mod_close() */
+ module_app_unload();
+}
+
+
+/**
+ * Stop all User-Agents
+ *
+ * @param forced True to force, otherwise false
+ */
+void ua_stop_all(bool forced)
+{
+ struct le *le;
+ bool ext_ref = false;
+
+ info("ua: stop all (forced=%d)\n", forced);
+
+ /* check if someone else has grabbed a ref to ua */
+ le = uag.ual.head;
+ while (le) {
+
+ struct ua *ua = le->data;
+ le = le->next;
+
+ if (mem_nrefs(ua) > 1) {
+
+ list_unlink(&ua->le);
+ list_flush(&ua->calls);
+ mem_deref(ua);
+
+ ext_ref = true;
+ }
+
+ ua_event(ua, UA_EVENT_SHUTDOWN, NULL, NULL);
+ }
+
+ if (ext_ref) {
+ info("ua: ext_ref -> cannot unload mods\n");
+ return;
+ }
+ else {
+ module_app_unload();
+ }
+
+ if (!list_isempty(&uag.ual)) {
+ const uint32_t n = list_count(&uag.ual);
+ info("Stopping %u useragent%s.. %s\n",
+ n, n==1 ? "" : "s", forced ? "(Forced)" : "");
+ }
+
+ if (forced)
+ sipsess_close_all(uag.sock);
+ else
+ list_flush(&uag.ual);
+
+ sip_close(uag.sip, forced);
+}
+
+
+/**
+ * Set the global UA exit handler. The exit handler will be called
+ * asyncronously when the SIP stack has exited.
+ *
+ * @param exith Exit handler
+ * @param arg Handler argument
+ */
+void uag_set_exit_handler(ua_exit_h *exith, void *arg)
+{
+ uag.exith = exith;
+ uag.arg = arg;
+}
+
+
+/**
+ * Reset the SIP transports for all User-Agents
+ *
+ * @param reg True to reset registration
+ * @param reinvite True to update active calls
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uag_reset_transp(bool reg, bool reinvite)
+{
+ struct network *net = baresip_network();
+ struct le *le;
+ int err;
+
+ /* Update SIP transports */
+ sip_transp_flush(uag.sip);
+
+ (void)net_check(net);
+ err = ua_add_transp(net);
+ if (err)
+ return err;
+
+ /* Re-REGISTER all User-Agents */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (reg && ua->acc->regint) {
+ err |= ua_register(ua);
+ }
+
+ /* update all active calls */
+ if (reinvite) {
+ struct le *lec;
+
+ for (lec = ua->calls.head; lec; lec = lec->next) {
+ struct call *call = lec->data;
+ const struct sa *laddr;
+
+ laddr = net_laddr_af(net, call_af(call));
+
+ err |= call_reset_transp(call, laddr);
+ }
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Print the SIP Status for all User-Agents
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_print_sip_status(struct re_printf *pf, void *unused)
+{
+ (void)unused;
+ return sip_debug(pf, uag.sip);
+}
+
+
+/**
+ * Print all calls for a given User-Agent
+ *
+ * @param pf Print handler for debug output
+ * @param ua User-Agent
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ua_print_calls(struct re_printf *pf, const struct ua *ua)
+{
+ uint32_t n, count=0;
+ uint32_t linenum;
+ int err = 0;
+
+ if (!ua) {
+ err |= re_hprintf(pf, "\n--- No active calls ---\n");
+ return err;
+ }
+
+ n = list_count(&ua->calls);
+
+ err |= re_hprintf(pf, "\n--- List of active calls (%u): ---\n",
+ n);
+
+ for (linenum=CALL_LINENUM_MIN; linenum<CALL_LINENUM_MAX; linenum++) {
+
+ const struct call *call;
+
+ call = call_find_linenum(&ua->calls, linenum);
+ if (call) {
+ ++count;
+
+ err |= re_hprintf(pf, " %c %H\n",
+ call == ua_call(ua) ? '>' : ' ',
+ call_info, call);
+ }
+
+ if (count >= n)
+ break;
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
+
+
+/**
+ * Get the global SIP Stack
+ *
+ * @return SIP Stack
+ */
+struct sip *uag_sip(void)
+{
+ return uag.sip;
+}
+
+
+/**
+ * Get the global SIP Session socket
+ *
+ * @return SIP Session socket
+ */
+struct sipsess_sock *uag_sipsess_sock(void)
+{
+ return uag.sock;
+}
+
+
+/**
+ * Get the global SIP Event socket
+ *
+ * @return SIP Event socket
+ */
+struct sipevent_sock *uag_sipevent_sock(void)
+{
+ return uag.evsock;
+}
+
+
+struct tls *uag_tls(void)
+{
+#ifdef USE_TLS
+ return uag.tls;
+#else
+ return NULL;
+#endif
+}
+
+
+/**
+ * Find the correct UA from the contact user
+ *
+ * @param cuser Contact username
+ *
+ * @return Matching UA if found, NULL if not found
+ */
+struct ua *uag_find(const struct pl *cuser)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_strcasecmp(cuser, ua->cuser))
+ return ua;
+ }
+
+ /* Try also matching by AOR, for better interop */
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (0 == pl_casecmp(cuser, &ua->acc->luri.user))
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) from an Address-of-Record (AOR)
+ *
+ * @param aor Address-of-Record string
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_aor(const char *aor)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+
+ if (str_isset(aor) && str_cmp(ua->acc->aor, aor))
+ continue;
+
+ return ua;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a User-Agent (UA) which has certain address parameter and/or value
+ *
+ * @param name SIP Address parameter name
+ * @param value SIP Address parameter value (optional)
+ *
+ * @return User-Agent (UA) if found, otherwise NULL
+ */
+struct ua *uag_find_param(const char *name, const char *value)
+{
+ struct le *le;
+
+ for (le = uag.ual.head; le; le = le->next) {
+ struct ua *ua = le->data;
+ struct sip_addr *laddr = account_laddr(ua->acc);
+ struct pl val;
+
+ if (value) {
+
+ if (0 == msg_param_decode(&laddr->params, name, &val)
+ &&
+ 0 == pl_strcasecmp(&val, value)) {
+ return ua;
+ }
+ }
+ else {
+ if (0 == msg_param_exists(&laddr->params, name, &val))
+ return ua;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the contact user/uri of a User-Agent (UA)
+ *
+ * If the Public GRUU is set, it will be returned.
+ * Otherwise the local contact-user (cuser) will be returned.
+ *
+ * @param ua User-Agent
+ *
+ * @return Contact user
+ */
+const char *ua_cuser(const struct ua *ua)
+{
+ if (!ua)
+ return NULL;
+
+ if (str_isset(ua->pub_gruu))
+ return ua->pub_gruu;
+
+ return ua->cuser;
+}
+
+
+const char *ua_local_cuser(const struct ua *ua)
+{
+ return ua ? ua->cuser : NULL;
+}
+
+
+/**
+ * Get Account of a User-Agent
+ *
+ * @param ua User-Agent
+ *
+ * @return Pointer to UA's account
+ */
+struct account *ua_account(const struct ua *ua)
+{
+ return ua ? ua->acc : NULL;
+}
+
+
+/**
+ * Set Public GRUU of a User-Agent (UA)
+ *
+ * @param ua User-Agent
+ * @param pval Public GRUU
+ */
+void ua_pub_gruu_set(struct ua *ua, const struct pl *pval)
+{
+ if (!ua)
+ return;
+
+ ua->pub_gruu = mem_deref(ua->pub_gruu);
+ (void)pl_strdup(&ua->pub_gruu, pval);
+}
+
+
+struct list *uag_list(void)
+{
+ return &uag.ual;
+}
+
+
+/**
+ * Return list of methods supported by the UA
+ *
+ * @return String of supported methods
+ */
+const char *uag_allowed_methods(void)
+{
+ return "INVITE,ACK,BYE,CANCEL,OPTIONS,REFER,"
+ "NOTIFY,SUBSCRIBE,INFO,MESSAGE";
+}
+
+
+int ua_print_supported(struct re_printf *pf, const struct ua *ua)
+{
+ size_t i;
+ int err;
+
+ err = re_hprintf(pf, "Supported:");
+
+ for (i=0; i<ua->extensionc; i++) {
+ err |= re_hprintf(pf, "%s%r",
+ i==0 ? " " : ",", &ua->extensionv[i]);
+ }
+
+ err |= re_hprintf(pf, "\r\n");
+
+ return err;
+}
+
+
+struct list *ua_calls(const struct ua *ua)
+{
+ return ua ? (struct list *)&ua->calls : NULL;
+}
+
+
+static void eh_destructor(void *arg)
+{
+ struct ua_eh *eh = arg;
+ list_unlink(&eh->le);
+}
+
+
+int uag_event_register(ua_event_h *h, void *arg)
+{
+ struct ua_eh *eh;
+
+ if (!h)
+ return EINVAL;
+
+ uag_event_unregister(h);
+
+ eh = mem_zalloc(sizeof(*eh), eh_destructor);
+ if (!eh)
+ return ENOMEM;
+
+ eh->h = h;
+ eh->arg = arg;
+
+ list_append(&uag.ehl, &eh->le, eh);
+
+ return 0;
+}
+
+
+void uag_event_unregister(ua_event_h *h)
+{
+ struct le *le;
+
+ for (le = uag.ehl.head; le; le = le->next) {
+
+ struct ua_eh *eh = le->data;
+
+ if (eh->h == h) {
+ mem_deref(eh);
+ break;
+ }
+ }
+}
+
+
+void uag_set_sub_handler(sip_msg_h *subh)
+{
+ uag.subh = subh;
+}
+
+
+void uag_current_set(struct ua *ua)
+{
+ uag.ua_cur = ua;
+}
+
+
+struct ua *uag_current(void)
+{
+ if (list_isempty(uag_list()))
+ return NULL;
+
+ return uag.ua_cur;
+}
+
+
+void ua_set_media_af(struct ua *ua, int af_media)
+{
+ if (!ua)
+ return;
+
+ ua->af_media = af_media;
+}
+
+
+int uag_set_extra_params(const char *eprm)
+{
+ uag.eprm = mem_deref(uag.eprm);
+
+ if (eprm)
+ return str_dup(&uag.eprm, eprm);
+
+ return 0;
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..40f476b
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,195 @@
+/**
+ * @file ui.c User Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+static int stdout_handler(const char *p, size_t size, void *arg)
+{
+ (void)arg;
+
+ if (1 != fwrite(p, size, 1, stdout))
+ return ENOMEM;
+
+ return 0;
+}
+
+
+/**
+ * Register a new User-Interface (UI) module
+ *
+ * @param uis UI Subsystem
+ * @param ui The User-Interface (UI) module to register
+ */
+void ui_register(struct ui_sub *uis, struct ui *ui)
+{
+ if (!uis || !ui)
+ return;
+
+ list_append(&uis->uil, &ui->le, ui);
+
+ debug("ui: %s\n", ui->name);
+}
+
+
+/**
+ * Un-register a User-Interface (UI) module
+ *
+ * @param ui The User-Interface (UI) module to un-register
+ */
+void ui_unregister(struct ui *ui)
+{
+ if (!ui)
+ return;
+
+ list_unlink(&ui->le);
+}
+
+
+/**
+ * Send an input key to the UI subsystem, with a print function for response
+ *
+ * @param uis UI Subsystem
+ * @param key Input character
+ * @param pf Print function for the response
+ */
+void ui_input_key(struct ui_sub *uis, char key, struct re_printf *pf)
+{
+ if (!uis)
+ return;
+
+ (void)cmd_process(baresip_commands(), &uis->uictx, key, pf, NULL);
+}
+
+
+/**
+ * Send an input string to the UI subsystem
+ *
+ * @param str Input string
+ */
+void ui_input_str(const char *str)
+{
+ struct re_printf pf;
+ struct pl pl;
+
+ if (!str)
+ return;
+
+ pf.vph = stdout_handler;
+ pf.arg = NULL;
+
+ pl_set_str(&pl, str);
+
+ (void)ui_input_pl(&pf, &pl);
+}
+
+
+int ui_input_pl(struct re_printf *pf, const struct pl *pl)
+{
+ struct cmd_ctx *ctx = NULL;
+ struct commands *commands = baresip_commands();
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl)
+ return EINVAL;
+
+ for (i=0; i<pl->l; i++) {
+ err |= cmd_process(commands, &ctx, pl->p[i], pf, NULL);
+ }
+
+ if (pl->l > 1 && ctx)
+ err |= cmd_process(commands, &ctx, '\n', pf, NULL);
+
+ return err;
+}
+
+
+/**
+ * Send output to all modules registered in the UI subsystem
+ *
+ * @param uis UI Subsystem
+ * @param fmt Formatted output string
+ */
+void ui_output(struct ui_sub *uis, const char *fmt, ...)
+{
+ char buf[512];
+ struct le *le;
+ va_list ap;
+ int n;
+
+ if (!uis)
+ return;
+
+ va_start(ap, fmt);
+ n = re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ if (n < 0)
+ return;
+
+ for (le = uis->uil.head; le; le = le->next) {
+ const struct ui *ui = le->data;
+
+ if (ui->outputh)
+ ui->outputh(buf);
+ }
+}
+
+
+/**
+ * Reset the state of the UI subsystem, free resources
+ *
+ * @param uis UI Subsystem
+ */
+void ui_reset(struct ui_sub *uis)
+{
+ if (!uis)
+ return;
+
+ uis->uictx = mem_deref(uis->uictx);
+}
+
+
+bool ui_isediting(const struct ui_sub *uis)
+{
+ if (!uis)
+ return false;
+
+ return uis->uictx != NULL;
+}
+
+
+int ui_password_prompt(char **passwordp)
+{
+ char pwd[64];
+ char *nl;
+ int err;
+
+ if (!passwordp)
+ return EINVAL;
+
+ /* note: blocking UI call */
+ fgets(pwd, sizeof(pwd), stdin);
+ pwd[sizeof(pwd) - 1] = '\0';
+
+ nl = strchr(pwd, '\n');
+ if (nl == NULL) {
+ (void)re_printf("Invalid password (0 - 63 characters"
+ " followed by newline)\n");
+ return EINVAL;
+ }
+
+ *nl = '\0';
+
+ err = str_dup(passwordp, pwd);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/src/vidcodec.c b/src/vidcodec.c
new file mode 100644
index 0000000..cd335ad
--- /dev/null
+++ b/src/vidcodec.c
@@ -0,0 +1,126 @@
+/**
+ * @file vidcodec.c Video Codec
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+
+
+/**
+ * Register a Video Codec
+ *
+ * @param vidcodecl List of video-codecs
+ * @param vc Video Codec
+ */
+void vidcodec_register(struct list *vidcodecl, struct vidcodec *vc)
+{
+ if (!vidcodecl || !vc)
+ return;
+
+ list_append(vidcodecl, &vc->le, vc);
+
+ info("vidcodec: %s\n", vc->name);
+}
+
+
+/**
+ * Unregister a Video Codec
+ *
+ * @param vc Video Codec
+ */
+void vidcodec_unregister(struct vidcodec *vc)
+{
+ if (!vc)
+ return;
+
+ list_unlink(&vc->le);
+}
+
+
+/**
+ * Find a Video Codec by name
+ *
+ * @param vidcodecl List of video-codecs
+ * @param name Name of the Video Codec to find
+ * @param variant Codec Variant
+ *
+ * @return Matching Video Codec if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find(const struct list *vidcodecl,
+ const char *name, const char *variant)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (variant && 0 != str_casecmp(variant, vc->variant))
+ continue;
+
+ return vc;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a Video Encoder by name
+ *
+ * @param vidcodecl List of video-codecs
+ * @param name Name of the Video Encoder to find
+ *
+ * @return Matching Video Encoder if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find_encoder(const struct list *vidcodecl,
+ const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (vc->ench)
+ return vc;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a Video Decoder by name
+ *
+ * @param vidcodecl List of video-codecs
+ * @param name Name of the Video Decoder to find
+ *
+ * @return Matching Video Decoder if found, otherwise NULL
+ */
+const struct vidcodec *vidcodec_find_decoder(const struct list *vidcodecl,
+ const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidcodecl); le; le=le->next) {
+
+ struct vidcodec *vc = le->data;
+
+ if (name && 0 != str_casecmp(name, vc->name))
+ continue;
+
+ if (vc->dech)
+ return vc;
+ }
+
+ return NULL;
+}
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..f191215
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,1421 @@
+/**
+ * @file src/video.c Video stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ *
+ * \ref GenericVideoStream
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Magic number */
+#define MAGIC 0x00070d10
+#include "magic.h"
+
+
+/** Internal video-encoder format */
+#ifndef VIDENC_INTERNAL_FMT
+#define VIDENC_INTERNAL_FMT (VID_FMT_YUV420P)
+#endif
+
+
+enum {
+ SRATE = 90000,
+ MAX_MUTED_FRAMES = 3,
+};
+
+/** Video transmit parameters */
+enum {
+ MEDIA_POLL_RATE = 250, /**< in [Hz] */
+ BURST_MAX = 8192, /**< in bytes */
+ RTP_PRESZ = 4 + RTP_HEADER_SIZE, /**< TURN and RTP header */
+ RTP_TRAILSZ = 12 + 4, /**< SRTP/SRTCP trailer */
+ PICUP_INTERVAL = 500,
+};
+
+
+/**
+ * \page GenericVideoStream Generic Video Stream
+ *
+ * Implements a generic video stream. The application can allocate multiple
+ * instances of a video stream, mapping it to a particular SDP media line.
+ * The video object has a Video Display and Source, and a video encoder
+ * and decoder. A particular video object is mapped to a generic media
+ * stream object.
+ *
+ *<pre>
+ * recv send
+ * | /|\
+ * \|/ |
+ * .---------. .-------.
+ * | video |--->|encoder|
+ * | | |-------|
+ * | object |--->|decoder|
+ * '---------' '-------'
+ * | /|\
+ * | |
+ * \|/ |
+ * .-------. .-------.
+ * |Video | |Video |
+ * |Display| |Source |
+ * '-------' '-------'
+ *</pre>
+ */
+
+/**
+ * Video stream - transmitter/encoder direction
+
+ \verbatim
+
+ Processing encoder pipeline:
+
+ . .--------. .- - - - -. .---------. .---------.
+ | ._O_. | | ! ! | | | |
+ | |___|-->| vidsrc |-->! vidconv !-->| vidfilt |-->| encoder |---> RTP
+ | | | ! ! | | | |
+ ' '--------' '- - - - -' '---------' '---------'
+ (optional)
+ \endverbatim
+ */
+struct vtx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current Video encoder */
+ struct videnc_state *enc; /**< Video encoder state */
+ struct vidsrc_prm vsrc_prm; /**< Video source parameters */
+ struct vidsz vsrc_size; /**< Video source size */
+ struct vidsrc_st *vsrc; /**< Video source */
+ struct lock *lock; /**< Lock for encoder */
+ struct vidframe *frame; /**< Source frame */
+ struct vidframe *mute_frame; /**< Frame with muted video */
+ struct lock *lock_tx; /**< Protect the sendq */
+ struct list sendq; /**< Tx-Queue (struct vidqent) */
+ struct tmr tmr_rtp; /**< Timer for sending RTP */
+ unsigned skipc; /**< Number of frames skipped */
+ struct list filtl; /**< Filters in encoding order */
+ char device[128]; /**< Source device name */
+ int muted_frames; /**< # of muted frames sent */
+ uint32_t ts_offset; /**< Random timestamp offset */
+ bool picup; /**< Send picture update */
+ bool muted; /**< Muted flag */
+ int frames; /**< Number of frames sent */
+ int efps; /**< Estimated frame-rate */
+ uint32_t ts_min;
+ uint32_t ts_max;
+};
+
+
+/**
+ * Video stream - receiver/decoder direction
+
+ \verbatim
+
+ Processing decoder pipeline:
+
+ .~~~~~~~~. .--------. .---------. .---------.
+ | _o_ | | | | | | |
+ | | |<--| vidisp |<--| vidfilt |<--| decoder |<--- RTP
+ | /'\ | | | | | | |
+ '~~~~~~~~' '--------' '---------' '---------'
+
+ \endverbatim
+
+ */
+struct vrx {
+ struct video *video; /**< Parent */
+ const struct vidcodec *vc; /**< Current video decoder */
+ struct viddec_state *dec; /**< Video decoder state */
+ struct vidisp_prm vidisp_prm; /**< Video display parameters */
+ struct vidisp_st *vidisp; /**< Video display */
+ struct lock *lock; /**< Lock for decoder */
+ struct list filtl; /**< Filters in decoding order */
+ struct tmr tmr_picup; /**< Picture update timer */
+ struct vidsz size; /**< Incoming video resolution */
+ enum vidorient orient; /**< Display orientation */
+ char device[128]; /**< Display device name */
+ int pt_rx; /**< Incoming RTP payload type */
+ int frames; /**< Number of frames received */
+ int efps; /**< Estimated frame-rate */
+ unsigned n_intra; /**< Intra-frames decoded */
+ unsigned n_picup; /**< Picture updates sent */
+ uint32_t ts_min;
+ uint32_t ts_max;
+};
+
+
+/** Generic Video stream */
+struct video {
+ MAGIC_DECL /**< Magic number for debugging */
+ struct config_video cfg;/**< Video configuration */
+ struct stream *strm; /**< Generic media stream */
+ struct vtx vtx; /**< Transmit/encoder direction */
+ struct vrx vrx; /**< Receive/decoder direction */
+ struct tmr tmr; /**< Timer for frame-rate estimation */
+ bool started; /**< True if video is started */
+ char *peer; /**< Peer URI */
+ bool nack_pli; /**< Send NACK/PLI to peer */
+ video_err_h *errh; /**< Error handler */
+ void *arg; /**< Error handler argument */
+};
+
+
+struct vidqent {
+ struct le le;
+ struct sa dst;
+ bool marker;
+ uint8_t pt;
+ uint32_t ts;
+ struct mbuf *mb;
+};
+
+
+static void request_picture_update(struct vrx *vrx);
+
+
+static void vidqent_destructor(void *arg)
+{
+ struct vidqent *qent = arg;
+
+ list_unlink(&qent->le);
+ mem_deref(qent->mb);
+}
+
+
+static int vidqent_alloc(struct vidqent **qentp,
+ bool marker, uint8_t pt, uint32_t ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len)
+{
+ struct vidqent *qent;
+ int err = 0;
+
+ if (!qentp || !pld)
+ return EINVAL;
+
+ qent = mem_zalloc(sizeof(*qent), vidqent_destructor);
+ if (!qent)
+ return ENOMEM;
+
+ qent->marker = marker;
+ qent->pt = pt;
+ qent->ts = ts;
+
+ qent->mb = mbuf_alloc(RTP_PRESZ + hdr_len + pld_len + RTP_TRAILSZ);
+ if (!qent->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ qent->mb->pos = qent->mb->end = RTP_PRESZ;
+
+ if (hdr)
+ (void)mbuf_write_mem(qent->mb, hdr, hdr_len);
+
+ (void)mbuf_write_mem(qent->mb, pld, pld_len);
+
+ qent->mb->pos = RTP_PRESZ;
+
+ out:
+ if (err)
+ mem_deref(qent);
+ else
+ *qentp = qent;
+
+ return err;
+}
+
+
+static void vidqueue_poll(struct vtx *vtx, uint64_t jfs, uint64_t prev_jfs)
+{
+ size_t burst, sent;
+ uint64_t bandwidth_kbps;
+ struct le *le;
+
+ if (!vtx)
+ return;
+
+ lock_write_get(vtx->lock_tx);
+
+ le = vtx->sendq.head;
+ if (!le)
+ goto out;
+
+ /*
+ * time [ms] * bitrate [kbps] / 8 = bytes
+ */
+ bandwidth_kbps = vtx->video->cfg.bitrate / 1000;
+ burst = (1 + jfs - prev_jfs) * bandwidth_kbps / 4;
+
+ burst = min(burst, BURST_MAX);
+ sent = 0;
+
+ while (le) {
+
+ struct vidqent *qent = le->data;
+
+ sent += mbuf_get_left(qent->mb);
+
+ stream_send(vtx->video->strm, false, qent->marker, qent->pt,
+ qent->ts, qent->mb);
+
+ le = le->next;
+ mem_deref(qent);
+
+ if (sent > burst) {
+ break;
+ }
+ }
+
+ out:
+ lock_rel(vtx->lock_tx);
+}
+
+
+static void rtp_tmr_handler(void *arg)
+{
+ struct vtx *vtx = arg;
+ uint64_t pjfs;
+
+ pjfs = vtx->tmr_rtp.jfs;
+
+ tmr_start(&vtx->tmr_rtp, 1000/MEDIA_POLL_RATE, rtp_tmr_handler, vtx);
+
+ vidqueue_poll(vtx, vtx->tmr_rtp.jfs, pjfs);
+}
+
+
+static void video_destructor(void *arg)
+{
+ struct video *v = arg;
+ struct vtx *vtx = &v->vtx;
+ struct vrx *vrx = &v->vrx;
+
+ /* transmit */
+ lock_write_get(vtx->lock_tx);
+ list_flush(&vtx->sendq);
+ lock_rel(vtx->lock_tx);
+ mem_deref(vtx->lock_tx);
+
+ tmr_cancel(&vtx->tmr_rtp);
+ mem_deref(vtx->vsrc);
+ lock_write_get(vtx->lock);
+ mem_deref(vtx->frame);
+ mem_deref(vtx->mute_frame);
+ mem_deref(vtx->enc);
+ list_flush(&vtx->filtl);
+ lock_rel(vtx->lock);
+ mem_deref(vtx->lock);
+
+ /* receive */
+ tmr_cancel(&vrx->tmr_picup);
+ lock_write_get(vrx->lock);
+ mem_deref(vrx->dec);
+ mem_deref(vrx->vidisp);
+ list_flush(&vrx->filtl);
+ lock_rel(vrx->lock);
+ mem_deref(vrx->lock);
+
+ tmr_cancel(&v->tmr);
+ mem_deref(v->strm);
+ mem_deref(v->peer);
+}
+
+
+static int get_fps(const struct video *v)
+{
+ const char *attr;
+
+ /* RFC4566 */
+ attr = sdp_media_rattr(stream_sdpmedia(v->strm), "framerate");
+ if (attr) {
+ /* NOTE: fractional values are ignored */
+ const double fps = atof(attr);
+ return (int)fps;
+ }
+ else
+ return v->cfg.fps;
+}
+
+
+static int packet_handler(bool marker, uint32_t ts,
+ const uint8_t *hdr, size_t hdr_len,
+ const uint8_t *pld, size_t pld_len,
+ void *arg)
+{
+ struct vtx *vtx = arg;
+ struct stream *strm = vtx->video->strm;
+ struct vidqent *qent;
+ uint32_t rtp_ts;
+ int err;
+
+ /* NOTE: does not handle timestamp wrap around */
+ if (ts < vtx->ts_min)
+ vtx->ts_min = ts;
+ if (ts > vtx->ts_max)
+ vtx->ts_max = ts;
+
+ /* add random timestamp offset */
+ rtp_ts = vtx->ts_offset + ts;
+
+ err = vidqent_alloc(&qent, marker, strm->pt_enc, rtp_ts,
+ hdr, hdr_len, pld, pld_len);
+ if (err)
+ return err;
+
+ lock_write_get(vtx->lock_tx);
+ qent->dst = *sdp_media_raddr(strm->sdp);
+ list_append(&vtx->sendq, &qent->le, qent);
+ lock_rel(vtx->lock_tx);
+
+ return err;
+}
+
+
+/**
+ * Encode video and send via RTP stream
+ *
+ * @note This function has REAL-TIME properties
+ *
+ * @param vtx Video transmit object
+ * @param frame Video frame to send
+ */
+static void encode_rtp_send(struct vtx *vtx, struct vidframe *frame)
+{
+ struct le *le;
+ int err = 0;
+ bool sendq_empty;
+
+ if (!vtx->enc)
+ return;
+
+ lock_write_get(vtx->lock_tx);
+ sendq_empty = (vtx->sendq.head == NULL);
+ lock_rel(vtx->lock_tx);
+
+ if (!sendq_empty) {
+ ++vtx->skipc;
+ return;
+ }
+
+ lock_write_get(vtx->lock);
+
+ /* Convert image */
+ if (frame->fmt != VIDENC_INTERNAL_FMT) {
+
+ vtx->vsrc_size = frame->size;
+
+ if (!vtx->frame) {
+
+ err = vidframe_alloc(&vtx->frame, VIDENC_INTERNAL_FMT,
+ &vtx->vsrc_size);
+ if (err)
+ goto unlock;
+ }
+
+ vidconv(vtx->frame, frame, 0);
+ frame = vtx->frame;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vtx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf && st->vf->ench)
+ err |= st->vf->ench(st, frame);
+ }
+
+ unlock:
+ lock_rel(vtx->lock);
+
+ if (err)
+ return;
+
+ /* Encode the whole picture frame */
+ err = vtx->vc->ench(vtx->enc, vtx->picup, frame);
+ if (err)
+ return;
+
+ vtx->picup = false;
+}
+
+
+/**
+ * Read frames from video source
+ *
+ * @param frame Video frame
+ * @param arg Handler argument
+ *
+ * @note This function has REAL-TIME properties
+ */
+static void vidsrc_frame_handler(struct vidframe *frame, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ ++vtx->frames;
+
+ /* Is the video muted? If so insert video mute image */
+ if (vtx->muted)
+ frame = vtx->mute_frame;
+
+ if (vtx->muted && vtx->muted_frames >= MAX_MUTED_FRAMES)
+ return;
+
+ /* Encode and send */
+ encode_rtp_send(vtx, frame);
+ vtx->muted_frames++;
+}
+
+
+static void vidsrc_error_handler(int err, void *arg)
+{
+ struct vtx *vtx = arg;
+
+ warning("video: video-source error: %m\n", err);
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+}
+
+
+static int vtx_alloc(struct vtx *vtx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vtx->lock);
+ err |= lock_alloc(&vtx->lock_tx);
+ if (err)
+ return err;
+
+ tmr_init(&vtx->tmr_rtp);
+
+ vtx->video = video;
+
+ /* The initial value of the timestamp SHOULD be random */
+ vtx->ts_offset = rand_u16();
+
+ str_ncpy(vtx->device, video->cfg.src_dev, sizeof(vtx->device));
+
+ tmr_start(&vtx->tmr_rtp, 1, rtp_tmr_handler, vtx);
+
+ vtx->ts_min = ~0;
+
+ return err;
+}
+
+
+static int vrx_alloc(struct vrx *vrx, struct video *video)
+{
+ int err;
+
+ err = lock_alloc(&vrx->lock);
+ if (err)
+ return err;
+
+ vrx->video = video;
+ vrx->pt_rx = -1;
+ vrx->orient = VIDORIENT_PORTRAIT;
+
+ str_ncpy(vrx->device, video->cfg.disp_dev, sizeof(vrx->device));
+
+ vrx->ts_min = ~0;
+
+ return err;
+}
+
+
+static void picup_tmr_handler(void *arg)
+{
+ struct vrx *vrx = arg;
+
+ request_picture_update(vrx);
+}
+
+
+static void request_picture_update(struct vrx *vrx)
+{
+ struct video *v = vrx->video;
+
+ if (tmr_isrunning(&vrx->tmr_picup))
+ return;
+
+ tmr_start(&vrx->tmr_picup, PICUP_INTERVAL, picup_tmr_handler, vrx);
+
+ /* send RTCP FIR to peer */
+ stream_send_fir(v->strm, v->nack_pli);
+
+ /* XXX: if RTCP is not enabled, send XML in SIP INFO ? */
+
+ ++vrx->n_picup;
+}
+
+
+/**
+ * Decode incoming RTP packets using the Video decoder
+ *
+ * NOTE: mb=NULL if no packet received
+ *
+ * @param vrx Video receive object
+ * @param hdr RTP Header
+ * @param mb Buffer with RTP payload
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int video_stream_decode(struct vrx *vrx, const struct rtp_header *hdr,
+ struct mbuf *mb)
+{
+ struct video *v = vrx->video;
+ struct vidframe *frame_filt = NULL;
+ struct vidframe frame_store, *frame = &frame_store;
+ struct le *le;
+ bool intra;
+ int err = 0;
+
+ if (!hdr || !mbuf_get_left(mb))
+ return 0;
+
+ lock_write_get(vrx->lock);
+
+ /* No decoder set */
+ if (!vrx->dec) {
+ warning("video: No video decoder!\n");
+ goto out;
+ }
+
+ /* todo: check if RTP timestamp wraps */
+
+ if (hdr->ts < vrx->ts_min)
+ vrx->ts_min = hdr->ts;
+ if (hdr->ts > vrx->ts_max)
+ vrx->ts_max = hdr->ts;
+
+ frame->data[0] = NULL;
+ err = vrx->vc->dech(vrx->dec, frame, &intra, hdr->m, hdr->seq, mb);
+ if (err) {
+
+ if (err != EPROTO) {
+ warning("video: %s decode error"
+ " (seq=%u, %u bytes): %m\n",
+ vrx->vc->name, hdr->seq,
+ mbuf_get_left(mb), err);
+ }
+
+ request_picture_update(vrx);
+
+ goto out;
+ }
+
+ if (intra) {
+ tmr_cancel(&vrx->tmr_picup);
+ ++vrx->n_intra;
+ }
+
+ /* Got a full picture-frame? */
+ if (!vidframe_isvalid(frame))
+ goto out;
+
+ vrx->size = frame->size;
+
+ if (!list_isempty(&vrx->filtl)) {
+
+ err = vidframe_alloc(&frame_filt, frame->fmt, &frame->size);
+ if (err)
+ goto out;
+
+ vidframe_copy(frame_filt, frame);
+
+ frame = frame_filt;
+ }
+
+ /* Process video frame through all Video Filters */
+ for (le = vrx->filtl.head; le; le = le->next) {
+
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf && st->vf->dech)
+ err |= st->vf->dech(st, frame);
+ }
+
+ err = vidisp_display(vrx->vidisp, v->peer, frame);
+ frame_filt = mem_deref(frame_filt);
+ if (err == ENODEV) {
+ warning("video: video-display was closed\n");
+ vrx->vidisp = mem_deref(vrx->vidisp);
+
+ lock_rel(vrx->lock);
+
+ if (v->errh) {
+ v->errh(err, "display closed", v->arg);
+ }
+
+ return err;
+ }
+
+ ++vrx->frames;
+
+out:
+ lock_rel(vrx->lock);
+
+ return err;
+}
+
+
+static int pt_handler(struct video *v, uint8_t pt_old, uint8_t pt_new)
+{
+ const struct sdp_format *lc;
+
+ lc = sdp_media_lformat(stream_sdpmedia(v->strm), pt_new);
+ if (!lc)
+ return ENOENT;
+
+ if (pt_old != (uint8_t)-1) {
+ info("Video decoder changed payload %u -> %u\n",
+ pt_old, pt_new);
+ }
+
+ v->vrx.pt_rx = pt_new;
+
+ return video_decoder_set(v, lc->data, lc->pt, lc->rparams);
+}
+
+
+/* Handle incoming stream data from the network */
+static void stream_recv_handler(const struct rtp_header *hdr,
+ struct rtpext *extv, size_t extc,
+ struct mbuf *mb, void *arg)
+{
+ struct video *v = arg;
+ int err;
+ (void)extv;
+ (void)extc;
+
+ if (!mb)
+ goto out;
+
+ /* Video payload-type changed? */
+ if (hdr->pt == v->vrx.pt_rx)
+ goto out;
+
+ err = pt_handler(v, v->vrx.pt_rx, hdr->pt);
+ if (err)
+ return;
+
+ out:
+ (void)video_stream_decode(&v->vrx, hdr, mb);
+}
+
+
+static void rtcp_handler(struct rtcp_msg *msg, void *arg)
+{
+ struct video *v = arg;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_FIR:
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_PSFB:
+ if (msg->hdr.count == RTCP_PSFB_PLI)
+ v->vtx.picup = true;
+ break;
+
+ case RTCP_RTPFB:
+ if (msg->hdr.count == RTCP_RTPFB_GNACK)
+ v->vtx.picup = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static int vtx_print_pipeline(struct re_printf *pf, const struct vtx *vtx)
+{
+ struct le *le;
+ struct vidsrc *vs;
+ int err;
+
+ if (!vtx)
+ return 0;
+
+ vs = vidsrc_get(vtx->vsrc);
+
+ err = re_hprintf(pf, "video tx pipeline: %10s",
+ vs ? vs->name : "src");
+
+ for (le = list_head(&vtx->filtl); le; le = le->next) {
+ struct vidfilt_enc_st *st = le->data;
+
+ if (st->vf->ench)
+ err |= re_hprintf(pf, " ---> %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " ---> %s\n",
+ vtx->vc ? vtx->vc->name : "encoder");
+
+ return err;
+}
+
+
+static int vrx_print_pipeline(struct re_printf *pf, const struct vrx *vrx)
+{
+ struct le *le;
+ struct vidisp *vd;
+ int err;
+
+ if (!vrx)
+ return 0;
+
+ vd = vidisp_get(vrx->vidisp);
+
+ err = re_hprintf(pf, "video rx pipeline: %10s",
+ vd ? vd->name : "disp");
+
+ for (le = list_head(&vrx->filtl); le; le = le->next) {
+ struct vidfilt_dec_st *st = le->data;
+
+ if (st->vf->dech)
+ err |= re_hprintf(pf, " <--- %s", st->vf->name);
+ }
+
+ err |= re_hprintf(pf, " <--- %s\n",
+ vrx->vc ? vrx->vc->name : "decoder");
+
+ return err;
+}
+
+
+int video_alloc(struct video **vp, const struct stream_param *stream_prm,
+ const struct config *cfg,
+ struct call *call, struct sdp_session *sdp_sess, int label,
+ const struct mnat *mnat, struct mnat_sess *mnat_sess,
+ const struct menc *menc, struct menc_sess *menc_sess,
+ const char *content, const struct list *vidcodecl,
+ video_err_h *errh, void *arg)
+{
+ struct video *v;
+ struct le *le;
+ int err = 0;
+
+ if (!vp || !cfg)
+ return EINVAL;
+
+ v = mem_zalloc(sizeof(*v), video_destructor);
+ if (!v)
+ return ENOMEM;
+
+ MAGIC_INIT(v);
+
+ v->cfg = cfg->video;
+ tmr_init(&v->tmr);
+
+ err = stream_alloc(&v->strm, stream_prm,
+ &cfg->avt, call, sdp_sess, "video", label,
+ mnat, mnat_sess, menc, menc_sess,
+ call_localuri(call),
+ stream_recv_handler, rtcp_handler, v);
+ if (err)
+ goto out;
+
+ if (cfg->avt.rtp_bw.max >= AUDIO_BANDWIDTH) {
+ stream_set_bw(v->strm, cfg->avt.rtp_bw.max - AUDIO_BANDWIDTH);
+ }
+
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "framerate", "%d", v->cfg.fps);
+
+ /* RFC 4585 */
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "rtcp-fb", "* nack pli");
+
+ /* RFC 4796 */
+ if (content) {
+ err |= sdp_media_set_lattr(stream_sdpmedia(v->strm), true,
+ "content", "%s", content);
+ }
+
+ if (err)
+ goto out;
+
+ v->errh = errh;
+ v->arg = arg;
+
+ err = vtx_alloc(&v->vtx, v);
+ err |= vrx_alloc(&v->vrx, v);
+ if (err)
+ goto out;
+
+ /* Video codecs */
+ for (le = list_head(vidcodecl); le; le = le->next) {
+ struct vidcodec *vc = le->data;
+ err |= sdp_format_add(NULL, stream_sdpmedia(v->strm), false,
+ vc->pt, vc->name, 90000, 1,
+ vc->fmtp_ench, vc->fmtp_cmph, vc, false,
+ "%s", vc->fmtp);
+ }
+
+ /* Video filters */
+ for (le = list_head(baresip_vidfiltl()); le; le = le->next) {
+ struct vidfilt *vf = le->data;
+ void *ctx = NULL;
+
+ err |= vidfilt_enc_append(&v->vtx.filtl, &ctx, vf);
+ err |= vidfilt_dec_append(&v->vrx.filtl, &ctx, vf);
+ if (err) {
+ warning("video: video-filter '%s' failed (%m)\n",
+ vf->name, err);
+ break;
+ }
+ }
+
+ out:
+ if (err)
+ mem_deref(v);
+ else
+ *vp = v;
+
+ return err;
+}
+
+
+static void vidisp_resize_handler(const struct vidsz *sz, void *arg)
+{
+ struct vrx *vrx = arg;
+ (void)vrx;
+
+ info("video: display resized: %u x %u\n", sz->w, sz->h);
+
+ /* XXX: update wanted picturesize and send re-invite to peer */
+}
+
+
+/* Set the video display - can be called multiple times */
+static int set_vidisp(struct vrx *vrx)
+{
+ struct vidisp *vd;
+
+ vrx->vidisp = mem_deref(vrx->vidisp);
+ vrx->vidisp_prm.view = NULL;
+ vrx->vidisp_prm.fullscreen = vrx->video->cfg.fullscreen;
+
+ vd = (struct vidisp *)vidisp_find(baresip_vidispl(),
+ vrx->video->cfg.disp_mod);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(&vrx->vidisp, vd, &vrx->vidisp_prm, vrx->device,
+ vidisp_resize_handler, vrx);
+}
+
+
+/* Set the encoder format - can be called multiple times */
+static int set_encoder_format(struct vtx *vtx, const char *src,
+ const char *dev, struct vidsz *size)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(baresip_vidsrcl(),
+ src);
+ int err;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx->vsrc_size = *size;
+ vtx->vsrc_prm.fps = get_fps(vtx->video);
+ vtx->vsrc_prm.orient = VIDORIENT_PORTRAIT;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ err = vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev, vidsrc_frame_handler,
+ vidsrc_error_handler, vtx);
+ if (err) {
+ info("video: no video source '%s': %m\n", src, err);
+ return err;
+ }
+
+ vtx->mute_frame = mem_deref(vtx->mute_frame);
+ err = vidframe_alloc(&vtx->mute_frame, VIDENC_INTERNAL_FMT, size);
+ if (err)
+ return err;
+
+ vidframe_fill(vtx->mute_frame, 0xff, 0xff, 0xff);
+
+ return err;
+}
+
+
+enum {TMR_INTERVAL = 5};
+static void tmr_handler(void *arg)
+{
+ struct video *v = arg;
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ /* Estimate framerates */
+ v->vtx.efps = v->vtx.frames / TMR_INTERVAL;
+ v->vrx.efps = v->vrx.frames / TMR_INTERVAL;
+
+ v->vtx.frames = 0;
+ v->vrx.frames = 0;
+}
+
+
+int video_start(struct video *v, const char *peer)
+{
+ struct vidsz size;
+ int err;
+
+ if (!v)
+ return EINVAL;
+
+ if (peer) {
+ mem_deref(v->peer);
+ err = str_dup(&v->peer, peer);
+ if (err)
+ return err;
+ }
+
+ stream_set_srate(v->strm, SRATE, SRATE);
+
+ if (vidisp_find(baresip_vidispl(), NULL)) {
+ err = set_vidisp(&v->vrx);
+ if (err) {
+ warning("video: could not set vidisp '%s': %m\n",
+ v->vrx.device, err);
+ }
+ }
+ else {
+ info("video: no video display\n");
+ }
+
+ if (vidsrc_find(baresip_vidsrcl(), NULL)) {
+ size.w = v->cfg.width;
+ size.h = v->cfg.height;
+ err = set_encoder_format(&v->vtx, v->cfg.src_mod,
+ v->vtx.device, &size);
+ if (err) {
+ warning("video: could not set encoder format to"
+ " [%u x %u] %m\n",
+ size.w, size.h, err);
+ }
+ }
+ else {
+ info("video: no video source\n");
+ }
+
+ tmr_start(&v->tmr, TMR_INTERVAL * 1000, tmr_handler, v);
+
+ if (v->vtx.vc && v->vrx.vc) {
+ info("%H%H",
+ vtx_print_pipeline, &v->vtx,
+ vrx_print_pipeline, &v->vrx);
+ }
+
+ v->started = true;
+
+ return 0;
+}
+
+
+void video_stop(struct video *v)
+{
+ if (!v)
+ return;
+
+ debug("video: stopping video source ..\n");
+
+ v->started = false;
+ v->vtx.vsrc = mem_deref(v->vtx.vsrc);
+}
+
+
+bool video_is_started(const struct video *v)
+{
+ return v ? v->started : false;
+}
+
+
+/**
+ * Mute the video stream
+ *
+ * @param v Video stream
+ * @param muted True to mute, false to un-mute
+ */
+void video_mute(struct video *v, bool muted)
+{
+ struct vtx *vtx;
+
+ if (!v)
+ return;
+
+ vtx = &v->vtx;
+
+ vtx->muted = muted;
+ vtx->muted_frames = 0;
+ vtx->picup = true;
+
+ video_update_picture(v);
+}
+
+
+static int vidisp_update(struct vrx *vrx)
+{
+ struct vidisp *vd = vidisp_get(vrx->vidisp);
+ int err = 0;
+
+ if (vd->updateh) {
+ err = vd->updateh(vrx->vidisp, vrx->vidisp_prm.fullscreen,
+ vrx->orient, NULL);
+ }
+
+ return err;
+}
+
+
+/**
+ * Enable video display fullscreen
+ *
+ * @param v Video stream
+ * @param fs True for fullscreen, otherwise false
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_fullscreen(struct video *v, bool fs)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vrx.vidisp_prm.fullscreen = fs;
+
+ return vidisp_update(&v->vrx);
+}
+
+
+static void vidsrc_update(struct vtx *vtx, const char *dev)
+{
+ struct vidsrc *vs = vidsrc_get(vtx->vsrc);
+
+ if (vs && vs->updateh)
+ vs->updateh(vtx->vsrc, &vtx->vsrc_prm, dev);
+}
+
+
+/**
+ * Set the orientation of the Video source and display
+ *
+ * @param v Video stream
+ * @param orient Video orientation (enum vidorient)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int video_set_orient(struct video *v, int orient)
+{
+ if (!v)
+ return EINVAL;
+
+ v->vtx.vsrc_prm.orient = v->vrx.orient = orient;
+ vidsrc_update(&v->vtx, NULL);
+ return vidisp_update(&v->vrx);
+}
+
+
+int video_encoder_set(struct video *v, struct vidcodec *vc,
+ int pt_tx, const char *params)
+{
+ struct vtx *vtx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ vtx = &v->vtx;
+
+ if (!vc->encupdh) {
+ info("video: vidcodec '%s' has no encoder\n", vc->name);
+ return ENOENT;
+ }
+
+ if (vc != vtx->vc) {
+
+ struct videnc_param prm;
+
+ prm.bitrate = v->cfg.bitrate;
+ prm.pktsize = 1024;
+ prm.fps = get_fps(v);
+ prm.max_fs = -1;
+
+ info("Set video encoder: %s %s (%u bit/s, %u fps)\n",
+ vc->name, vc->variant, prm.bitrate, prm.fps);
+
+ vtx->enc = mem_deref(vtx->enc);
+ err = vc->encupdh(&vtx->enc, vc, &prm, params,
+ packet_handler, vtx);
+ if (err) {
+ warning("video: encoder alloc: %m\n", err);
+ return err;
+ }
+
+ vtx->vc = vc;
+ }
+
+ stream_update_encoder(v->strm, pt_tx);
+
+ return err;
+}
+
+
+int video_decoder_set(struct video *v, struct vidcodec *vc, int pt_rx,
+ const char *fmtp)
+{
+ struct vrx *vrx;
+ int err = 0;
+
+ if (!v)
+ return EINVAL;
+
+ /* handle vidcodecs without a decoder */
+ if (!vc->decupdh) {
+ struct list *vidcodecl = baresip_vidcodecl();
+ struct vidcodec *vcd;
+
+ info("video: vidcodec '%s' has no decoder\n", vc->name);
+
+ vcd = (struct vidcodec *)vidcodec_find_decoder(vidcodecl,
+ vc->name);
+ if (!vcd) {
+ warning("video: could not find decoder (%s)\n",
+ vc->name);
+ return ENOENT;
+ }
+
+ vc = vcd;
+ }
+
+ vrx = &v->vrx;
+
+ vrx->pt_rx = pt_rx;
+
+ if (vc != vrx->vc) {
+
+ info("Set video decoder: %s %s\n", vc->name, vc->variant);
+
+ vrx->dec = mem_deref(vrx->dec);
+
+ err = vc->decupdh(&vrx->dec, vc, fmtp);
+ if (err) {
+ warning("video: decoder alloc: %m\n", err);
+ return err;
+ }
+
+ vrx->vc = vc;
+ }
+
+ return err;
+}
+
+
+/**
+ * Use the next video encoder in the local list of negotiated codecs
+ *
+ * @param video Video object
+ */
+void video_encoder_cycle(struct video *video)
+{
+ const struct sdp_format *rc = NULL;
+
+ if (!video)
+ return;
+
+ rc = sdp_media_format_cycle(stream_sdpmedia(video_strm(video)));
+ if (!rc) {
+ info("cycle video: no remote codec found\n");
+ return;
+ }
+
+ (void)video_encoder_set(video, rc->data, rc->pt, rc->params);
+}
+
+
+struct stream *video_strm(const struct video *v)
+{
+ return v ? v->strm : NULL;
+}
+
+
+void video_update_picture(struct video *v)
+{
+ if (!v)
+ return;
+ v->vtx.picup = true;
+}
+
+
+/**
+ * Get the driver-specific view of the video stream
+ *
+ * @param v Video stream
+ *
+ * @return Opaque view
+ */
+void *video_view(const struct video *v)
+{
+ if (!v)
+ return NULL;
+
+ return v->vrx.vidisp_prm.view;
+}
+
+
+/**
+ * Set the current Video Source device name
+ *
+ * @param v Video stream
+ * @param dev Device name
+ */
+void video_vidsrc_set_device(struct video *v, const char *dev)
+{
+ if (!v)
+ return;
+
+ vidsrc_update(&v->vtx, dev);
+}
+
+
+static bool sdprattr_contains(struct stream *s, const char *name,
+ const char *str)
+{
+ const char *attr = sdp_media_rattr(stream_sdpmedia(s), name);
+ return attr ? (NULL != strstr(attr, str)) : false;
+}
+
+
+void video_sdp_attr_decode(struct video *v)
+{
+ if (!v)
+ return;
+
+ /* RFC 4585 */
+ v->nack_pli = sdprattr_contains(v->strm, "rtcp-fb", "nack");
+}
+
+
+int video_debug(struct re_printf *pf, const struct video *v)
+{
+ const struct vtx *vtx;
+ const struct vrx *vrx;
+ int err;
+
+ if (!v)
+ return 0;
+
+ vtx = &v->vtx;
+ vrx = &v->vrx;
+
+ err = re_hprintf(pf, "\n--- Video stream ---\n");
+ err |= re_hprintf(pf, " started: %s\n", v->started ? "yes" : "no");
+
+ err |= re_hprintf(pf, " tx: %u x %u, fps=%d\n",
+ vtx->vsrc_size.w,
+ vtx->vsrc_size.h, vtx->vsrc_prm.fps);
+ err |= re_hprintf(pf, " skipc=%u\n", vtx->skipc);
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ video_calc_seconds(vtx->ts_max - vtx->ts_min));
+
+ err |= re_hprintf(pf, " rx: %u x %u\n", vrx->size.w, vrx->size.h);
+ err |= re_hprintf(pf, " pt=%d\n", vrx->pt_rx);
+
+ err |= re_hprintf(pf, " n_intra=%u, n_picup=%u\n",
+ vrx->n_intra, vrx->n_picup);
+ err |= re_hprintf(pf, " time = %.3f sec\n",
+ video_calc_seconds(vrx->ts_max - vrx->ts_min));
+
+ if (!list_isempty(baresip_vidfiltl())) {
+ err |= vtx_print_pipeline(pf, vtx);
+ err |= vrx_print_pipeline(pf, vrx);
+ }
+
+ err |= stream_debug(pf, v->strm);
+
+ return err;
+}
+
+
+int video_print(struct re_printf *pf, const struct video *v)
+{
+ if (!v)
+ return 0;
+
+ return re_hprintf(pf, " efps=%d/%d", v->vtx.efps, v->vrx.efps);
+}
+
+
+int video_set_source(struct video *v, const char *name, const char *dev)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(baresip_vidsrcl(),
+ name);
+ struct vtx *vtx;
+
+ if (!v)
+ return EINVAL;
+
+ if (!vs)
+ return ENOENT;
+
+ vtx = &v->vtx;
+
+ vtx->vsrc = mem_deref(vtx->vsrc);
+
+ return vs->alloch(&vtx->vsrc, vs, NULL, &vtx->vsrc_prm,
+ &vtx->vsrc_size, NULL, dev,
+ vidsrc_frame_handler, vidsrc_error_handler, vtx);
+}
+
+
+void video_set_devicename(struct video *v, const char *src, const char *disp)
+{
+ if (!v)
+ return;
+
+ str_ncpy(v->vtx.device, src, sizeof(v->vtx.device));
+ str_ncpy(v->vrx.device, disp, sizeof(v->vrx.device));
+}
+
+
+/**
+ * Calculate the RTP timestamp from Presentation Time Stamp (PTS)
+ * or Decoding Time Stamp (DTS) and framerate.
+ *
+ * @note The calculated RTP Timestamp may wrap around.
+ *
+ * @param pts Presentation Time Stamp (PTS)
+ * @param fps Framerate in [frames per second]
+ *
+ * @return RTP Timestamp
+ */
+uint32_t video_calc_rtp_timestamp(int64_t pts, unsigned fps)
+{
+ uint64_t rtp_ts;
+
+ if (!fps)
+ return 0;
+
+ rtp_ts = ((uint64_t)SRATE * pts) / fps;
+
+ return (uint32_t)rtp_ts;
+}
+
+
+double video_calc_seconds(uint32_t rtp_ts)
+{
+ double timestamp;
+
+ /* convert from RTP clockrate to seconds */
+ timestamp = (double)rtp_ts / (double)SRATE;
+
+ return timestamp;
+}
diff --git a/src/vidfilt.c b/src/vidfilt.c
new file mode 100644
index 0000000..c55a1e5
--- /dev/null
+++ b/src/vidfilt.c
@@ -0,0 +1,103 @@
+/**
+ * @file vidfilt.c Video Filter
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/**
+ * Register a new Video Filter
+ *
+ * @param vidfiltl List of Video-Filters
+ * @param vf Video Filter to register
+ */
+void vidfilt_register(struct list *vidfiltl, struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_append(vidfiltl, &vf->le, vf);
+
+ info("vidfilt: %s\n", vf->name);
+}
+
+
+/**
+ * Unregister a Video Filter
+ *
+ * @param vf Video Filter to unregister
+ */
+void vidfilt_unregister(struct vidfilt *vf)
+{
+ if (!vf)
+ return;
+
+ list_unlink(&vf->le);
+}
+
+
+static void vidfilt_enc_destructor(void *arg)
+{
+ struct vidfilt_enc_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_enc_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_enc_st *st = NULL;
+ int err;
+
+ if (vf->encupdh) {
+ err = vf->encupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_enc_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
+
+
+static void vidfilt_dec_destructor(void *arg)
+{
+ struct vidfilt_dec_st *st = arg;
+
+ list_unlink(&st->le);
+}
+
+
+int vidfilt_dec_append(struct list *filtl, void **ctx,
+ const struct vidfilt *vf)
+{
+ struct vidfilt_dec_st *st = NULL;
+ int err;
+
+ if (vf->decupdh) {
+ err = vf->decupdh(&st, ctx, vf);
+ if (err)
+ return err;
+ }
+ else {
+ st = mem_zalloc(sizeof(*st), vidfilt_dec_destructor);
+ if (!st)
+ return ENOMEM;
+ }
+
+ st->vf = vf;
+ list_append(filtl, &st->le, st);
+
+ return 0;
+}
diff --git a/src/vidisp.c b/src/vidisp.c
new file mode 100644
index 0000000..d267f58
--- /dev/null
+++ b/src/vidisp.c
@@ -0,0 +1,132 @@
+/**
+ * @file vidisp.c Video Display
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Display state */
+struct vidisp_st {
+ struct vidisp *vd; /**< Video Display */
+};
+
+
+static void destructor(void *arg)
+{
+ struct vidisp *vd = arg;
+
+ list_unlink(&vd->le);
+}
+
+
+/**
+ * Register a Video output display
+ *
+ * @param vp Pointer to allocated Video Display
+ * @param vidispl List of Video-displays
+ * @param name Name of Video Display
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ * @param disph Display handler
+ * @param hideh Hide-window handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_register(struct vidisp **vp, struct list *vidispl, const char *name,
+ vidisp_alloc_h *alloch, vidisp_update_h *updateh,
+ vidisp_disp_h *disph, vidisp_hide_h *hideh)
+{
+ struct vidisp *vd;
+
+ if (!vp || !vidispl)
+ return EINVAL;
+
+ vd = mem_zalloc(sizeof(*vd), destructor);
+ if (!vd)
+ return ENOMEM;
+
+ list_append(vidispl, &vd->le, vd);
+
+ vd->name = name;
+ vd->alloch = alloch;
+ vd->updateh = updateh;
+ vd->disph = disph;
+ vd->hideh = hideh;
+
+ info("vidisp: %s\n", name);
+
+ *vp = vd;
+ return 0;
+}
+
+
+const struct vidisp *vidisp_find(const struct list *vidispl, const char *name)
+{
+ struct le *le;
+
+ for (le = list_head(vidispl); le; le = le->next) {
+ struct vidisp *vd = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vd->name))
+ continue;
+
+ /* Found */
+ return vd;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a video display state
+ *
+ * @param stp Pointer to allocated display state
+ * @param vidispl List of Video-displays
+ * @param name Name of video display
+ * @param prm Video display parameters (optional)
+ * @param dev Display device
+ * @param resizeh Window resize handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_alloc(struct vidisp_st **stp, struct list *vidispl,
+ const char *name,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp *vd = (struct vidisp *)vidisp_find(vidispl, name);
+ if (!vd)
+ return ENOENT;
+
+ return vd->alloch(stp, vd, prm, dev, resizeh, arg);
+}
+
+
+/**
+ * Display a video frame
+ *
+ * @param st Video display state
+ * @param title Display title
+ * @param frame Video frame
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ if (!st || !frame)
+ return EINVAL;
+
+ return st->vd->disph(st, title, frame);
+}
+
+
+struct vidisp *vidisp_get(struct vidisp_st *st)
+{
+ return st ? st->vd : NULL;
+}
diff --git a/src/vidsrc.c b/src/vidsrc.c
new file mode 100644
index 0000000..2c8f9ff
--- /dev/null
+++ b/src/vidsrc.c
@@ -0,0 +1,125 @@
+/**
+ * @file vidsrc.c Video Source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "core.h"
+
+
+/** Video Source state */
+struct vidsrc_st {
+ struct vidsrc *vs; /**< Video Source */
+};
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc *vs = arg;
+
+ list_unlink(&vs->le);
+}
+
+
+/**
+ * Register a Video Source
+ *
+ * @param vsp Pointer to allocated Video Source
+ * @param vidsrcl List of Video Sources
+ * @param name Name of Video Source
+ * @param alloch Allocation handler
+ * @param updateh Update handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_register(struct vidsrc **vsp, struct list *vidsrcl,
+ const char *name,
+ vidsrc_alloc_h *alloch, vidsrc_update_h *updateh)
+{
+ struct vidsrc *vs;
+
+ if (!vsp || !vidsrcl)
+ return EINVAL;
+
+ vs = mem_zalloc(sizeof(*vs), destructor);
+ if (!vs)
+ return ENOMEM;
+
+ list_append(vidsrcl, &vs->le, vs);
+
+ vs->name = name;
+ vs->alloch = alloch;
+ vs->updateh = updateh;
+
+ info("vidsrc: %s\n", name);
+
+ *vsp = vs;
+
+ return 0;
+}
+
+
+/**
+ * Find a Video Source by name
+ *
+ * @param vidsrcl List of Video Sources
+ * @param name Name of the Video Source to find
+ *
+ * @return Matching Video Source if found, otherwise NULL
+ */
+const struct vidsrc *vidsrc_find(const struct list *vidsrcl, const char *name)
+{
+ struct le *le;
+
+ for (le=list_head(vidsrcl); le; le=le->next) {
+
+ struct vidsrc *vs = le->data;
+
+ if (str_isset(name) && 0 != str_casecmp(name, vs->name))
+ continue;
+
+ return vs;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Allocate a new video source state
+ *
+ * @param stp Pointer to allocated state
+ * @param vidsrcl List of Video Sources
+ * @param name Name of the video source
+ * @param ctx Optional media context
+ * @param prm Video source parameters
+ * @param size Wanted video size of the source
+ * @param fmt Format parameter
+ * @param dev Video device
+ * @param frameh Video frame handler
+ * @param errorh Error handler (optional)
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int vidsrc_alloc(struct vidsrc_st **stp, struct list *vidsrcl,
+ const char *name,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt, const char *dev,
+ vidsrc_frame_h *frameh, vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc *vs = (struct vidsrc *)vidsrc_find(vidsrcl, name);
+ if (!vs)
+ return ENOENT;
+
+ return vs->alloch(stp, vs, ctx, prm, size, fmt, dev,
+ frameh, errorh, arg);
+}
+
+
+struct vidsrc *vidsrc_get(struct vidsrc_st *st)
+{
+ return st ? st->vs : NULL;
+}
diff --git a/test/account.c b/test/account.c
new file mode 100644
index 0000000..bf03b03
--- /dev/null
+++ b/test/account.c
@@ -0,0 +1,69 @@
+/**
+ * @file test/account.c Tests for account
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define DEBUG_MODULE "account"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static const char str[] =
+ "\"Mr User\" <sip:user:pass@domain.com>"
+ ";answermode=auto"
+ ";auth_user=xuser"
+ ";outbound=\"sip:edge.domain.com\""
+ ";ptime=10"
+ ";regint=600"
+ ";pubint=700"
+ ";sipnat=outbound"
+ ";stunuser=bob@bob.com"
+ ";stunpass=taj:aa"
+ ";stunserver=\"stun:stunserver.org\""
+ ;
+
+
+int test_account(void)
+{
+ struct account *acc = NULL;
+ struct sip_addr *addr;
+ int err = 0;
+
+ err = account_alloc(&acc, str);
+ TEST_ERR(err);
+ ASSERT_TRUE(acc != NULL);
+
+ /* verify the decoded SIP aor */
+ addr = account_laddr(acc);
+ ASSERT_TRUE(addr != NULL);
+ TEST_STRCMP("Mr User", 7, addr->dname.p, addr->dname.l);
+ TEST_STRCMP("sip", 3, addr->uri.scheme.p, addr->uri.scheme.l);
+ TEST_STRCMP("user", 4, addr->uri.user.p, addr->uri.user.l);
+ TEST_STRCMP("pass", 4, addr->uri.password.p, addr->uri.password.l);
+ TEST_STRCMP("domain.com", 10, addr->uri.host.p, addr->uri.host.l);
+ ASSERT_EQ(0, addr->uri.params.l);
+ ASSERT_TRUE(addr->params.l > 0);
+
+ /* verify all decoded parameters */
+ ASSERT_TRUE(ANSWERMODE_AUTO == account_answermode(acc));
+ ASSERT_STREQ("xuser", account_auth_user(acc));
+ ASSERT_STREQ("sip:edge.domain.com", account_outbound(acc, 0));
+ ASSERT_TRUE(NULL == account_outbound(acc, 1));
+ ASSERT_TRUE(NULL == account_outbound(acc, 333));
+ ASSERT_EQ(10, account_ptime(acc));
+ ASSERT_EQ(600, account_regint(acc));
+ ASSERT_EQ(700, account_pubint(acc));
+ ASSERT_STREQ("bob@bob.com", account_stun_user(acc));
+ ASSERT_STREQ("taj:aa", account_stun_pass(acc));
+ ASSERT_STREQ("stunserver.org", account_stun_host(acc));
+
+ out:
+ mem_deref(acc);
+ return err;
+}
diff --git a/test/aulevel.c b/test/aulevel.c
new file mode 100644
index 0000000..6d083f6
--- /dev/null
+++ b/test/aulevel.c
@@ -0,0 +1,61 @@
+/**
+ * @file test/aulevel.c Baresip selftest -- audio levels
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define DEBUG_MODULE "aulevel"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#define PREC .6
+
+
+int test_aulevel(void)
+{
+ static const struct {
+ int16_t sampv[2];
+ double level;
+ } testv[] = {
+
+ { { 0, -0}, -96.0 },
+ { { 0, 1}, -93.0 },
+ { { 1, -1}, -90.0 },
+ { { 2, -2}, -84.0 },
+ { { 4, -4}, -78.0 },
+ { { 8, -8}, -72.0 },
+ { { 16, -16}, -66.0 },
+ { { 32, -32}, -60.0 },
+ { { 64, -64}, -54.0 },
+ { { 128, -128}, -48.0 },
+ { { 256, -256}, -42.0 },
+ { { 512, -512}, -36.0 },
+ { { 1024, -1024}, -30.0 },
+ { { 2048, -2048}, -24.0 },
+ { { 4096, -4096}, -18.0 },
+ { { 8192, -8192}, -12.0 },
+ { {16384, -16384}, -6.0 },
+ { {32767, -32768}, 0.0 },
+ };
+ size_t i;
+ int err = 0;
+
+ for (i=0; i<ARRAY_SIZE(testv); i++) {
+
+ double level;
+
+ level = aulevel_calc_dbov(testv[i].sampv,
+ ARRAY_SIZE(testv[i].sampv));
+
+ ASSERT_DOUBLE_EQ(testv[i].level, level, PREC);
+ }
+
+ out:
+ return err;
+}
diff --git a/test/call.c b/test/call.c
new file mode 100644
index 0000000..34c5fd5
--- /dev/null
+++ b/test/call.c
@@ -0,0 +1,906 @@
+/**
+ * @file test/call.c Baresip selftest -- call
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define MAGIC 0x7004ca11
+
+
+enum behaviour {
+ BEHAVIOUR_ANSWER = 0,
+ BEHAVIOUR_PROGRESS,
+ BEHAVIOUR_REJECT
+};
+
+enum action {
+ ACTION_RECANCEL = 0,
+ ACTION_HANGUP_A,
+ ACTION_HANGUP_B,
+ ACTION_NOTHING
+};
+
+struct agent {
+ struct fixture *fix; /* pointer to parent */
+ struct agent *peer;
+ struct ua *ua;
+ uint16_t close_scode;
+ bool failed;
+
+ unsigned n_incoming;
+ unsigned n_progress;
+ unsigned n_established;
+ unsigned n_closed;
+ unsigned n_dtmf_recv;
+};
+
+struct fixture {
+ uint32_t magic;
+ struct agent a, b;
+ struct sa laddr_sip;
+ enum behaviour behaviour;
+ enum action estab_action;
+ char buri[256];
+ int err;
+ unsigned exp_estab;
+ unsigned exp_closed;
+};
+
+
+#define fixture_init_prm(f, prm) \
+ memset(f, 0, sizeof(*f)); \
+ \
+ f->a.fix = f; \
+ f->b.fix = f; \
+ \
+ err = ua_init("test", true, true, true, false); \
+ TEST_ERR(err); \
+ \
+ f->magic = MAGIC; \
+ f->exp_estab = 1; \
+ f->exp_closed = 1; \
+ mock_aucodec_register(); \
+ \
+ err = ua_alloc(&f->a.ua, \
+ "A <sip:a@127.0.0.1>;regint=0" prm); \
+ TEST_ERR(err); \
+ err = ua_alloc(&f->b.ua, \
+ "B <sip:b@127.0.0.1>;regint=0" prm); \
+ TEST_ERR(err); \
+ \
+ f->a.peer = &f->b; \
+ f->b.peer = &f->a; \
+ \
+ err = uag_event_register(event_handler, f); \
+ TEST_ERR(err); \
+ \
+ err = sip_transp_laddr(uag_sip(), &f->laddr_sip, \
+ SIP_TRANSP_UDP, NULL); \
+ TEST_ERR(err); \
+ \
+ re_snprintf(f->buri, sizeof(f->buri), "sip:b@%J", &f->laddr_sip);
+
+
+#define fixture_init(f) \
+ fixture_init_prm((f), "");
+
+
+#define fixture_close(f) \
+ mem_deref(f->b.ua); \
+ mem_deref(f->a.ua); \
+ \
+ mock_aucodec_unregister(); \
+ \
+ uag_event_unregister(event_handler); \
+ \
+ ua_stop_all(true); \
+ ua_close();
+
+#define fixture_abort(f, error) \
+ do { \
+ (f)->err = (error); \
+ re_cancel(); \
+ } while (0)
+
+
+static void event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ struct fixture *f = arg;
+ struct agent *ag;
+ int err = 0;
+ (void)prm;
+
+#if 1
+ info("test: [ %s ] event: %s (%s)\n",
+ ua_aor(ua), uag_event_str(ev), prm);
+#endif
+
+ ASSERT_TRUE(f != NULL);
+ ASSERT_EQ(MAGIC, f->magic);
+
+ if (ua == f->a.ua)
+ ag = &f->a;
+ else if (ua == f->b.ua)
+ ag = &f->b;
+ else {
+ return;
+ }
+
+ switch (ev) {
+
+ case UA_EVENT_CALL_INCOMING:
+ ++ag->n_incoming;
+
+ switch (f->behaviour) {
+
+ case BEHAVIOUR_ANSWER:
+ err = ua_answer(ua, call);
+ if (err) {
+ warning("ua_answer failed (%m)\n", err);
+ goto out;
+ }
+ break;
+
+ case BEHAVIOUR_PROGRESS:
+ err = ua_progress(ua, call);
+ if (err) {
+ warning("ua_progress failed (%m)\n", err);
+ goto out;
+ }
+ break;
+
+ case BEHAVIOUR_REJECT:
+ ua_hangup(ua, call, 0, 0);
+ call = NULL;
+ ag->failed = true;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case UA_EVENT_CALL_PROGRESS:
+ ++ag->n_progress;
+
+ re_cancel();
+ break;
+
+ case UA_EVENT_CALL_ESTABLISHED:
+ ++ag->n_established;
+
+ /* are both agents established? */
+ if (ag->n_established >= f->exp_estab &&
+ ag->peer->n_established >= f->exp_estab) {
+
+ switch (f->estab_action) {
+
+ case ACTION_RECANCEL:
+ re_cancel();
+ break;
+
+ case ACTION_HANGUP_A:
+ f->a.failed = true;
+ ua_hangup(f->a.ua, NULL, 0, 0);
+ break;
+
+ case ACTION_HANGUP_B:
+ f->b.failed = true;
+ ua_hangup(f->b.ua, NULL, 0, 0);
+ break;
+
+ case ACTION_NOTHING:
+ /* Do nothing, wait */
+ break;
+ }
+ }
+ break;
+
+ case UA_EVENT_CALL_CLOSED:
+ ++ag->n_closed;
+
+ ag->close_scode = call_scode(call);
+
+ if (ag->close_scode)
+ ag->failed = true;
+
+ if (ag->n_closed >= f->exp_closed &&
+ ag->peer->n_closed >= f->exp_closed) {
+
+ re_cancel();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (ag->failed && ag->peer->failed) {
+ info("test: re_cancel on call failed\n");
+ re_cancel();
+ return;
+ }
+
+ out:
+ if (err) {
+ warning("error in event-handler (%m)\n", err);
+ f->err = err;
+ re_cancel();
+ }
+}
+
+
+int test_call_answer(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(0, fix.a.n_closed);
+ ASSERT_EQ(0, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_incoming);
+ ASSERT_EQ(1, fix.b.n_established);
+ ASSERT_EQ(0, fix.b.n_closed);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_reject(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_REJECT;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(0, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+
+ ASSERT_EQ(1, fix.b.n_incoming);
+ ASSERT_EQ(0, fix.b.n_established);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_af_mismatch(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ ua_set_media_af(f->a.ua, AF_INET6);
+ ua_set_media_af(f->b.ua, AF_INET);
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(0, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+ ASSERT_EQ(488, fix.a.close_scode);
+
+ ASSERT_EQ(0, fix.b.n_incoming);
+ ASSERT_EQ(0, fix.b.n_established);
+ ASSERT_EQ(1, fix.b.n_closed);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_answer_hangup_a(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+ f->estab_action = ACTION_HANGUP_A;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+ ASSERT_EQ(0, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_established);
+ ASSERT_EQ(1, fix.b.n_closed);
+ ASSERT_EQ(0, fix.b.close_scode);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_answer_hangup_b(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+ f->estab_action = ACTION_HANGUP_B;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+ ASSERT_EQ(0, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_established);
+ ASSERT_EQ(1, fix.b.n_closed);
+ ASSERT_EQ(0, fix.b.close_scode);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_rtp_timeout(void)
+{
+#define RTP_TIMEOUT_MS 1
+ struct fixture fix, *f = &fix;
+ struct call *call;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+ f->estab_action = ACTION_NOTHING;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ call = ua_call(f->a.ua);
+ ASSERT_TRUE(call != NULL);
+
+ call_enable_rtp_timeout(call, RTP_TIMEOUT_MS);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+ ASSERT_EQ(701, fix.a.close_scode); /* verify timeout */
+
+ ASSERT_EQ(1, fix.b.n_established);
+ ASSERT_EQ(1, fix.b.n_closed);
+ ASSERT_EQ(0, fix.b.close_scode);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+/* veriy that line-numbers are in sequence */
+static bool linenum_are_sequential(const struct ua *ua)
+{
+ uint32_t linenum = 0;
+ struct le *le;
+
+ for (le = list_head(ua_calls(ua)) ; le ; le = le->next) {
+ struct call *call = le->data;
+
+ if (call_linenum(call) <= linenum)
+ return false;
+
+ linenum = call_linenum(call);
+ }
+
+ return true;
+}
+
+
+int test_call_multiple(void)
+{
+ struct fixture fix, *f = &fix;
+ struct le *le;
+ unsigned i;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+ f->exp_estab = 4;
+
+ /*
+ * Step 1 -- make 4 calls from A to B
+ */
+ for (i=0; i<4; i++) {
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+ }
+
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(4, fix.a.n_established);
+ ASSERT_EQ(0, fix.a.n_closed);
+
+ ASSERT_EQ(4, fix.b.n_incoming);
+ ASSERT_EQ(4, fix.b.n_established);
+ ASSERT_EQ(0, fix.b.n_closed);
+
+ ASSERT_EQ(4, list_count(ua_calls(f->a.ua)));
+ ASSERT_EQ(4, list_count(ua_calls(f->b.ua)));
+ ASSERT_TRUE(linenum_are_sequential(f->a.ua));
+ ASSERT_TRUE(linenum_are_sequential(f->b.ua));
+
+
+ /*
+ * Step 2 -- hangup calls with even line-number
+ */
+
+ f->exp_closed = 2;
+
+ le = list_head(ua_calls(f->a.ua));
+ while (le) {
+ struct call *call = le->data;
+ le = le->next;
+
+ if (!(call_linenum(call) % 2)) {
+ ua_hangup(f->a.ua, call, 0, 0);
+ }
+ }
+
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(2, list_count(ua_calls(f->a.ua)));
+ ASSERT_EQ(2, list_count(ua_calls(f->b.ua)));
+ ASSERT_TRUE(linenum_are_sequential(f->a.ua));
+ ASSERT_TRUE(linenum_are_sequential(f->b.ua));
+
+
+ /*
+ * Step 3 -- make 2 calls from A to B
+ */
+
+ f->a.n_established = 0;
+ f->b.n_established = 0;
+ f->exp_estab = 2;
+ for (i=0; i<2; i++) {
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+ }
+
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(4, list_count(ua_calls(f->a.ua)));
+ ASSERT_EQ(4, list_count(ua_calls(f->b.ua)));
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+int test_call_max(void)
+{
+ struct fixture fix, *f = &fix;
+ unsigned i;
+ int err = 0;
+
+ /* Set the max-calls limit */
+ conf_config()->call.max_calls = 1;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+
+ /* Make 2 calls, one should work and one should fail */
+ for (i=0; i<2; i++) {
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+ }
+
+ f->b.failed = true; /* tiny hack to stop the runloop */
+
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(1, fix.a.n_closed);
+ ASSERT_EQ(486, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_incoming);
+ ASSERT_EQ(0, fix.b.n_closed);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+static const char dtmf_digits[] = "123";
+
+
+static void dtmf_handler(struct call *call, char key, void *arg)
+{
+ struct agent *ag = arg;
+ int err = 0;
+ (void)call;
+
+ /* ignore key-release */
+ if (key == KEYCODE_REL)
+ return;
+
+ ASSERT_EQ(dtmf_digits[ag->n_dtmf_recv], key);
+ ++ag->n_dtmf_recv;
+
+ if (ag->n_dtmf_recv >= str_len(dtmf_digits)) {
+ re_cancel();
+ }
+
+ out:
+ if (err) {
+ fixture_abort(ag->fix, err);
+ }
+}
+
+
+int test_call_dtmf(void)
+{
+ struct fixture fix, *f = &fix;
+ struct ausrc *ausrc = NULL;
+ size_t i, n = str_len(dtmf_digits);
+ int err = 0;
+
+ /* Use a low packet time, so the test completes quickly */
+ fixture_init_prm(f, ";ptime=1");
+
+ /* audio-source is needed for dtmf/telev to work */
+ err = mock_ausrc_register(&ausrc);
+ TEST_ERR(err);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ call_set_handlers(ua_call(f->a.ua), NULL, dtmf_handler, &f->a);
+ call_set_handlers(ua_call(f->b.ua), NULL, dtmf_handler, &f->b);
+
+ /* send some DTMF digits from A to B .. */
+ for (i=0; i<n; i++) {
+ err = call_send_digit(ua_call(f->a.ua), dtmf_digits[i]);
+ TEST_ERR(err);
+ }
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_dtmf_recv);
+ ASSERT_EQ(n, fix.b.n_dtmf_recv);
+
+ out:
+ fixture_close(f);
+ mem_deref(ausrc);
+
+ return err;
+}
+
+
+#ifdef USE_VIDEO
+int test_call_video(void)
+{
+ struct fixture fix, *f = &fix;
+ struct vidsrc *vidsrc = NULL;
+ struct vidisp *vidisp = NULL;
+ int err = 0;
+
+ conf_config()->video.fps = 100;
+
+ fixture_init(f);
+
+ /* to enable video, we need one vidsrc and vidcodec */
+ mock_vidcodec_register();
+ err = mock_vidsrc_register(&vidsrc);
+ TEST_ERR(err);
+ err = mock_vidisp_register(&vidisp);
+ TEST_ERR(err);
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+ f->estab_action = ACTION_NOTHING;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_ON);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(10000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ /* verify that video was enabled for this call */
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(1, fix.b.n_established);
+
+ ASSERT_TRUE(call_has_video(ua_call(f->a.ua)));
+ ASSERT_TRUE(call_has_video(ua_call(f->b.ua)));
+
+ out:
+ fixture_close(f);
+ mem_deref(vidisp);
+ mem_deref(vidsrc);
+ mock_vidcodec_unregister();
+
+ return err;
+}
+#endif
+
+
+static void mock_sample_handler(const void *sampv, size_t sampc, void *arg)
+{
+ struct fixture *fix = arg;
+ bool got_aulevel;
+ (void)sampv;
+ (void)sampc;
+
+ got_aulevel =
+ 0 == audio_level_get(call_audio(ua_call(fix->a.ua)), NULL) &&
+ 0 == audio_level_get(call_audio(ua_call(fix->b.ua)), NULL);
+
+ if (got_aulevel)
+ re_cancel();
+}
+
+
+int test_call_aulevel(void)
+{
+ struct fixture fix, *f = &fix;
+ struct ausrc *ausrc = NULL;
+ struct auplay *auplay = NULL;
+ double lvl;
+ int err = 0;
+
+ /* Use a low packet time, so the test completes quickly */
+ fixture_init_prm(f, ";ptime=1");
+
+ conf_config()->audio.level = true;
+
+ err = mock_ausrc_register(&ausrc);
+ TEST_ERR(err);
+ err = mock_auplay_register(&auplay, mock_sample_handler, f);
+ TEST_ERR(err);
+
+ f->estab_action = ACTION_NOTHING;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ /* verify audio silence */
+ err = audio_level_get(call_audio(ua_call(f->a.ua)), &lvl);
+ TEST_ERR(err);
+ ASSERT_EQ(-96, lvl);
+ err = audio_level_get(call_audio(ua_call(f->b.ua)), &lvl);
+ TEST_ERR(err);
+ ASSERT_EQ(-96, lvl);
+
+ out:
+ conf_config()->audio.level = false;
+
+ fixture_close(f);
+ mem_deref(auplay);
+ mem_deref(ausrc);
+
+ return err;
+}
+
+
+int test_call_progress(void)
+{
+ struct fixture fix, *f = &fix;
+ int err = 0;
+
+ fixture_init(f);
+
+ f->behaviour = BEHAVIOUR_PROGRESS;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(1, fix.a.n_progress);
+ ASSERT_EQ(0, fix.a.n_established);
+ ASSERT_EQ(0, fix.a.n_closed);
+ ASSERT_EQ(0, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_incoming);
+ ASSERT_EQ(0, fix.b.n_progress);
+ ASSERT_EQ(0, fix.b.n_established);
+ ASSERT_EQ(0, fix.b.n_closed);
+
+ out:
+ fixture_close(f);
+
+ return err;
+}
+
+
+static void float_sample_handler(const void *sampv, size_t sampc, void *arg)
+{
+ struct fixture *fix = arg;
+ (void)sampv;
+ (void)sampc;
+
+ if (sampc && fix->a.n_established && fix->b.n_established)
+ re_cancel();
+}
+
+
+static int test_media_base(enum audio_mode txmode)
+{
+ struct fixture fix, *f = &fix;
+ struct ausrc *ausrc = NULL;
+ struct auplay *auplay = NULL;
+ int err = 0;
+
+ fixture_init(f);
+
+ conf_config()->audio.txmode = txmode;
+
+ conf_config()->audio.src_fmt = AUFMT_FLOAT;
+ conf_config()->audio.play_fmt = AUFMT_FLOAT;
+
+ err = mock_ausrc_register(&ausrc);
+ TEST_ERR(err);
+ err = mock_auplay_register(&auplay, float_sample_handler, f);
+ TEST_ERR(err);
+
+ f->estab_action = ACTION_NOTHING;
+
+ f->behaviour = BEHAVIOUR_ANSWER;
+
+ /* Make a call from A to B */
+ err = ua_connect(f->a.ua, 0, NULL, f->buri, NULL, VIDMODE_OFF);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ TEST_ERR(err);
+ TEST_ERR(fix.err);
+
+ ASSERT_EQ(0, fix.a.n_incoming);
+ ASSERT_EQ(1, fix.a.n_established);
+ ASSERT_EQ(0, fix.a.n_closed);
+ ASSERT_EQ(0, fix.a.close_scode);
+
+ ASSERT_EQ(1, fix.b.n_incoming);
+ ASSERT_EQ(1, fix.b.n_established);
+ ASSERT_EQ(0, fix.b.n_closed);
+
+ out:
+ conf_config()->audio.src_fmt = AUFMT_S16LE;
+ conf_config()->audio.play_fmt = AUFMT_S16LE;
+
+ fixture_close(f);
+ mem_deref(auplay);
+ mem_deref(ausrc);
+
+ if (fix.err)
+ return fix.err;
+
+ return err;
+}
+
+
+int test_call_format_float(void)
+{
+ int err;
+
+ err = test_media_base(AUDIO_MODE_POLL);
+ ASSERT_EQ(0, err);
+
+ err = test_media_base(AUDIO_MODE_THREAD);
+ ASSERT_EQ(0, err);
+
+ conf_config()->audio.txmode = AUDIO_MODE_POLL;
+
+ out:
+ return err;
+}
diff --git a/test/cmd.c b/test/cmd.c
new file mode 100644
index 0000000..2df09ec
--- /dev/null
+++ b/test/cmd.c
@@ -0,0 +1,161 @@
+/**
+ * @file test/cmd.c Baresip selftest -- cmd
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+struct test {
+ unsigned cmd_called;
+};
+
+static int cmd_test(struct re_printf *pf, void *arg)
+{
+ struct cmd_arg *carg = arg;
+ struct test *test = carg->data;
+ int err = 0;
+ (void)pf;
+
+ ASSERT_EQ('@', carg->key);
+ ASSERT_TRUE(NULL == carg->prm);
+ ASSERT_EQ(true, carg->complete);
+
+ ++test->cmd_called;
+
+ out:
+ return err;
+}
+
+
+static const struct cmd cmdv[] = {
+ {NULL, '@', 0, "Test command", cmd_test},
+};
+
+
+static int vprintf_null(const char *p, size_t size, void *arg)
+{
+ (void)p;
+ (void)size;
+ (void)arg;
+ return 0;
+}
+
+
+static struct re_printf pf_null = {vprintf_null, 0};
+
+
+int test_cmd(void)
+{
+ struct commands *commands = NULL;
+ struct cmd_ctx *ctx = 0;
+ struct test test;
+ int err = 0;
+
+ memset(&test, 0, sizeof(test));
+
+ err = cmd_init(&commands);
+ ASSERT_EQ(0, err);
+
+ err = cmd_register(commands, cmdv, ARRAY_SIZE(cmdv));
+ ASSERT_EQ(0, err);
+
+ /* it is not possible to register same block twice */
+ ASSERT_EQ(EALREADY, cmd_register(commands, cmdv, ARRAY_SIZE(cmdv)));
+
+ /* issue a different command */
+ err = cmd_process(commands, &ctx, 'h', &pf_null, &test);
+ ASSERT_EQ(0, err);
+ ASSERT_EQ(0, test.cmd_called);
+
+ /* issue our command, expect handler to be called */
+ err = cmd_process(commands, &ctx, '@', &pf_null, &test);
+ ASSERT_EQ(0, err);
+ ASSERT_EQ(1, test.cmd_called);
+
+ cmd_unregister(commands, cmdv);
+
+ /* verify that context was not created */
+ ASSERT_TRUE(NULL == ctx);
+
+ out:
+ mem_deref(commands);
+ return err;
+}
+
+
+static int long_handler(struct re_printf *pf, void *arg)
+{
+ struct cmd_arg *carg = arg;
+ struct test *test = carg->data;
+ int err = 0;
+ (void)pf;
+
+ ASSERT_STREQ("123", carg->prm);
+
+ ++test->cmd_called;
+
+ out:
+ return err;
+}
+
+
+static const struct cmd longcmdv[] = {
+ { "test", 0, 0, "Test Command", long_handler},
+};
+
+
+int test_cmd_long(void)
+{
+ struct commands *commands = NULL;
+ struct test test;
+ const struct cmd *cmd;
+ static const char *input_str = "/test 123\n";
+ struct cmd_ctx *ctx = NULL;
+ size_t i;
+ int err;
+
+ memset(&test, 0, sizeof(test));
+
+ err = cmd_init(&commands);
+ ASSERT_EQ(0, err);
+
+ /* Verify that the command does not exist */
+ cmd = cmd_find_long(commands, "test");
+ ASSERT_TRUE(cmd == NULL);
+
+ /* Register and verify command */
+ err = cmd_register(commands, longcmdv, ARRAY_SIZE(longcmdv));
+ ASSERT_EQ(0, err);
+
+ cmd = cmd_find_long(commands, "test");
+ ASSERT_TRUE(cmd != NULL);
+
+ /* Feed it some input data .. */
+
+ for (i=0; i<strlen(input_str); i++) {
+
+ err = cmd_process(commands, &ctx, input_str[i],
+ &pf_null, &test);
+ ASSERT_EQ(0, err);
+ }
+
+ err = cmd_process_long(commands, "test 123", 8, &pf_null, &test);
+ ASSERT_EQ(0, err);
+
+ ASSERT_EQ(2, test.cmd_called);
+
+ /* Cleanup .. */
+
+ cmd_unregister(commands, longcmdv);
+
+ cmd = cmd_find_long(commands, "test");
+ ASSERT_TRUE(cmd == NULL);
+
+ out:
+ mem_deref(commands);
+ return err;
+}
diff --git a/test/contact.c b/test/contact.c
new file mode 100644
index 0000000..5b357ff
--- /dev/null
+++ b/test/contact.c
@@ -0,0 +1,55 @@
+/**
+ * @file test/contact.c Baresip selftest -- contacts
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+int test_contact(void)
+{
+ struct contacts contacts;
+ struct contact *c;
+ const char *addr = "sip:neil@young.com";
+ struct pl pl_addr;
+ int err;
+
+ err = contact_init(&contacts);
+ ASSERT_EQ(0, err);
+
+ /* Verify that we have no contacts */
+
+ ASSERT_EQ(0, list_count(contact_list(&contacts)));
+
+ c = contact_find(&contacts, "sip:null@void.com");
+ ASSERT_TRUE(c == NULL);
+
+ /* Add one contact, list should have one entry and
+ find should return the added contact */
+
+ pl_set_str(&pl_addr, addr);
+ err = contact_add(&contacts, &c, &pl_addr);
+ ASSERT_EQ(0, err);
+ ASSERT_TRUE(c != NULL);
+
+ ASSERT_EQ(1, list_count(contact_list(&contacts)));
+
+ c = contact_find(&contacts, addr);
+ ASSERT_TRUE(c != NULL);
+
+ ASSERT_STREQ(addr, contact_str(c));
+
+ /* Delete 1 contact, verify that list is empty */
+
+ mem_deref(c);
+
+ ASSERT_EQ(0, list_count(contact_list(&contacts)));
+
+ out:
+ contact_close(&contacts);
+
+ return err;
+}
diff --git a/test/cplusplus.cpp b/test/cplusplus.cpp
new file mode 100644
index 0000000..e6ec89f
--- /dev/null
+++ b/test/cplusplus.cpp
@@ -0,0 +1,22 @@
+/**
+ * @file test/cplusplus.cpp Baresip selftest -- C++ compatibility
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "test.h"
+
+
+int test_cplusplus(void)
+{
+ const char *version = sys_libre_version_get();
+ int err = 0;
+
+ ASSERT_TRUE(str_isset(version));
+ info("c++ ok\n");
+
+out:
+ return err;
+}
diff --git a/test/main.c b/test/main.c
new file mode 100644
index 0000000..e917f2d
--- /dev/null
+++ b/test/main.c
@@ -0,0 +1,271 @@
+/**
+ * @file test/main.c Selftest for Baresip core
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef SOLARIS
+#define __EXTENSIONS__ 1
+#endif
+#include <getopt.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+typedef int (test_exec_h)(void);
+
+struct test {
+ test_exec_h *exec;
+ const char *name;
+};
+
+#define TEST(a) {a, #a}
+
+static const struct test tests[] = {
+ TEST(test_account),
+ TEST(test_aulevel),
+ TEST(test_call_af_mismatch),
+ TEST(test_call_answer),
+ TEST(test_call_answer_hangup_a),
+ TEST(test_call_answer_hangup_b),
+ TEST(test_call_reject),
+ TEST(test_call_rtp_timeout),
+ TEST(test_call_multiple),
+ TEST(test_call_max),
+ TEST(test_call_dtmf),
+ TEST(test_call_aulevel),
+ TEST(test_call_progress),
+ TEST(test_call_format_float),
+#ifdef USE_VIDEO
+ TEST(test_call_video),
+ TEST(test_video),
+#endif
+ TEST(test_cmd),
+ TEST(test_cmd_long),
+ TEST(test_contact),
+ TEST(test_cplusplus),
+ TEST(test_message),
+ TEST(test_mos),
+ TEST(test_network),
+ TEST(test_play),
+ TEST(test_ua_alloc),
+ TEST(test_ua_options),
+ TEST(test_ua_register),
+ TEST(test_ua_register_dns),
+ TEST(test_ua_register_auth),
+ TEST(test_ua_register_auth_dns),
+ TEST(test_uag_find_param),
+};
+
+
+static int run_one_test(const struct test *test)
+{
+ int err;
+
+ re_printf("[ RUN ] %s\n", test->name);
+
+ err = test->exec();
+ if (err) {
+ warning("%s: test failed (%m)\n",
+ test->name, err);
+ return err;
+ }
+
+ re_printf("[ OK ]\n");
+
+ return 0;
+}
+
+
+static int run_tests(void)
+{
+ size_t i;
+ int err;
+
+ for (i=0; i<ARRAY_SIZE(tests); i++) {
+
+ re_printf("[ RUN ] %s\n", tests[i].name);
+
+ err = tests[i].exec();
+ if (err) {
+ warning("%s: test failed (%m)\n",
+ tests[i].name, err);
+ return err;
+ }
+
+ re_printf("[ OK ]\n");
+ }
+
+ return 0;
+}
+
+
+static void test_listcases(void)
+{
+ size_t i, n;
+
+ n = ARRAY_SIZE(tests);
+
+ (void)re_printf("\n%zu test cases:\n", n);
+
+ for (i=0; i<(n+1)/2; i++) {
+
+ (void)re_printf(" %-32s %s\n",
+ tests[i].name,
+ (i+(n+1)/2) < n ? tests[i+(n+1)/2].name : "");
+ }
+
+ (void)re_printf("\n");
+}
+
+
+static const struct test *find_test(const char *name)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(tests); i++) {
+
+ if (0 == str_casecmp(name, tests[i].name))
+ return &tests[i];
+ }
+
+ return NULL;
+}
+
+
+static void ua_exit_handler(void *arg)
+{
+ (void)arg;
+
+ debug("ua exited -- stopping main runloop\n");
+ re_cancel();
+}
+
+
+static void usage(void)
+{
+ (void)re_fprintf(stderr,
+ "Usage: selftest [options] <testcases..>\n"
+ "options:\n"
+ "\t-l List all testcases and exit\n"
+ "\t-v Verbose output (INFO level)\n"
+ );
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct config *config;
+ size_t i, ntests;
+ bool verbose = false;
+ int err;
+
+ err = libre_init();
+ if (err)
+ return err;
+
+ log_enable_info(false);
+
+ for (;;) {
+ const int c = getopt(argc, argv, "hlv");
+ if (0 > c)
+ break;
+
+ switch (c) {
+
+ case '?':
+ case 'h':
+ usage();
+ return -2;
+
+ case 'l':
+ test_listcases();
+ return 0;
+
+ case 'v':
+ if (verbose)
+ log_enable_debug(true);
+ else
+ log_enable_info(true);
+ verbose = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (argc >= (optind + 1))
+ ntests = argc - optind;
+ else
+ ntests = ARRAY_SIZE(tests);
+
+ re_printf("running baresip selftest version %s with %zu tests\n",
+ BARESIP_VERSION, ntests);
+
+ /* note: run SIP-traffic on localhost */
+ config = conf_config();
+ if (!config) {
+ err = ENOENT;
+ goto out;
+ }
+
+ err = baresip_init(config, false);
+ if (err)
+ goto out;
+
+ str_ncpy(config->sip.local, "127.0.0.1:0", sizeof(config->sip.local));
+
+ uag_set_exit_handler(ua_exit_handler, NULL);
+
+ if (argc >= (optind + 1)) {
+
+ for (i=0; i<ntests; i++) {
+ const char *name = argv[optind + i];
+ const struct test *test;
+
+ test = find_test(name);
+ if (test) {
+ err = run_one_test(test);
+ if (err)
+ goto out;
+ }
+ else {
+ re_fprintf(stderr,
+ "testcase not found: `%s'\n",
+ name);
+ err = ENOENT;
+ goto out;
+ }
+ }
+ }
+ else {
+ err = run_tests();
+ if (err)
+ goto out;
+ }
+
+#if 1
+ ua_stop_all(true);
+#endif
+
+ re_printf("\x1b[32mOK. %zu tests passed successfully\x1b[;m\n",
+ ntests);
+
+ out:
+ if (err) {
+ warning("test failed (%m)\n", err);
+ re_printf("%H\n", re_debug, 0);
+ }
+ ua_stop_all(true);
+ ua_close();
+
+ baresip_close();
+
+ libre_close();
+
+ tmr_debug();
+ mem_debug();
+
+ return err;
+}
diff --git a/test/message.c b/test/message.c
new file mode 100644
index 0000000..ddad3ce
--- /dev/null
+++ b/test/message.c
@@ -0,0 +1,232 @@
+/**
+ * @file test/message.c Baresip selftest -- message sending
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+struct test {
+ enum sip_transp transp;
+ int err;
+};
+
+struct endpoint {
+ struct test *test;
+ struct endpoint *other;
+ struct message *message;
+ struct ua *ua;
+ char uri[256];
+ unsigned n_msg;
+ unsigned n_resp;
+};
+
+
+static const char dummy_msg[] = "hei paa deg";
+static const char text_plain[] = "text/plain";
+
+
+static bool endpoint_is_complete(const struct endpoint *ep)
+{
+ return ep->n_msg >= 1 || ep->n_resp >= 1;
+}
+
+
+static bool test_is_complete(struct endpoint *ep)
+{
+ return endpoint_is_complete(ep) &&
+ endpoint_is_complete(ep->other);
+}
+
+
+static void message_recv_handler(const struct pl *peer, const struct pl *ctype,
+ struct mbuf *body, void *arg)
+{
+ struct endpoint *ep = arg;
+ int err = 0;
+
+ info("[ %s ] recv msg from %r: \"%b\"\n", ep->uri, peer,
+ mbuf_buf(body), mbuf_get_left(body));
+
+ TEST_STRCMP(text_plain, strlen(text_plain),
+ ctype->p, ctype->l);
+
+ TEST_STRCMP(dummy_msg, str_len(dummy_msg),
+ mbuf_buf(body), mbuf_get_left(body));
+
+ ++ep->n_msg;
+
+ if (test_is_complete(ep)) {
+ re_cancel();
+ return;
+ }
+
+ out:
+ if (err) {
+ ep->test->err = err;
+ re_cancel();
+ }
+}
+
+
+static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct endpoint *ep = arg;
+
+ ++ep->n_resp;
+
+ if (err) {
+ warning("sending failed: %m\n", err);
+ goto out;
+ }
+
+ if (msg->scode >= 300) {
+ warning("sending failed: %u %r\n", msg->scode, &msg->reason);
+ err = EPROTO;
+ goto out;
+ }
+
+ info("[ %s ] message sent OK\n", ep->uri);
+
+ ASSERT_EQ(ep->test->transp, msg->tp);
+ ASSERT_EQ(200, msg->scode);
+
+ if (test_is_complete(ep)) {
+ re_cancel();
+ return;
+ }
+
+ out:
+ if (err) {
+ ep->test->err = err;
+ re_cancel();
+ }
+}
+
+
+static void endpoint_destructor(void *data)
+{
+ struct endpoint *ep = data;
+
+ mem_deref(ep->message);
+ mem_deref(ep->ua);
+}
+
+
+static int endpoint_alloc(struct endpoint **epp, struct test *test,
+ const char *name, enum sip_transp transp)
+{
+ struct endpoint *ep = NULL;
+ struct sa laddr;
+ char aor[256];
+ int err = 0;
+
+ err = sip_transp_laddr(uag_sip(), &laddr, transp, NULL);
+ TEST_ERR(err);
+
+ ep = mem_zalloc(sizeof(*ep), endpoint_destructor);
+ if (!ep)
+ return ENOMEM;
+
+ ep->test = test;
+
+ if (re_snprintf(aor, sizeof(aor),
+ "%s <sip:%s@127.0.0.1;transport=%s>;regint=0",
+ name, name,
+ sip_transp_name(transp)) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (re_snprintf(ep->uri, sizeof(ep->uri), "sip:%s@%J;transport=%s",
+ name,
+ &laddr, sip_transp_name(transp)) < 0) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = ua_alloc(&ep->ua, aor);
+ if (err)
+ goto out;
+
+ err = message_init(&ep->message);
+ TEST_ERR(err);
+
+ out:
+ if (err)
+ mem_deref(ep);
+ else
+ *epp = ep;
+
+ return err;
+}
+
+
+static int test_message_transp(enum sip_transp transp)
+{
+ struct test test;
+ struct endpoint *a = NULL, *b = NULL;
+ bool enable_udp, enable_tcp;
+ int err = 0;
+
+ enable_udp = transp == SIP_TRANSP_UDP;
+ enable_tcp = transp == SIP_TRANSP_TCP;
+
+ memset(&test, 0, sizeof(test));
+
+ test.transp = transp;
+
+ err = ua_init("test", enable_udp, enable_tcp, false, false);
+ TEST_ERR(err);
+
+ err = endpoint_alloc(&a, &test, "a", transp);
+ TEST_ERR(err);
+
+ err = endpoint_alloc(&b, &test, "b", transp);
+ TEST_ERR(err);
+
+ a->other = b;
+ b->other = a;
+
+ /* NOTE: can only listen to one global instance for now */
+ err = message_listen(NULL, b->message, message_recv_handler, b);
+ TEST_ERR(err);
+
+ /* Send a message from A to B */
+ err = message_send(a->ua, b->uri, dummy_msg, send_resp_handler, a);
+ TEST_ERR(err);
+
+ err = re_main_timeout(1000);
+ TEST_ERR(err);
+
+ TEST_ERR(test.err);
+ ASSERT_EQ(0, a->n_msg);
+ ASSERT_EQ(1, a->n_resp);
+ ASSERT_EQ(1, b->n_msg);
+ ASSERT_EQ(0, b->n_resp);
+
+ out:
+ mem_deref(b);
+ mem_deref(a);
+ ua_close();
+
+ return err;
+}
+
+
+int test_message(void)
+{
+ int err = 0;
+
+ err = test_message_transp(SIP_TRANSP_UDP);
+ TEST_ERR(err);
+
+ err |= test_message_transp(SIP_TRANSP_TCP);
+ TEST_ERR(err);
+
+ out:
+ return err;
+}
diff --git a/test/mock/cert.c b/test/mock/cert.c
new file mode 100644
index 0000000..a711082
--- /dev/null
+++ b/test/mock/cert.c
@@ -0,0 +1,82 @@
+/**
+ * @file cert.c TLS Certificate
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include "../test.h"
+
+
+/**
+ * Dummy certificate for testing.
+ *
+ *
+ * It was generated like this:
+ *
+ * $ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
+ * -days 36500 -nodes
+ *
+ *
+ * Dumping information:
+ *
+ * $ openssl x509 -subject -dates -fingerprint -in cert.pem
+ * subject= /C=NO/ST=Retest/O=Retest AS/CN=Mr Retest/emailAddress=re@test.org
+ * notBefore=Nov 23 18:40:38 2014 GMT
+ * notAfter=Oct 30 18:40:38 2114 GMT
+ * Fingerprint=49:A4:E9:F4:80:3A:D4:38:84:F1:64:C3:B9:4B:F9:BB:80:F7:07:76
+ */
+
+const char test_certificate[] =
+
+"-----BEGIN CERTIFICATE-----\r\n"
+"MIIDmTCCAoGgAwIBAgIJAIt1/MAlTpB7MA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV\r\n"
+"BAYTAk5PMQ8wDQYDVQQIDAZSZXRlc3QxEjAQBgNVBAoMCVJldGVzdCBBUzESMBAG\r\n"
+"A1UEAwwJTXIgUmV0ZXN0MRowGAYJKoZIhvcNAQkBFgtyZUB0ZXN0Lm9yZzAgFw0x\r\n"
+"NDExMjMxODQwMzhaGA8yMTE0MTAzMDE4NDAzOFowYjELMAkGA1UEBhMCTk8xDzAN\r\n"
+"BgNVBAgMBlJldGVzdDESMBAGA1UECgwJUmV0ZXN0IEFTMRIwEAYDVQQDDAlNciBS\r\n"
+"ZXRlc3QxGjAYBgkqhkiG9w0BCQEWC3JlQHRlc3Qub3JnMIIBIjANBgkqhkiG9w0B\r\n"
+"AQEFAAOCAQ8AMIIBCgKCAQEAqnX4j6WK6tcN/X+C8C9ZSSlhVT2OdPB/lAPa3T3w\r\n"
+"eB3wu2C9gnZcCSvekBhyFKSi4w0Az5HNjJWWQqpSeYW2MCEKI97DIu0hg/5qn2le\r\n"
+"2sDjo4u/SNdH0CQHaLD2Xu0hhvZ/dTIulxpy5hLVmxs8/UZ8QKZ3vxDFE92p4LBL\r\n"
+"tLYz6+TvWovmUqYL97J+2muXUMcZCCTbk8DQSGLBbsawXejVF8RgPiFHCvefybUQ\r\n"
+"JCbtTDTfMykVnMEv3yMmfcXG/mwG8CLDRv7y8wh632aDdWfKN+g70gH0CFdjn070\r\n"
+"eIZyJ3TyRi4b55RSC4FAdP2YKToOUH55N86wrbprnHb8swIDAQABo1AwTjAdBgNV\r\n"
+"HQ4EFgQUcaZeVPUmMPFvKwYzn27b8BUJV3AwHwYDVR0jBBgwFoAUcaZeVPUmMPFv\r\n"
+"KwYzn27b8BUJV3AwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAbsMt\r\n"
+"zruNpBUZv08vdoWN9QWaJrmv8fvcx/RcuVLRuAaLYExEUJnoz3+dNFbR38BvncVC\r\n"
+"LlDcSIK06JIHX6E7gJegWQdECoO/YgQGCwoIoQJtNCybxtZccb5uAGY/+qO3uOx0\r\n"
+"Vx1NxrAMh5cpOIhZ8XiSYA2+JB71prW97diSQS+cU9xWCJxPU7UqQ10nV7PbfSmp\r\n"
+"XTnj+togZPXYzJmSQR4RoM4Vqu27syo7xYQ90twoRKpRYTPdDTArpkTn6KuUuCJ1\r\n"
+"t2v9LOkkxOvF11rLY6rLf0BG4XYkhnz4CLLt428wvAPykPcs95Q9TwpF/nKEwyfh\r\n"
+"J+cC3FZTiBf/YmPPaA==\r\n"
+"-----END CERTIFICATE-----\r\n"
+
+"-----BEGIN PRIVATE KEY-----\r\n"
+"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCqdfiPpYrq1w39\r\n"
+"f4LwL1lJKWFVPY508H+UA9rdPfB4HfC7YL2CdlwJK96QGHIUpKLjDQDPkc2MlZZC\r\n"
+"qlJ5hbYwIQoj3sMi7SGD/mqfaV7awOOji79I10fQJAdosPZe7SGG9n91Mi6XGnLm\r\n"
+"EtWbGzz9RnxApne/EMUT3angsEu0tjPr5O9ai+ZSpgv3sn7aa5dQxxkIJNuTwNBI\r\n"
+"YsFuxrBd6NUXxGA+IUcK95/JtRAkJu1MNN8zKRWcwS/fIyZ9xcb+bAbwIsNG/vLz\r\n"
+"CHrfZoN1Z8o36DvSAfQIV2OfTvR4hnIndPJGLhvnlFILgUB0/ZgpOg5Qfnk3zrCt\r\n"
+"umucdvyzAgMBAAECggEAC1xxhKFz8NMEi7DD+V4uhUHMyvGfXQvqdOMM41INhPP5\r\n"
+"54M7HkblO3dBDjmS4O1YLenf8/WzzXrq2OahOJhA3FRXaKygNOO5KCL82EMdn1bb\r\n"
+"1TqrNR+kGatNEx04TntfkK89L4J4uHl6zvrSYdQe7IKWJXjy4jkr6XcMq30Ujqa6\r\n"
+"Val03Cr40VL0ZSYXnwTf/P65GtQTdyTOemYkUbMH9qRoxHmE7ZsPRpbXU4k30JWh\r\n"
+"6VYy+7h5XmjrX+VdIiia6sMjy0mbtsxJb6DOo19ro4DfF5JmFZpXnBhsJGhMxiEM\r\n"
+"94QEC5Tv6b0hWomFpOm3I5jOnktavCFQ1NsNHUspgQKBgQDUQm2+F06YdC6Pgt+9\r\n"
+"COnd9rz2lB2TjGRIJvis6MW7EfQ7XHkH9y/sDGLzINBVn6DkaHmQZDrIg77Mey+H\r\n"
+"LG0QL6+7WK7c1X/Zga6LKvkLlmcMWq6i1uu+Q3UODu7XcFh6f8kDThP+BubfUWpw\r\n"
+"rCRq74gF1JzQISoKqmyW/AXMZQKBgQDNln0jzgh/ySWRjJbuh6GqPQunDQsh3K3I\r\n"
+"4WDHK40NHFgIzPomKO+pqsOu3V/X81NARqfUyoIp/455YRheLHBUJg7maLfxrBq/\r\n"
+"qQPEUwSAI6lheMz1WNWri65GvwBlVENajVuMh/xfmKVL1KVV+LXGO5L1UClWITCM\r\n"
+"VSC0QT8XNwKBgQC7bYUWS+JdAIp0su36MDrCgzPM0HFlbpzGkZMYq9qeG3Z8TGWb\r\n"
+"QQyR9UYSxjDwyqn5xr9BXyABG0SJr2UCiZosps8YMXEHE4d3eumzfdi4ALEx2YlH\r\n"
+"xVwZf9uG9Gy21D9svBW102YX8+Q94diJcZgezTBhZaKqrf4/uMl2cUh1eQKBgQDB\r\n"
+"heZYXOqdN0A5CUlOUbg5YutkHaAcCPpBvP33niRRchvgdOsIHsKzSL6ZDWPaCP+V\r\n"
+"4qy7XsE2PYzk7yQcCeLXI1glRe/Y+3PWdIfKN4dmA6u+yBLO5QeFSqALkmISAEbC\r\n"
+"p4vE9oD3j94RSqM0EUEy0ANfDk1K+UUU5FE7vKth8wKBgQCoKvPNfC3FmyhLK7EK\r\n"
+"zgfIYAcoi52EFu6xQ9PvZHg60ptYvq1L+H6LR8cxYfcrPTt3aDbFf41RahPkokNh\r\n"
+"2HDxiHd4HwWkAGiqZXCTA2znb+rnJ9fheI6g/Wb3p+oGeCFHMcdUcsl1qWBFDtax\r\n"
+"ygS/tEFgy1z2dVMLbLKqEUscsA==\r\n"
+"-----END PRIVATE KEY-----\r\n"
+;
diff --git a/test/mock/dnssrv.c b/test/mock/dnssrv.c
new file mode 100644
index 0000000..e5ac188
--- /dev/null
+++ b/test/mock/dnssrv.c
@@ -0,0 +1,245 @@
+/**
+ * @file mock/dnssrv.c Mock DNS server
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include "../test.h"
+
+
+#define DEBUG_MODULE "mock/dnssrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#define LOCAL_PORT 0
+
+
+static void dns_server_match(struct dns_server *srv, struct list *rrl,
+ const char *name, uint16_t type)
+{
+ struct dnsrr *rr0 = NULL;
+ struct le *le;
+
+ le = srv->rrl.head;
+ while (le) {
+
+ struct dnsrr *rr = le->data;
+ le = le->next;
+
+ if (type == rr->type && 0==str_casecmp(name, rr->name)) {
+
+ if (!rr0)
+ rr0 = rr;
+ list_append(rrl, &rr->le_priv, rr);
+ }
+ }
+
+ /* If rotation is enabled, then rotate multiple entries
+ in a deterministic way (no randomness please) */
+ if (srv->rotate && rr0) {
+ list_unlink(&rr0->le);
+ list_append(&srv->rrl, &rr0->le, rr0);
+ }
+}
+
+
+static void decode_dns_query(struct dns_server *srv,
+ const struct sa *src, struct mbuf *mb)
+{
+ struct list rrl = LIST_INIT;
+ struct dnshdr hdr;
+ struct le *le;
+ char *qname = NULL;
+ size_t start, end;
+ uint16_t type, dnsclass;
+ int err = 0;
+
+ start = mb->pos;
+ end = mb->end;
+
+ if (dns_hdr_decode(mb, &hdr) || hdr.qr || hdr.nq != 1) {
+ DEBUG_WARNING("unable to decode query header\n");
+ return;
+ }
+
+ err = dns_dname_decode(mb, &qname, start);
+ if (err) {
+ DEBUG_WARNING("unable to decode query name\n");
+ goto out;
+ }
+
+ if (mbuf_get_left(mb) < 4) {
+ err = EBADMSG;
+ DEBUG_WARNING("unable to decode query type/class\n");
+ goto out;
+ }
+
+ type = ntohs(mbuf_read_u16(mb));
+ dnsclass = ntohs(mbuf_read_u16(mb));
+
+ DEBUG_INFO("dnssrv: type=%s query-name='%s'\n",
+ dns_rr_typename(type), qname);
+
+ if (dnsclass == DNS_CLASS_IN) {
+ dns_server_match(srv, &rrl, qname, type);
+ }
+
+ hdr.qr = true;
+ hdr.tc = false;
+ hdr.rcode = DNS_RCODE_OK;
+ hdr.nq = 1;
+ hdr.nans = list_count(&rrl);
+
+ mb->pos = start;
+
+ err = dns_hdr_encode(mb, &hdr);
+ if (err)
+ goto out;
+
+ mb->pos = end;
+
+ DEBUG_INFO("dnssrv: @@ found %u answers for %s\n",
+ list_count(&rrl), qname);
+
+ for (le = rrl.head; le; le = le->next) {
+ struct dnsrr *rr = le->data;
+
+ err = dns_rr_encode(mb, rr, 0, NULL, start);
+ if (err)
+ goto out;
+ }
+
+ mb->pos = start;
+
+ (void)udp_send(srv->us, src, mb);
+
+ out:
+ list_clear(&rrl);
+ mem_deref(qname);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct dns_server *srv = arg;
+
+ decode_dns_query(srv, src, mb);
+}
+
+
+static void destructor(void *arg)
+{
+ struct dns_server *srv = arg;
+
+ list_flush(&srv->rrl);
+ mem_deref(srv->us);
+}
+
+
+int dns_server_alloc(struct dns_server **srvp, bool rotate)
+{
+ struct dns_server *srv;
+ int err;
+
+ if (!srvp)
+ return EINVAL;
+
+ srv = mem_zalloc(sizeof(*srv), destructor);
+ if (!srv)
+ return ENOMEM;
+
+ err = sa_set_str(&srv->addr, "127.0.0.1", LOCAL_PORT);
+ if (err)
+ goto out;
+
+ err = udp_listen(&srv->us, &srv->addr, udp_recv, srv);
+ if (err)
+ goto out;
+
+ err = udp_local_get(srv->us, &srv->addr);
+ if (err)
+ goto out;
+
+ srv->rotate = rotate;
+
+ out:
+ if (err)
+ mem_deref(srv);
+ else
+ *srvp = srv;
+
+ return err;
+}
+
+
+int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr)
+{
+ struct dnsrr *rr;
+ int err;
+
+ if (!srv || !name)
+ return EINVAL;
+
+ rr = dns_rr_alloc();
+ if (!rr)
+ return ENOMEM;
+
+ err = str_dup(&rr->name, name);
+ if (err)
+ goto out;
+
+ rr->type = DNS_TYPE_A;
+ rr->dnsclass = DNS_CLASS_IN;
+ rr->ttl = 3600;
+ rr->rdlen = 0;
+
+ rr->rdata.a.addr = addr;
+
+ list_append(&srv->rrl, &rr->le, rr);
+
+ out:
+ if (err)
+ mem_deref(rr);
+
+ return err;
+}
+
+
+int dns_server_add_srv(struct dns_server *srv, const char *name,
+ uint16_t pri, uint16_t weight, uint16_t port,
+ const char *target)
+{
+ struct dnsrr *rr;
+ int err;
+
+ if (!srv || !name || !port || !target)
+ return EINVAL;
+
+ rr = dns_rr_alloc();
+ if (!rr)
+ return ENOMEM;
+
+ err = str_dup(&rr->name, name);
+ if (err)
+ goto out;
+
+ rr->type = DNS_TYPE_SRV;
+ rr->dnsclass = DNS_CLASS_IN;
+ rr->ttl = 3600;
+ rr->rdlen = 0;
+
+ rr->rdata.srv.pri = pri;
+ rr->rdata.srv.weight = weight;
+ rr->rdata.srv.port = port;
+ str_dup(&rr->rdata.srv.target, target);
+
+ list_append(&srv->rrl, &rr->le, rr);
+
+ out:
+ if (err)
+ mem_deref(rr);
+
+ return err;
+}
diff --git a/test/mock/mock_aucodec.c b/test/mock/mock_aucodec.c
new file mode 100644
index 0000000..fd91721
--- /dev/null
+++ b/test/mock/mock_aucodec.c
@@ -0,0 +1,94 @@
+/**
+ * @file mock/mock_aucodec.c Mock audio codec
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+/* A dummy protocol header */
+#define L16_HEADER 0x1616
+
+
+static int mock_l16_encode(struct auenc_state *st, uint8_t *buf, size_t *len,
+ const int16_t *sampv, size_t sampc)
+{
+ int16_t *p = (void *)buf;
+ (void)st;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (*len < sampc*2)
+ return ENOMEM;
+
+ *len = 2 + sampc*2;
+
+ *p++ = L16_HEADER;
+
+ while (sampc--)
+ *p++ = htons(*sampv++);
+
+ return 0;
+}
+
+
+static int mock_l16_decode(struct audec_state *st,
+ int16_t *sampv, size_t *sampc,
+ const uint8_t *buf, size_t len)
+{
+ int16_t *p = (void *)buf;
+ uint16_t hdr;
+ (void)st;
+
+ if (!buf || !len || !sampv)
+ return EINVAL;
+
+ if (len < 2)
+ return EINVAL;
+
+ if (*sampc < len/2)
+ return ENOMEM;
+
+ *sampc = (len - 2)/2;
+
+ hdr = *p++;
+ if (L16_HEADER != hdr) {
+ warning("mock_aucodec: invalid L16 header"
+ " 0x%04x (len=%zu)\n", hdr, len);
+ return EPROTO;
+ }
+
+ len = len/2 - 2;
+ while (len--)
+ *sampv++ = ntohs(*p++);
+
+ return 0;
+}
+
+
+static struct aucodec ac_dummy = {
+ .name = "FOO16",
+ .srate = 8000,
+ .crate = 8000,
+ .ch = 1,
+ .ench = mock_l16_encode,
+ .dech = mock_l16_decode,
+};
+
+
+void mock_aucodec_register(void)
+{
+ aucodec_register(baresip_aucodecl(), &ac_dummy);
+}
+
+
+void mock_aucodec_unregister(void)
+{
+ aucodec_unregister(&ac_dummy);
+}
diff --git a/test/mock/mock_auplay.c b/test/mock/mock_auplay.c
new file mode 100644
index 0000000..2a4ffcc
--- /dev/null
+++ b/test/mock/mock_auplay.c
@@ -0,0 +1,102 @@
+/**
+ * @file mock/mock_auplay.c Mock audio player
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+struct auplay_st {
+ const struct auplay *ap; /* inheritance */
+
+ struct tmr tmr;
+ struct auplay_prm prm;
+ void *sampv;
+ size_t sampc;
+ auplay_write_h *wh;
+ void *arg;
+};
+
+
+static struct {
+ mock_sample_h *sampleh;
+ void *arg;
+} mock;
+
+
+static void tmr_handler(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ tmr_start(&st->tmr, st->prm.ptime, tmr_handler, st);
+
+ if (st->wh)
+ st->wh(st->sampv, st->sampc, st->arg);
+
+ /* feed the audio-samples back to the test */
+ if (mock.sampleh)
+ mock.sampleh(st->sampv, st->sampc, mock.arg);
+}
+
+
+static void auplay_destructor(void *arg)
+{
+ struct auplay_st *st = arg;
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->sampv);
+}
+
+
+static int mock_auplay_alloc(struct auplay_st **stp, const struct auplay *ap,
+ struct auplay_prm *prm, const char *device,
+ auplay_write_h *wh, void *arg)
+{
+ struct auplay_st *st;
+ int err = 0;
+ (void)device;
+
+ if (!stp || !ap || !prm)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), auplay_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->ap = ap;
+ st->prm = *prm;
+ st->wh = wh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ tmr_start(&st->tmr, 0, tmr_handler, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int mock_auplay_register(struct auplay **auplayp,
+ mock_sample_h *sampleh, void *arg)
+{
+ mock.sampleh = sampleh;
+ mock.arg = arg;
+
+ return auplay_register(auplayp, baresip_auplayl(),
+ "mock-auplay", mock_auplay_alloc);
+}
diff --git a/test/mock/mock_ausrc.c b/test/mock/mock_ausrc.c
new file mode 100644
index 0000000..070cbfc
--- /dev/null
+++ b/test/mock/mock_ausrc.c
@@ -0,0 +1,91 @@
+/**
+ * @file mock/mock_ausrc.c Mock audio source
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+struct ausrc_st {
+ const struct ausrc *as; /* inheritance */
+
+ struct tmr tmr;
+ struct ausrc_prm prm;
+ void *sampv;
+ size_t sampc;
+ ausrc_read_h *rh;
+ void *arg;
+};
+
+
+static void tmr_handler(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ tmr_start(&st->tmr, st->prm.ptime, tmr_handler, st);
+
+ if (st->rh)
+ st->rh(st->sampv, st->sampc, st->arg);
+}
+
+
+static void ausrc_destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->sampv);
+}
+
+
+static int mock_ausrc_alloc(struct ausrc_st **stp, const struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *device,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ int err = 0;
+ (void)ctx;
+ (void)device;
+ (void)errh;
+
+ if (!stp || !as || !prm)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), ausrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = as;
+ st->prm = *prm;
+ st->rh = rh;
+ st->arg = arg;
+
+ st->sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->sampv = mem_zalloc(aufmt_sample_size(prm->fmt) * st->sampc, NULL);
+ if (!st->sampv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ tmr_start(&st->tmr, 0, tmr_handler, st);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int mock_ausrc_register(struct ausrc **ausrcp)
+{
+ return ausrc_register(ausrcp, baresip_ausrcl(),
+ "mock-ausrc", mock_ausrc_alloc);
+}
diff --git a/test/mock/mock_vidcodec.c b/test/mock/mock_vidcodec.c
new file mode 100644
index 0000000..9609e65
--- /dev/null
+++ b/test/mock/mock_vidcodec.c
@@ -0,0 +1,210 @@
+/**
+ * @file mock/mock_vidcodec.c Mock video codec
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+#define HDR_SIZE 12
+
+
+struct hdr {
+ enum vidfmt fmt;
+ unsigned width;
+ unsigned height;
+};
+
+struct videnc_state {
+ int64_t pts;
+ unsigned fps;
+ videnc_packet_h *pkth;
+ void *arg;
+};
+
+struct viddec_state {
+ struct vidframe *frame;
+};
+
+
+static int hdr_decode(struct hdr *hdr, struct mbuf *mb)
+{
+ if (mbuf_get_left(mb) < HDR_SIZE)
+ return EBADMSG;
+
+ hdr->fmt = ntohl(mbuf_read_u32(mb));
+ hdr->width = ntohl(mbuf_read_u32(mb));
+ hdr->height = ntohl(mbuf_read_u32(mb));
+
+ return 0;
+}
+
+
+static void decode_destructor(void *arg)
+{
+ struct viddec_state *vds = arg;
+
+ mem_deref(vds->frame);
+}
+
+
+static int mock_encode_update(struct videnc_state **vesp,
+ const struct vidcodec *vc,
+ struct videnc_param *prm, const char *fmtp,
+ videnc_packet_h *pkth, void *arg)
+{
+ struct videnc_state *ves;
+ (void)fmtp;
+
+ if (!vesp || !vc || !prm || prm->pktsize < (HDR_SIZE + 1))
+ return EINVAL;
+
+ ves = *vesp;
+
+ if (!ves) {
+
+ ves = mem_zalloc(sizeof(*ves), NULL);
+ if (!ves)
+ return ENOMEM;
+
+ *vesp = ves;
+ }
+
+ ves->fps = prm->fps;
+ ves->pkth = pkth;
+ ves->arg = arg;
+
+ return 0;
+}
+
+
+static int mock_encode(struct videnc_state *ves, bool update,
+ const struct vidframe *frame)
+{
+ struct mbuf *hdr;
+ uint8_t payload[2] = {0,0};
+ uint32_t rtp_ts;
+ int err;
+ (void)update;
+
+ if (!ves || !frame)
+ return EINVAL;
+
+ hdr = mbuf_alloc(16);
+
+ err = mbuf_write_u32(hdr, htonl(frame->fmt));
+ err |= mbuf_write_u32(hdr, htonl(frame->size.w));
+ err |= mbuf_write_u32(hdr, htonl(frame->size.h));
+ if (err)
+ goto out;
+
+ rtp_ts = video_calc_rtp_timestamp(++ves->pts, ves->fps);
+
+ err = ves->pkth(true, rtp_ts, hdr->buf, hdr->end,
+ payload, sizeof(payload), ves->arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(hdr);
+
+ return err;
+}
+
+
+static int mock_decode_update(struct viddec_state **vdsp,
+ const struct vidcodec *vc, const char *fmtp)
+{
+ struct viddec_state *vds;
+ int err = 0;
+ (void)vc;
+ (void)fmtp;
+
+ if (!vdsp)
+ return EINVAL;
+
+ vds = *vdsp;
+
+ if (vds)
+ return 0;
+
+ vds = mem_zalloc(sizeof(*vds), decode_destructor);
+ if (!vds)
+ return ENOMEM;
+
+ if (err)
+ mem_deref(vds);
+ else
+ *vdsp = vds;
+
+ return err;
+}
+
+
+static int mock_decode(struct viddec_state *vds, struct vidframe *frame,
+ bool *intra, bool marker, uint16_t seq, struct mbuf *mb)
+{
+ struct vidsz size;
+ struct hdr hdr;
+ int err, i;
+ (void)marker;
+ (void)seq;
+
+ if (!vds || !frame || !intra || !mb)
+ return EINVAL;
+
+ *intra = false;
+
+ err = hdr_decode(&hdr, mb);
+ if (err) {
+ warning("mock_vidcodec: could not decode header (%m)\n", err);
+ return err;
+ }
+
+ size.w = hdr.width;
+ size.h = hdr.height;
+
+ if (!vds->frame) {
+ err = vidframe_alloc(&vds->frame, hdr.fmt, &size);
+ if (err)
+ goto out;
+ }
+
+ for (i=0; i<4; i++) {
+ frame->data[i] = vds->frame->data[i];
+ frame->linesize[i] = vds->frame->linesize[i];
+ }
+
+ frame->size.w = vds->frame->size.w;
+ frame->size.h = vds->frame->size.h;
+ frame->fmt = vds->frame->fmt;
+
+ out:
+ return err;
+}
+
+
+static struct vidcodec vc_dummy = {
+ .name = "H266",
+ .encupdh = mock_encode_update,
+ .ench = mock_encode,
+ .decupdh = mock_decode_update,
+ .dech = mock_decode,
+};
+
+
+void mock_vidcodec_register(void)
+{
+ vidcodec_register(baresip_vidcodecl(), &vc_dummy);
+}
+
+
+void mock_vidcodec_unregister(void)
+{
+ vidcodec_unregister(&vc_dummy);
+}
diff --git a/test/mock/mock_vidisp.c b/test/mock/mock_vidisp.c
new file mode 100644
index 0000000..322dd1c
--- /dev/null
+++ b/test/mock/mock_vidisp.c
@@ -0,0 +1,97 @@
+/**
+ * @file mock/mock_vidisp.c Mock video display
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+#define MAX_WIDTH 65536
+#define MAX_HEIGHT 65536
+
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance */
+ unsigned n_frame;
+};
+
+
+static void disp_destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+ (void)st;
+}
+
+
+static int mock_disp_alloc(struct vidisp_st **stp, const struct vidisp *vd,
+ struct vidisp_prm *prm, const char *dev,
+ vidisp_resize_h *resizeh, void *arg)
+{
+ struct vidisp_st *st;
+ (void)prm;
+ (void)dev;
+ (void)resizeh;
+ (void)arg;
+
+ if (!stp || !vd)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), disp_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+
+ *stp = st;
+
+ return 0;
+}
+
+
+static int mock_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ unsigned width, height;
+ (void)title;
+
+ if (!st || !frame)
+ return EINVAL;
+
+ width = frame->size.w;
+ height = frame->size.h;
+
+ if (!vidframe_isvalid(frame)) {
+ warning("mock_vidisp: got invalid frame\n");
+ return EPROTO;
+ }
+
+ /* verify that the video frame is good */
+ if (frame->fmt >= VID_FMT_N)
+ return EPROTO;
+ if (width == 0 || width > MAX_WIDTH)
+ return EPROTO;
+ if (height == 0 || height > MAX_HEIGHT)
+ return EPROTO;
+ if (frame->linesize[0] == 0)
+ return EPROTO;
+
+ ++st->n_frame;
+
+ if (st->n_frame >= 10) {
+ info("mock_vidisp: got %u frames -- stopping re_main\n",
+ st->n_frame);
+ re_cancel(); /* XXX use a callback handler instead */
+ }
+
+ return 0;
+}
+
+
+int mock_vidisp_register(struct vidisp **vidispp)
+{
+ return vidisp_register(vidispp, baresip_vidispl(), "mock-vidisp",
+ mock_disp_alloc, NULL, mock_display, NULL);
+}
diff --git a/test/mock/mock_vidsrc.c b/test/mock/mock_vidsrc.c
new file mode 100644
index 0000000..8f92bc4
--- /dev/null
+++ b/test/mock/mock_vidsrc.c
@@ -0,0 +1,91 @@
+/**
+ * @file mock/mock_vidsrc.c Mock video source
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "../test.h"
+
+
+struct vidsrc_st {
+ const struct vidsrc *vs; /* inheritance */
+
+ struct vidframe *frame;
+ struct tmr tmr;
+ int fps;
+ vidsrc_frame_h *frameh;
+ void *arg;
+};
+
+
+static void tmr_handler(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ tmr_start(&st->tmr, 1000/st->fps, tmr_handler, st);
+
+ if (st->frameh)
+ st->frameh(st->frame, st->arg);
+}
+
+
+static void vidsrc_destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ tmr_cancel(&st->tmr);
+ mem_deref(st->frame);
+}
+
+
+static int mock_vidsrc_alloc(struct vidsrc_st **stp, const struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err = 0;
+ (void)ctx;
+ (void)fmt;
+ (void)dev;
+ (void)errorh;
+
+ if (!stp || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), vidsrc_destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vs = vs;
+ st->fps = prm->fps;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size);
+ if (err)
+ goto out;
+
+ tmr_start(&st->tmr, 0, tmr_handler, st);
+
+ info("mock_vidsrc: new instance with size %u x %u (%d fps)\n",
+ size->w, size->h, prm->fps);
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int mock_vidsrc_register(struct vidsrc **vidsrcp)
+{
+ return vidsrc_register(vidsrcp, baresip_vidsrcl(), "mock-vidsrc",
+ mock_vidsrc_alloc, NULL);
+}
diff --git a/test/mos.c b/test/mos.c
new file mode 100644
index 0000000..20b13c2
--- /dev/null
+++ b/test/mos.c
@@ -0,0 +1,53 @@
+/**
+ * @file test/mos.c Test the MOS (Mean Opinion Score) calculator
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define DEBUG_MODULE "mos"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+int test_mos(void)
+{
+#define PRECISION 0.001
+ static struct {
+ /* input: */
+ double rtt;
+ double jitter;
+ uint32_t packet_loss;
+
+ /* output: */
+ double r_factor;
+ double mos;
+ } testv[] = {
+ { 0.0, 0.0, 0, 92.95, 4.404 },
+ { 500.0, 0.0, 0, 54.20, 2.796 },
+ { 1000.0, 0.0, 0, 4.20, 0.990 },
+ { 0.0, 100.0, 0, 84.20, 4.172 },
+ { 0.0, 200.0, 0, 64.20, 3.315 },
+ { 0.0, 0.0, 1, 90.45, 4.350 },
+ { 0.0, 0.0, 10, 67.95, 3.499 },
+ { 10.0, 10.0, 10, 67.20, 3.463 },
+ };
+ size_t i;
+ int err = 0;
+
+ for (i=0; i<ARRAY_SIZE(testv); i++) {
+ double r_factor, mos;
+
+ mos = mos_calculate(&r_factor, testv[i].rtt, testv[i].jitter,
+ testv[i].packet_loss);
+
+ ASSERT_DOUBLE_EQ(testv[i].r_factor, r_factor, PRECISION);
+ ASSERT_DOUBLE_EQ(testv[i].mos, mos, PRECISION);
+ }
+
+ out:
+ return err;
+}
diff --git a/test/net.c b/test/net.c
new file mode 100644
index 0000000..170fc3b
--- /dev/null
+++ b/test/net.c
@@ -0,0 +1,46 @@
+/**
+ * @file test/net.c Baresip selftest -- networking
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+static struct config_net default_config;
+
+
+static void net_change_handler(void *arg)
+{
+ unsigned *count = arg;
+ ++*count;
+ info("network changed\n");
+}
+
+
+int test_network(void)
+{
+ struct network *net = NULL;
+ unsigned change_count = 0;
+ int err;
+
+ err = net_alloc(&net, &default_config, AF_INET);
+ TEST_ERR(err);
+ ASSERT_TRUE(net != NULL);
+
+ ASSERT_EQ(AF_INET, net_af(net));
+
+ net_change(net, 1, net_change_handler, &change_count);
+
+ ASSERT_EQ(0, change_count);
+
+ net_force_change(net);
+
+ ASSERT_EQ(1, change_count);
+
+ out:
+ mem_deref(net);
+ return err;
+}
diff --git a/test/play.c b/test/play.c
new file mode 100644
index 0000000..82c8813
--- /dev/null
+++ b/test/play.c
@@ -0,0 +1,99 @@
+/**
+ * @file test/play.c Baresip selftest -- audio file player
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define NUM_SAMPLES 320 /* 8000 Hz, 1 channel, 40ms */
+
+
+struct test {
+ struct mbuf *mb_samp;
+};
+
+
+static struct mbuf *generate_tone(void)
+{
+ struct mbuf *mb;
+ unsigned i;
+ int err = 0;
+
+ mb = mbuf_alloc(NUM_SAMPLES * 2);
+ if (!mb)
+ return NULL;
+
+ for (i=0; i<NUM_SAMPLES; i++)
+ err |= mbuf_write_u16(mb, i);
+
+ mb->pos = 0;
+
+ if (err)
+ return mem_deref(mb);
+ else
+ return mb;
+}
+
+
+static void sample_handler(const void *sampv, size_t sampc, void *arg)
+{
+ struct test *test = arg;
+ size_t bytec = sampc * 2;
+ int err = 0;
+
+ if (!test->mb_samp) {
+ test->mb_samp = mbuf_alloc(bytec);
+ ASSERT_TRUE(test->mb_samp != NULL);
+ }
+
+ /* save the samples that was played */
+ err = mbuf_write_mem(test->mb_samp, (void *)sampv, bytec);
+
+ out:
+ /* stop the test? */
+ if (err || test->mb_samp->end >= (NUM_SAMPLES*2))
+ re_cancel();
+}
+
+
+int test_play(void)
+{
+ struct auplay *auplay = NULL;
+ struct player *player = NULL;
+ struct play *play = NULL;
+ struct mbuf *mb_tone = NULL;
+ struct test test = {0};
+ int err;
+
+ /* use a mock audio-driver to save the audio-samples */
+ err = mock_auplay_register(&auplay, sample_handler, &test);
+ ASSERT_EQ(0, err);
+
+ err = play_init(&player);
+ ASSERT_EQ(0, err);
+
+ mb_tone = generate_tone();
+ ASSERT_TRUE(mb_tone != NULL);
+
+ err = play_tone(&play, player, mb_tone, 8000, 1, 0);
+ ASSERT_EQ(0, err);
+
+ err = re_main_timeout(10000);
+ ASSERT_EQ(0, err);
+
+ /* verify the audio-samples that was played */
+ TEST_MEMCMP(mb_tone->buf, NUM_SAMPLES*2,
+ test.mb_samp->buf, test.mb_samp->end);
+
+ out:
+ mem_deref(test.mb_samp);
+ mem_deref(mb_tone);
+ mem_deref(play);
+ mem_deref(player);
+ mem_deref(auplay);
+ return err;
+}
diff --git a/test/sip/aor.c b/test/sip/aor.c
new file mode 100644
index 0000000..2d7e0d3
--- /dev/null
+++ b/test/sip/aor.c
@@ -0,0 +1,98 @@
+/**
+ * @file sip/aor.c Mock SIP server -- SIP Address of Record
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include "sipsrv.h"
+
+
+static void destructor(void *arg)
+{
+ struct aor *aor = arg;
+
+ list_flush(&aor->locl);
+ hash_unlink(&aor->he);
+ mem_deref(aor->uri);
+}
+
+
+static int uri_canon(char **curip, const struct uri *uri)
+{
+ if (pl_isset(&uri->user))
+ return re_sdprintf(curip, "%r:%H@%r",
+ &uri->scheme,
+ uri_user_unescape, &uri->user,
+ &uri->host);
+ else
+ return re_sdprintf(curip, "%r:%r",
+ &uri->scheme,
+ &uri->host);
+}
+
+
+int aor_create(struct sip_server *srv, struct aor **aorp,
+ const struct uri *uri)
+{
+ struct aor *aor;
+ int err;
+
+ if (!aorp || !uri)
+ return EINVAL;
+
+ aor = mem_zalloc(sizeof(*aor), destructor);
+ if (!aor)
+ return ENOMEM;
+
+ err = uri_canon(&aor->uri, uri);
+ if (err)
+ goto out;
+
+ hash_append(srv->ht_aor, hash_joaat_str_ci(aor->uri), &aor->he, aor);
+
+ out:
+ if (err)
+ mem_deref(aor);
+ else
+ *aorp = aor;
+
+ return err;
+}
+
+
+int aor_find(struct sip_server *srv, struct aor **aorp, const struct uri *uri)
+{
+ struct list *lst;
+ struct aor *aor = NULL;
+ struct le *le;
+ char *curi;
+ int err;
+
+ if (!uri)
+ return EINVAL;
+
+ err = uri_canon(&curi, uri);
+ if (err)
+ return err;
+
+ lst = hash_list(srv->ht_aor, hash_joaat_str_ci(curi));
+
+ for (le=list_head(lst); le; le=le->next) {
+
+ aor = le->data;
+
+ if (!str_casecmp(curi, aor->uri))
+ break;
+ }
+
+ mem_deref(curi);
+
+ if (!le)
+ return ENOENT;
+
+ if (aorp)
+ *aorp = aor;
+
+ return 0;
+}
diff --git a/test/sip/auth.c b/test/sip/auth.c
new file mode 100644
index 0000000..5d3e224
--- /dev/null
+++ b/test/sip/auth.c
@@ -0,0 +1,91 @@
+/**
+ * @file sip/auth.c Mock SIP server -- authentication
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include "sipsrv.h"
+
+
+enum {
+ NONCE_MIN_SIZE = 33,
+};
+
+
+int auth_print(struct re_printf *pf, const struct auth *auth)
+{
+ uint8_t key[MD5_SIZE];
+ uint64_t nv[2];
+
+ if (!auth)
+ return EINVAL;
+
+ nv[0] = time(NULL);
+ nv[1] = auth->srv->secret;
+
+ md5((uint8_t *)nv, sizeof(nv), key);
+
+ return re_hprintf(pf,
+ "Digest realm=\"%s\", nonce=\"%w%llx\", "
+ "qop=\"auth\"%s",
+ auth->realm,
+ key, sizeof(key), nv[0],
+ auth->stale ? ", stale=true" : "");
+}
+
+
+int auth_chk_nonce(struct sip_server *srv,
+ const struct pl *nonce, uint32_t expires)
+{
+ uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE];
+ uint64_t nv[2];
+ struct pl pl;
+ int64_t age;
+ unsigned i;
+
+ if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE)
+ return EINVAL;
+
+ pl = *nonce;
+
+ for (i=0; i<sizeof(nkey); i++) {
+ nkey[i] = ch_hex(*pl.p++) << 4;
+ nkey[i] += ch_hex(*pl.p++);
+ pl.l -= 2;
+ }
+
+ nv[0] = pl_x64(&pl);
+ nv[1] = srv->secret;
+
+ md5((uint8_t *)nv, sizeof(nv), ckey);
+
+ if (memcmp(nkey, ckey, MD5_SIZE))
+ return EAUTH;
+
+ age = time(NULL) - nv[0];
+
+ if (age < 0 || age > expires)
+ return ETIMEDOUT;
+
+ return 0;
+}
+
+
+int auth_set_realm(struct auth *auth, const char *realm)
+{
+ size_t len;
+
+ if (!auth || !realm)
+ return EINVAL;
+
+ len = strlen(realm);
+ if (len >= sizeof(auth->realm))
+ return ENOMEM;
+
+ memcpy(auth->realm, realm, len);
+ auth->realm[len] = '\0';
+
+ return 0;
+}
diff --git a/test/sip/domain.c b/test/sip/domain.c
new file mode 100644
index 0000000..063e3b9
--- /dev/null
+++ b/test/sip/domain.c
@@ -0,0 +1,187 @@
+/**
+ * @file sip/domain.c Mock SIP server -- domain handling
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include "sipsrv.h"
+
+
+#define DEBUG_MODULE "mock/sipsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ NONCE_EXPIRES = 300,
+};
+
+
+static void destructor(void *arg)
+{
+ struct domain *dom = arg;
+
+ hash_unlink(&dom->he);
+ hash_flush(dom->ht_usr);
+ mem_deref(dom->ht_usr);
+ mem_deref(dom->name);
+}
+
+
+static struct domain *lookup(struct sip_server *srv, const struct pl *name)
+{
+ struct list *lst;
+ struct le *le;
+
+ lst = hash_list(srv->ht_dom, hash_joaat_ci(name->p, name->l));
+
+ for (le=list_head(lst); le; le=le->next) {
+
+ struct domain *dom = le->data;
+
+ if (pl_strcasecmp(name, dom->name))
+ continue;
+
+ return dom;
+ }
+
+ return NULL;
+}
+
+
+int domain_add(struct sip_server *srv, const char *name)
+{
+ struct domain *dom;
+ int err;
+
+ dom = mem_zalloc(sizeof(*dom), destructor);
+ if (!dom)
+ return ENOMEM;
+
+ err = str_dup(&dom->name, name);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&dom->ht_usr, 32);
+ if (err)
+ return err;
+
+ hash_append(srv->ht_dom, hash_joaat_str_ci(name), &dom->he, dom);
+
+ out:
+ if (err)
+ mem_deref(dom);
+
+ return err;
+}
+
+
+int domain_find(struct sip_server *srv, const struct uri *uri)
+{
+ int err = ENOENT;
+ struct sa addr;
+
+ if (!uri)
+ return EINVAL;
+
+ if (!sa_set(&addr, &uri->host, uri->port)) {
+
+ if (!uri->port) {
+
+ uint16_t port = SIP_PORT;
+
+ if (!pl_strcasecmp(&uri->scheme, "sips"))
+ port = SIP_PORT_TLS;
+
+ sa_set_port(&addr, port);
+ }
+
+ if (sip_transp_isladdr(srv->sip, SIP_TRANSP_NONE, &addr))
+ return 0;
+
+ return ENOENT;
+ }
+
+ err = lookup(srv, &uri->host) ? 0 : ENOENT;
+
+ return err;
+}
+
+
+int domain_auth(struct sip_server *srv,
+ const struct uri *uri, bool user_match,
+ const struct sip_msg *msg, enum sip_hdrid hdrid,
+ struct auth *auth)
+{
+ struct domain *dom;
+ struct list *lst;
+ struct le *le;
+ int err = ENOENT;
+
+ if (!uri || !msg || !auth)
+ return EINVAL;
+
+ dom = lookup(srv, &uri->host);
+ if (!dom) {
+ DEBUG_WARNING("domain not found (%r)\n", &uri->host);
+ return ENOENT;
+ }
+
+ err = auth_set_realm(auth, dom->name);
+ if (err)
+ return err;
+
+ auth->stale = false;
+
+ lst = hash_list(msg->hdrht, hdrid);
+
+ for (le=list_head(lst); le; le=le->next) {
+
+ const struct sip_hdr *hdr = le->data;
+ struct httpauth_digest_resp resp;
+ const struct user *usr;
+
+ if (hdr->id != hdrid)
+ continue;
+
+ if (httpauth_digest_response_decode(&resp, &hdr->val))
+ continue;
+
+ if (pl_strcasecmp(&resp.realm, dom->name))
+ continue;
+
+ if (auth_chk_nonce(srv, &resp.nonce, NONCE_EXPIRES)) {
+ auth->stale = true;
+ continue;
+ }
+
+ auth->stale = false;
+
+ usr = user_find(dom->ht_usr, &resp.username);
+ if (!usr) {
+ DEBUG_WARNING("user not found (%r)\n", &resp.username);
+ break;
+ }
+
+ err = httpauth_digest_response_auth(&resp, &msg->met,usr->ha1);
+ if (err)
+ return err;
+
+ if (user_match && pl_cmp(&resp.username, &uri->user))
+ return EPERM;
+
+ return 0;
+ }
+
+ return EAUTH;
+}
+
+
+struct domain *domain_lookup(struct sip_server *srv, const char *name)
+{
+ struct pl pl;
+
+ pl_set_str(&pl, name);
+
+ return lookup(srv, &pl);
+}
diff --git a/test/sip/location.c b/test/sip/location.c
new file mode 100644
index 0000000..be2bd66
--- /dev/null
+++ b/test/sip/location.c
@@ -0,0 +1,188 @@
+/**
+ * @file sip/location.c Mock SIP server -- location handling
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <time.h>
+#include <re.h>
+#include "sipsrv.h"
+
+
+struct loctmp {
+ struct sa src;
+ struct uri duri;
+ char *uri;
+ char *callid;
+ uint32_t expires;
+ uint32_t cseq;
+ double q;
+};
+
+
+static void destructor_loctmp(void *arg)
+{
+ struct loctmp *tmp = arg;
+
+ mem_deref(tmp->uri);
+ mem_deref(tmp->callid);
+}
+
+
+static void destructor_location(void *arg)
+{
+ struct location *loc = arg;
+
+ list_unlink(&loc->le);
+ mem_deref(loc->uri);
+ mem_deref(loc->callid);
+ mem_deref(loc->tmp);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct location *loc = le->data;
+
+ return uri_cmp(&loc->duri, arg);
+}
+
+
+int location_update(struct list *locl, const struct sip_msg *msg,
+ const struct sip_addr *contact, uint32_t expires)
+{
+ struct location *loc, *loc_new = NULL;
+ struct loctmp *tmp;
+ struct pl pl;
+ int err;
+
+ if (!locl || !msg || !contact)
+ return EINVAL;
+
+ loc = list_ledata(list_apply(locl, true, cmp_handler,
+ (void *)&contact->uri));
+ if (!loc) {
+ if (expires == 0)
+ return 0;
+
+ loc = loc_new = mem_zalloc(sizeof(*loc), destructor_location);
+ if (!loc)
+ return ENOMEM;
+
+ list_append(locl, &loc->le, loc);
+ }
+ else {
+ if (!pl_strcmp(&msg->callid, loc->callid) &&
+ msg->cseq.num <= loc->cseq)
+ return EPROTO;
+
+ if (expires == 0) {
+ loc->rm = true;
+ return 0;
+ }
+ }
+
+ tmp = mem_zalloc(sizeof(*tmp), destructor_loctmp);
+ if (!tmp) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = pl_strdup(&tmp->uri, &contact->auri);
+ if (err)
+ goto out;
+
+ pl_set_str(&pl, tmp->uri);
+
+ if (uri_decode(&tmp->duri, &pl)) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ err = pl_strdup(&tmp->callid, &msg->callid);
+ if (err)
+ goto out;
+
+
+ if (!msg_param_decode(&contact->params, "q", &pl))
+ tmp->q = pl_float(&pl);
+ else
+ tmp->q = 1;
+
+ tmp->cseq = msg->cseq.num;
+ tmp->expires = expires;
+ tmp->src = msg->src;
+
+ out:
+ if (err) {
+ mem_deref(loc_new);
+ mem_deref(tmp);
+ }
+ else {
+ mem_deref(loc->tmp);
+ loc->tmp = tmp;
+ }
+
+ return err;
+}
+
+
+void location_commit(struct list *locl)
+{
+ time_t now = time(NULL);
+ struct le *le;
+
+ if (!locl)
+ return;
+
+ for (le=locl->head; le; ) {
+
+ struct location *loc = le->data;
+
+ le = le->next;
+
+ if (loc->rm) {
+ list_unlink(&loc->le);
+ mem_deref(loc);
+ }
+ else if (loc->tmp) {
+
+ mem_deref(loc->uri);
+ mem_deref(loc->callid);
+
+ loc->uri = mem_ref(loc->tmp->uri);
+ loc->callid = mem_ref(loc->tmp->callid);
+ loc->duri = loc->tmp->duri;
+ loc->cseq = loc->tmp->cseq;
+ loc->expires = loc->tmp->expires + now;
+ loc->src = loc->tmp->src;
+ loc->q = loc->tmp->q;
+
+ loc->tmp = mem_deref(loc->tmp);
+ }
+ }
+}
+
+
+void location_rollback(struct list *locl)
+{
+ struct le *le;
+
+ if (!locl)
+ return;
+
+ for (le=locl->head; le; ) {
+
+ struct location *loc = le->data;
+
+ le = le->next;
+
+ if (!loc->uri) {
+ list_unlink(&loc->le);
+ mem_deref(loc);
+ }
+ else {
+ loc->tmp = mem_deref(loc->tmp);
+ loc->rm = false;
+ }
+ }
+}
diff --git a/test/sip/sipsrv.c b/test/sip/sipsrv.c
new file mode 100644
index 0000000..374ea3b
--- /dev/null
+++ b/test/sip/sipsrv.c
@@ -0,0 +1,330 @@
+/**
+ * @file sip/sipsrv.c Mock SIP server
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "../test.h"
+#include "sipsrv.h"
+
+
+#define DEBUG_MODULE "mock/sipsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#define LOCAL_PORT 0
+#define LOCAL_SECURE_PORT 0
+#define EXPIRES_MIN 60
+#define EXPIRES_MAX 3600
+
+
+static int print_contact(struct re_printf *pf, const struct aor *aor)
+{
+ const uint64_t now = (uint64_t)time(NULL);
+ struct le *le;
+ int err = 0;
+
+ for (le=aor->locl.head; le; le=le->next) {
+
+ const struct location *loc = le->data;
+
+ if (loc->expires < now)
+ continue;
+
+ err |= re_hprintf(pf, "Contact: <%s>;expires=%lli\r\n",
+ loc->uri, loc->expires - now);
+ }
+
+ return err;
+}
+
+
+static bool handle_register(struct sip_server *srv, const struct sip_msg *msg)
+{
+ struct auth auth = { srv, "", false };
+ struct sip *sip = srv->sip;
+ struct list *lst;
+ struct aor *aor;
+ struct le *le;
+ int err;
+
+ /* Request URI */
+ err = domain_find(srv, &msg->uri);
+ if (err) {
+ if (err == ENOENT) {
+ warning("domain not found\n");
+ return false;
+ }
+
+ sip_reply(sip, msg, 500, strerror(err));
+ warning("domain find error: %s\n", strerror(err));
+ return true;
+ }
+
+ /* Authorize */
+ if (srv->auth_enabled)
+ err = domain_auth(srv, &msg->to.uri, true,
+ msg, SIP_HDR_AUTHORIZATION, &auth);
+ else
+ err = domain_find(srv, &msg->to.uri);
+
+ if (err && err != EAUTH) {
+ DEBUG_NOTICE("domain auth/find error: %m\n", err);
+ }
+
+ switch (err) {
+
+ case 0:
+ break;
+
+ case EAUTH:
+ sip_replyf(sip, msg, 401, "Unauthorized",
+ "WWW-Authenticate: %H\r\n"
+ "Content-Length: 0\r\n\r\n",
+ auth_print, &auth);
+ return true;
+
+ case EPERM:
+ sip_reply(sip, msg, 403, "Forbidden");
+ return true;
+
+ case ENOENT:
+ sip_reply(sip, msg, 404, "Not Found");
+ return true;
+
+ default:
+ sip_reply(sip, msg, 500, strerror(err));
+ warning("domain error: %s\n", strerror(err));
+ return true;
+ }
+
+ /* Find AoR */
+ err = aor_find(srv, &aor, &msg->to.uri);
+ if (err) {
+ if (err != ENOENT) {
+ sip_reply(sip, msg, 500, strerror(err));
+ warning("aor find error: %s\n", strerror(err));
+ return true;
+ }
+
+ err = aor_create(srv, &aor, &msg->to.uri);
+ if (err) {
+ sip_reply(sip, msg, 500, strerror(err));
+ warning("aor create error: %s\n", strerror(err));
+ return true;
+ }
+ }
+
+ /* Process Contacts */
+ lst = hash_list(msg->hdrht, SIP_HDR_CONTACT);
+
+ for (le=list_head(lst); le; le=le->next) {
+
+ const struct sip_hdr *hdr = le->data;
+ struct sip_addr contact;
+ uint32_t expires;
+ struct pl pl;
+
+ if (hdr->id != SIP_HDR_CONTACT)
+ continue;
+
+ err = sip_addr_decode(&contact, &hdr->val);
+ if (err) {
+ sip_reply(sip, msg, 400, "Bad Contact");
+ goto fail;
+ }
+
+ if (!msg_param_decode(&contact.params, "expires", &pl))
+ expires = pl_u32(&pl);
+ else if (pl_isset(&msg->expires))
+ expires = pl_u32(&msg->expires);
+ else
+ expires = 3600;
+
+ if (expires > 0 && expires < EXPIRES_MIN) {
+ sip_replyf(sip, msg, 423, "Interval Too Brief",
+ "Min-Expires: %u\r\n"
+ "Content-Length: 0\r\n\r\n",
+ EXPIRES_MIN);
+ goto fail;
+ }
+
+ expires = min(expires, EXPIRES_MAX);
+
+ err = location_update(&aor->locl, msg, &contact, expires);
+ if (err) {
+ sip_reply(sip, msg, 500, strerror(err));
+ if (err != EPROTO)
+ warning("location update error: %s\n",
+ strerror(err));
+ goto fail;
+ }
+ }
+
+ location_commit(&aor->locl);
+
+ sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK",
+ "%H"
+ "Date: %H\r\n"
+ "Content-Length: 0\r\n\r\n",
+ print_contact, aor,
+ fmt_gmtime, NULL);
+
+ return true;
+
+ fail:
+ location_rollback(&aor->locl);
+
+ return true;
+}
+
+
+static bool sip_msg_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sip_server *srv = arg;
+ int err = 0;
+
+#if 0
+ DEBUG_NOTICE("[%u] recv %r via %s\n", srv->instance,
+ &msg->met, sip_transp_name(msg->tp));
+#endif
+
+ srv->tp_last = msg->tp;
+
+ if (0 == pl_strcmp(&msg->met, "REGISTER")) {
+ ++srv->n_register_req;
+ if (handle_register(srv, msg))
+ goto out;
+
+ sip_reply(srv->sip, msg, 503, "Server Error");
+ }
+ else {
+ DEBUG_NOTICE("method not handled (%r)\n", &msg->met);
+ return false;
+ }
+
+ if (srv->terminate)
+ err = sip_reply(srv->sip, msg, 503, "Server Error");
+
+ if (err) {
+ DEBUG_WARNING("could not reply: %m\n", err);
+ }
+
+ out:
+ if (srv->terminate)
+ re_cancel(); /* XXX: avoid this */
+
+ return true;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_server *srv = arg;
+
+ srv->terminate = true;
+
+ sip_close(srv->sip, false);
+ mem_deref(srv->sip);
+
+ hash_flush(srv->ht_aor);
+ mem_deref(srv->ht_aor);
+
+ hash_flush(srv->ht_dom);
+ mem_deref(srv->ht_dom);
+}
+
+
+int sip_server_alloc(struct sip_server **srvp)
+{
+ struct sip_server *srv;
+ struct sa laddr, laddrs;
+ struct tls *tls = NULL;
+ int err;
+
+ if (!srvp)
+ return EINVAL;
+
+ srv = mem_zalloc(sizeof *srv, destructor);
+ if (!srv)
+ return ENOMEM;
+
+ err = sa_set_str(&laddr, "127.0.0.1", LOCAL_PORT);
+ err |= sa_set_str(&laddrs, "127.0.0.1", LOCAL_SECURE_PORT);
+ if (err)
+ goto out;
+
+ err = sip_alloc(&srv->sip, NULL, 16, 16, 16,
+ "mock SIP server", NULL, NULL);
+ if (err)
+ goto out;
+
+ err |= sip_transp_add(srv->sip, SIP_TRANSP_UDP, &laddr);
+ err |= sip_transp_add(srv->sip, SIP_TRANSP_TCP, &laddr);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = tls_set_certificate(tls, test_certificate,
+ strlen(test_certificate));
+ if (err)
+ goto out;
+
+ err |= sip_transp_add(srv->sip, SIP_TRANSP_TLS, &laddrs, tls);
+#endif
+ if (err)
+ goto out;
+
+ err = sip_listen(&srv->lsnr, srv->sip, true, sip_msg_handler, srv);
+ if (err)
+ goto out;
+
+ srv->secret = rand_u64();
+
+ err = hash_alloc(&srv->ht_dom, 32);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&srv->ht_aor, 32);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(tls);
+ if (err)
+ mem_deref(srv);
+ else
+ *srvp = srv;
+
+ return err;
+}
+
+
+int sip_server_uri(struct sip_server *srv, char *uri, size_t sz,
+ enum sip_transp tp)
+{
+ struct sa laddr;
+ int err;
+
+ if (!srv || !uri || !sz)
+ return EINVAL;
+
+ err = sip_transp_laddr(srv->sip, &laddr, tp, NULL);
+ if (err)
+ return err;
+
+ /* NOTE: angel brackets needed to parse ;transport parameter */
+ if (re_snprintf(uri, sz, "<sip:x:x@%J%s>",
+ &laddr, sip_transp_param(tp)) < 0)
+ return ENOMEM;
+
+ return 0;
+}
diff --git a/test/sip/sipsrv.h b/test/sip/sipsrv.h
new file mode 100644
index 0000000..a4b8bbf
--- /dev/null
+++ b/test/sip/sipsrv.h
@@ -0,0 +1,122 @@
+/**
+ * @file sip/sipsrv.h Mock SIP server -- interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+struct auth;
+
+
+/*
+ * SIP Server
+ */
+
+struct sip_server {
+ struct sip *sip;
+ struct sip_lsnr *lsnr;
+ bool auth_enabled;
+ bool terminate;
+ unsigned instance;
+
+ unsigned n_register_req;
+ enum sip_transp tp_last;
+
+ uint64_t secret;
+ struct hash *ht_dom;
+ struct hash *ht_aor;
+};
+
+int sip_server_alloc(struct sip_server **srvp);
+int sip_server_uri(struct sip_server *srv, char *uri, size_t sz,
+ enum sip_transp tp);
+
+
+/*
+ * AoR
+ */
+
+struct aor {
+ struct le he;
+ struct list locl;
+ char *uri;
+};
+
+int aor_create(struct sip_server *srv, struct aor **aorp,
+ const struct uri *uri);
+int aor_find(struct sip_server *srv, struct aor **aorp,
+ const struct uri *uri);
+
+
+/*
+ * Auth
+ */
+
+struct auth {
+ const struct sip_server *srv;
+ char realm[256];
+ bool stale;
+};
+
+int auth_print(struct re_printf *pf, const struct auth *auth);
+int auth_chk_nonce(struct sip_server *srv,
+ const struct pl *nonce, uint32_t expires);
+int auth_set_realm(struct auth *auth, const char *realm);
+
+
+/*
+ * Domain
+ */
+
+struct domain {
+ struct le he;
+ struct hash *ht_usr;
+ char *name;
+};
+
+
+int domain_add(struct sip_server *srv, const char *name);
+int domain_find(struct sip_server *srv, const struct uri *uri);
+int domain_auth(struct sip_server *srv,
+ const struct uri *uri, bool user_match,
+ const struct sip_msg *msg, enum sip_hdrid hdrid,
+ struct auth *auth);
+struct domain *domain_lookup(struct sip_server *srv, const char *name);
+
+
+/*
+ * Location
+ */
+
+struct location {
+ struct le le;
+ struct sa src;
+ struct uri duri;
+ char *uri;
+ char *callid;
+ struct loctmp *tmp;
+ uint64_t expires;
+ uint32_t cseq;
+ double q;
+ bool rm;
+};
+
+int location_update(struct list *locl, const struct sip_msg *msg,
+ const struct sip_addr *contact, uint32_t expires);
+void location_commit(struct list *locl);
+void location_rollback(struct list *locl);
+
+
+/*
+ * User
+ */
+
+struct user {
+ struct le he;
+ uint8_t ha1[MD5_SIZE];
+ char *name;
+};
+
+int user_add(struct hash *ht, const char *username, const char *password,
+ const char *realm);
+struct user *user_find(struct hash *ht, const struct pl *name);
diff --git a/test/sip/user.c b/test/sip/user.c
new file mode 100644
index 0000000..99fb47d
--- /dev/null
+++ b/test/sip/user.c
@@ -0,0 +1,73 @@
+/**
+ * @file sip/user.c Mock SIP server -- user handling
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include "sipsrv.h"
+
+
+static void destructor(void *arg)
+{
+ struct user *usr = arg;
+
+ hash_unlink(&usr->he);
+ mem_deref(usr->name);
+}
+
+
+int user_add(struct hash *ht, const char *username, const char *password,
+ const char *realm)
+{
+ struct user *usr;
+ int err;
+
+ usr = mem_zalloc(sizeof(*usr), destructor);
+ if (!usr) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = str_dup(&usr->name, username);
+ if (err) {
+ goto out;
+ }
+
+ err = md5_printf(usr->ha1, "%s:%s:%s", username, realm, password);
+ if (err) {
+ goto out;
+ }
+
+ hash_append(ht, hash_joaat_str(username), &usr->he, usr);
+
+ out:
+ if (err) {
+ mem_deref(usr);
+ }
+
+ return err;
+}
+
+
+struct user *user_find(struct hash *ht, const struct pl *name)
+{
+ struct list *lst;
+ struct le *le;
+
+ if (!ht || !name)
+ return NULL;
+
+ lst = hash_list(ht, hash_joaat((uint8_t *)name->p, name->l));
+
+ for (le=list_head(lst); le; le=le->next) {
+
+ struct user *usr = le->data;
+
+ if (pl_strcmp(name, usr->name))
+ continue;
+
+ return usr;
+ }
+
+ return NULL;
+}
diff --git a/test/srcs.mk b/test/srcs.mk
new file mode 100644
index 0000000..0bcb6de
--- /dev/null
+++ b/test/srcs.mk
@@ -0,0 +1,54 @@
+#
+# srcs.mk All application source files.
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+
+#
+# Test-cases:
+#
+TEST_SRCS += account.c
+TEST_SRCS += aulevel.c
+TEST_SRCS += call.c
+TEST_SRCS += cmd.c
+TEST_SRCS += contact.c
+TEST_SRCS += cplusplus.c
+TEST_SRCS += message.c
+TEST_SRCS += mos.c
+TEST_SRCS += net.c
+TEST_SRCS += play.c
+TEST_SRCS += ua.c
+ifneq ($(USE_VIDEO),)
+TEST_SRCS += video.c
+endif
+
+
+#
+# Mocks
+#
+TEST_SRCS += mock/dnssrv.c
+
+TEST_SRCS += sip/aor.c
+TEST_SRCS += sip/auth.c
+TEST_SRCS += sip/domain.c
+TEST_SRCS += sip/location.c
+TEST_SRCS += sip/sipsrv.c
+TEST_SRCS += sip/user.c
+
+ifneq ($(USE_TLS),)
+TEST_SRCS += mock/cert.c
+endif
+
+TEST_SRCS += mock/mock_aucodec.c
+TEST_SRCS += mock/mock_auplay.c
+TEST_SRCS += mock/mock_ausrc.c
+ifneq ($(USE_VIDEO),)
+TEST_SRCS += mock/mock_vidsrc.c
+TEST_SRCS += mock/mock_vidcodec.c
+TEST_SRCS += mock/mock_vidisp.c
+endif
+
+TEST_SRCS += test.c
+
+TEST_SRCS += main.c
diff --git a/test/test.c b/test/test.c
new file mode 100644
index 0000000..d1fa3ad
--- /dev/null
+++ b/test/test.c
@@ -0,0 +1,109 @@
+#include <math.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+static void timeout_handler(void *arg)
+{
+ int *err = arg;
+
+ warning("selftest: re_main() loop timed out -- test hung..\n");
+
+ *err = ETIMEDOUT;
+
+ re_cancel();
+}
+
+
+static void signal_handler(int sig)
+{
+ re_fprintf(stderr, "test interrupted by signal %d\n", sig);
+ re_cancel();
+}
+
+
+int re_main_timeout(uint32_t timeout_ms)
+{
+ struct tmr tmr;
+ int err = 0;
+
+ tmr_init(&tmr);
+
+ tmr_start(&tmr, timeout_ms, timeout_handler, &err);
+ re_main(signal_handler);
+
+ tmr_cancel(&tmr);
+ return err;
+}
+
+
+bool test_cmp_double(double a, double b, double precision)
+{
+ return fabs(a - b) < precision;
+}
+
+
+void test_hexdump_dual(FILE *f,
+ const void *ep, size_t elen,
+ const void *ap, size_t alen)
+{
+ const uint8_t *ebuf = ep;
+ const uint8_t *abuf = ap;
+ size_t i, j, len;
+#define WIDTH 8
+
+ if (!f || !ep || !ap)
+ return;
+
+ len = max(elen, alen);
+
+ (void)re_fprintf(f, "\nOffset: Expected (%zu bytes): "
+ " Actual (%zu bytes):\n", elen, alen);
+
+ for (i=0; i < len; i += WIDTH) {
+
+ (void)re_fprintf(f, "0x%04zx ", i);
+
+ for (j=0; j<WIDTH; j++) {
+ const size_t pos = i+j;
+ if (pos < elen) {
+ bool wrong = pos >= alen;
+
+ if (wrong)
+ (void)re_fprintf(f, "\x1b[35m");
+ (void)re_fprintf(f, " %02x", ebuf[pos]);
+ if (wrong)
+ (void)re_fprintf(f, "\x1b[;m");
+ }
+ else
+ (void)re_fprintf(f, " ");
+ }
+
+ (void)re_fprintf(f, " ");
+
+ for (j=0; j<WIDTH; j++) {
+ const size_t pos = i+j;
+ if (pos < alen) {
+ bool wrong;
+
+ if (pos < elen)
+ wrong = ebuf[pos] != abuf[pos];
+ else
+ wrong = true;
+
+ if (wrong)
+ (void)re_fprintf(f, "\x1b[33m");
+ (void)re_fprintf(f, " %02x", abuf[pos]);
+ if (wrong)
+ (void)re_fprintf(f, "\x1b[;m");
+ }
+ else
+ (void)re_fprintf(f, " ");
+ }
+
+ (void)re_fprintf(f, "\n");
+ }
+
+ (void)re_fprintf(f, "\n");
+}
diff --git a/test/test.h b/test/test.h
new file mode 100644
index 0000000..f276a8d
--- /dev/null
+++ b/test/test.h
@@ -0,0 +1,224 @@
+/**
+ * @file test.h Selftest for Baresip core -- internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#define ASSERT_TRUE(cond) \
+ if (!(cond)) { \
+ warning("selftest: ASSERT_TRUE: %s:%u:\n", \
+ __FILE__, __LINE__); \
+ err = EINVAL; \
+ goto out; \
+ }
+
+#define ASSERT_EQ(expected, actual) \
+ if ((expected) != (actual)) { \
+ warning("selftest: ASSERT_EQ: %s:%u:" \
+ " expected=%d, actual=%d\n", \
+ __FILE__, __LINE__, \
+ (int)(expected), (int)(actual)); \
+ err = EINVAL; \
+ goto out; \
+ }
+
+#define ASSERT_DOUBLE_EQ(expected, actual, prec) \
+ if (!test_cmp_double((expected), (actual), (prec))) { \
+ warning("selftest: ASSERT_DOUBLE_EQ: %s:%u:" \
+ " expected=%f, actual=%f\n", \
+ __FILE__, __LINE__, \
+ (double)(expected), (double)(actual)); \
+ err = EINVAL; \
+ goto out; \
+ }
+
+#define ASSERT_STREQ(expected, actual) \
+ if (0 != str_cmp((expected), (actual))) { \
+ warning("selftest: ASSERT_STREQ: %s:%u:" \
+ " expected = '%s', actual = '%s'\n", \
+ __FILE__, __LINE__, \
+ (expected), (actual)); \
+ err = EBADMSG; \
+ goto out; \
+ }
+
+#define TEST_ERR(err) \
+ if ((err)) { \
+ (void)re_fprintf(stderr, "\n"); \
+ warning("TEST_ERR: %s:%u:" \
+ " (%m)\n", \
+ __FILE__, __LINE__, \
+ (err)); \
+ goto out; \
+ }
+
+#define TEST_MEMCMP(expected, expn, actual, actn) \
+ if (expn != actn || \
+ 0 != memcmp((expected), (actual), (expn))) { \
+ (void)re_fprintf(stderr, "\n"); \
+ warning("TEST_MEMCMP: %s:%u:" \
+ " %s(): failed\n", \
+ __FILE__, __LINE__, __func__); \
+ test_hexdump_dual(stderr, \
+ expected, expn, \
+ actual, actn); \
+ err = EINVAL; \
+ goto out; \
+ }
+
+#define TEST_STRCMP(expected, expn, actual, actn) \
+ if (expn != actn || \
+ 0 != memcmp((expected), (actual), (expn))) { \
+ (void)re_fprintf(stderr, "\n"); \
+ warning("TEST_STRCMP: %s:%u:" \
+ " failed\n", \
+ __FILE__, __LINE__); \
+ (void)re_fprintf(stderr, \
+ "expected string: (%zu bytes)\n" \
+ "\"%b\"\n", \
+ (size_t)(expn), \
+ (expected), (size_t)(expn)); \
+ (void)re_fprintf(stderr, \
+ "actual string: (%zu bytes)\n" \
+ "\"%b\"\n", \
+ (size_t)(actn), \
+ (actual), (size_t)(actn)); \
+ err = EINVAL; \
+ goto out; \
+ }
+
+
+/* helpers */
+
+int re_main_timeout(uint32_t timeout_ms);
+bool test_cmp_double(double a, double b, double precision);
+void test_hexdump_dual(FILE *f,
+ const void *ep, size_t elen,
+ const void *ap, size_t alen);
+
+
+#ifdef USE_TLS
+extern const char test_certificate[];
+#endif
+
+
+/*
+ * Mock DNS-Server
+ */
+
+struct dns_server {
+ struct udp_sock *us;
+ struct sa addr;
+ struct list rrl;
+ bool rotate;
+};
+
+int dns_server_alloc(struct dns_server **srvp, bool rotate);
+int dns_server_add_a(struct dns_server *srv,
+ const char *name, uint32_t addr);
+int dns_server_add_srv(struct dns_server *srv, const char *name,
+ uint16_t pri, uint16_t weight, uint16_t port,
+ const char *target);
+
+/*
+ * Mock Audio-codec
+ */
+
+void mock_aucodec_register(void);
+void mock_aucodec_unregister(void);
+
+/*
+ * Mock Audio-source
+ */
+
+struct ausrc;
+
+int mock_ausrc_register(struct ausrc **ausrcp);
+
+
+/*
+ * Mock Audio-player
+ */
+
+struct auplay;
+
+typedef void (mock_sample_h)(const void *sampv, size_t sampc, void *arg);
+
+int mock_auplay_register(struct auplay **auplayp,
+ mock_sample_h *sampleh, void *arg);
+
+
+/*
+ * Mock Video-source
+ */
+
+struct vidsrc;
+
+int mock_vidsrc_register(struct vidsrc **vidsrcp);
+
+
+/*
+ * Mock Video-codec
+ */
+
+void mock_vidcodec_register(void);
+void mock_vidcodec_unregister(void);
+
+
+/*
+ * Mock Video-display
+ */
+
+struct vidisp;
+
+int mock_vidisp_register(struct vidisp **vidispp);
+
+
+/* test cases */
+
+int test_account(void);
+int test_aulevel(void);
+int test_cmd(void);
+int test_cmd_long(void);
+int test_contact(void);
+int test_ua_alloc(void);
+int test_uag_find_param(void);
+int test_ua_register(void);
+int test_ua_register_dns(void);
+int test_ua_register_auth(void);
+int test_ua_register_auth_dns(void);
+int test_ua_options(void);
+int test_message(void);
+int test_mos(void);
+int test_network(void);
+int test_play(void);
+
+int test_call_answer(void);
+int test_call_reject(void);
+int test_call_af_mismatch(void);
+int test_call_answer_hangup_a(void);
+int test_call_answer_hangup_b(void);
+int test_call_rtp_timeout(void);
+int test_call_multiple(void);
+int test_call_max(void);
+int test_call_dtmf(void);
+int test_call_video(void);
+int test_call_aulevel(void);
+int test_call_progress(void);
+int test_call_format_float(void);
+
+#ifdef USE_VIDEO
+int test_video(void);
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int test_cplusplus(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/test/ua.c b/test/ua.c
new file mode 100644
index 0000000..4f04e4f
--- /dev/null
+++ b/test/ua.c
@@ -0,0 +1,714 @@
+/**
+ * @file test/ua.c Baresip selftest -- User-Agent (UA)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+#include "sip/sipsrv.h"
+
+
+#define MAGIC 0x9044bbfc
+
+
+struct test {
+ struct sip_server *srvv[16];
+ size_t srvc;
+ struct ua *ua;
+ int err;
+ unsigned got_register_ok;
+ unsigned n_resp;
+ uint32_t magic;
+};
+
+
+static void test_init(struct test *t)
+{
+ memset(t, 0, sizeof(*t));
+ t->magic = MAGIC;
+}
+
+
+static void test_reset(struct test *t)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(t->srvv); i++)
+ mem_deref(t->srvv[i]);
+ mem_deref(t->ua);
+
+ memset(t, 0, sizeof(*t));
+}
+
+
+static void test_abort(struct test *t, int err)
+{
+ t->err = err;
+ re_cancel();
+}
+
+
+static void ua_event_handler(struct ua *ua, enum ua_event ev,
+ struct call *call, const char *prm, void *arg)
+{
+ struct test *t = arg;
+ size_t i;
+ int err = 0;
+ (void)call;
+ (void)prm;
+
+ ASSERT_TRUE(t != NULL);
+
+ if (ua != t->ua)
+ return;
+
+ if (ev == UA_EVENT_REGISTER_OK) {
+
+ info("event: Register OK!\n");
+
+ ++t->got_register_ok;
+
+ /* verify register success */
+ ASSERT_TRUE(ua_isregistered(t->ua));
+
+ /* Terminate SIP Server, then De-REGISTER */
+ for (i=0; i<t->srvc; i++)
+ t->srvv[i]->terminate = true;
+
+ t->ua = mem_deref(t->ua);
+ }
+ else if (ev == UA_EVENT_REGISTER_FAIL) {
+
+ err = EAUTH;
+ re_cancel();
+ }
+
+ out:
+ if (err) {
+ warning("selftest: event handler error: %m\n", err);
+ t->err = err;
+ }
+}
+
+
+static int reg(enum sip_transp tp)
+{
+ struct test t;
+ char aor[256];
+ int err;
+
+ memset(&t, 0, sizeof t);
+
+ err = sip_server_alloc(&t.srvv[0]);
+ if (err) {
+ warning("failed to create sip server (%d/%m)\n", err, err);
+ goto out;
+ }
+
+ err = sip_server_uri(t.srvv[0], aor, sizeof(aor), tp);
+ TEST_ERR(err);
+
+ err = ua_alloc(&t.ua, aor);
+ TEST_ERR(err);
+
+ err = uag_event_register(ua_event_handler, &t);
+ if (err)
+ goto out;
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ if (err)
+ goto out;
+
+ if (t.err)
+ err = t.err;
+
+ ASSERT_TRUE(t.srvv[0]->n_register_req > 0);
+ ASSERT_EQ(tp, t.srvv[0]->tp_last);
+ ASSERT_TRUE(t.got_register_ok > 0);
+
+ out:
+ if (err) {
+ warning("selftest: ua_register test failed (%m)\n", err);
+ }
+ uag_event_unregister(ua_event_handler);
+ test_reset(&t);
+
+ return err;
+}
+
+
+int test_ua_register(void)
+{
+ int err = 0;
+
+ err = ua_init("test", true, true, true, false);
+ TEST_ERR(err);
+
+ err |= reg(SIP_TRANSP_UDP);
+ err |= reg(SIP_TRANSP_TCP);
+#ifdef USE_TLS
+ err |= reg(SIP_TRANSP_TLS);
+#endif
+
+ ua_close();
+
+ out:
+ return err;
+}
+
+
+int test_ua_alloc(void)
+{
+ struct ua *ua;
+ uint32_t n_uas = list_count(uag_list());
+ int err = 0;
+
+ /* make sure we dont have that UA already */
+ ASSERT_TRUE(NULL == uag_find_aor("sip:user@127.0.0.1"));
+
+ err = ua_alloc(&ua, "Foo <sip:user@127.0.0.1>;regint=0");
+ if (err)
+ return err;
+
+ /* verify this UA-instance */
+ ASSERT_TRUE(!ua_isregistered(ua));
+ ASSERT_STREQ("sip:user@127.0.0.1", ua_aor(ua));
+ ASSERT_TRUE(NULL == ua_call(ua));
+
+ /* verify global UA keeper */
+ ASSERT_EQ((n_uas + 1), list_count(uag_list()));
+ ASSERT_TRUE(ua == uag_find_aor("sip:user@127.0.0.1"));
+
+ mem_deref(ua);
+
+ ASSERT_EQ((n_uas), list_count(uag_list()));
+
+ out:
+ return err;
+}
+
+
+int test_uag_find_param(void)
+{
+ struct ua *ua1 = NULL, *ua2 = NULL;
+ int err = 0;
+
+ ASSERT_TRUE(NULL == uag_find_param("not", "found"));
+
+ err = ua_alloc(&ua1, "<sip:x@127.0.0.1>;regint=0;abc");
+ err |= ua_alloc(&ua2, "<sip:x@127.0.0.1>;regint=0;def=123");
+ if (err)
+ goto out;
+
+ ASSERT_TRUE(ua1 == uag_find_param("abc", NULL));
+ ASSERT_TRUE(NULL == uag_find_param("abc", "123"));
+ ASSERT_TRUE(ua2 == uag_find_param("def", NULL));
+ ASSERT_TRUE(ua2 == uag_find_param("def", "123"));
+
+ ASSERT_TRUE(NULL == uag_find_param("not", "found"));
+
+ out:
+ mem_deref(ua2);
+ mem_deref(ua1);
+
+ return err;
+}
+
+
+static const char *_sip_transp_srvid(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "_sip._udp";
+ case SIP_TRANSP_TCP: return "_sip._tcp";
+ case SIP_TRANSP_TLS: return "_sips._tcp";
+ default: return "???";
+ }
+}
+
+
+static int reg_dns(enum sip_transp tp)
+{
+ struct dns_server *dnssrv = NULL;
+ struct test t;
+ const char *domain = "test.invalid";
+ struct network *net = baresip_network();
+ unsigned server_count = 1;
+ char aor[256];
+ char srv[256];
+ size_t i;
+ int err;
+
+ memset(&t, 0, sizeof t);
+
+ /*
+ * Setup server-side mocks:
+ */
+
+ err = dns_server_alloc(&dnssrv, true);
+ TEST_ERR(err);
+
+ info("| DNS-server on %J\n", &dnssrv->addr);
+
+ /* NOTE: must be done before ua_init() */
+ err = net_use_nameserver(net, &dnssrv->addr);
+ TEST_ERR(err);
+
+ for (i=0; i<server_count; i++) {
+ struct sa sip_addr;
+ char arec[256];
+
+ err = sip_server_alloc(&t.srvv[i]);
+ if (err) {
+ warning("failed to create sip server (%d/%m)\n",
+ err, err);
+ goto out;
+ }
+
+ err = domain_add(t.srvv[0], domain);
+ if (err)
+ goto out;
+
+ err = sip_transp_laddr(t.srvv[i]->sip, &sip_addr, tp, NULL);
+ TEST_ERR(err);
+
+ info("| SIP-server on %J\n", &sip_addr);
+
+ re_snprintf(arec, sizeof(arec),
+ "alpha%u.%s", i+1, domain);
+
+ re_snprintf(srv, sizeof(srv),
+ "%s.%s", _sip_transp_srvid(tp), domain);
+ err = dns_server_add_srv(dnssrv, srv,
+ 20, 0, sa_port(&sip_addr),
+ arec);
+ TEST_ERR(err);
+
+ err = dns_server_add_a(dnssrv, arec, sa_in(&sip_addr));
+ TEST_ERR(err);
+ }
+ t.srvc = server_count;
+
+ /* NOTE: angel brackets needed to parse ;transport parameter */
+ if (re_snprintf(aor, sizeof(aor), "<sip:x:x@%s;transport=%s>",
+ domain, sip_transp_name(tp)) < 0)
+ return ENOMEM;
+
+ /*
+ * Start SIP client:
+ */
+
+ err = ua_init("test", true, true, true, false);
+ TEST_ERR(err);
+
+ err = ua_alloc(&t.ua, aor);
+ TEST_ERR(err);
+
+ err = uag_event_register(ua_event_handler, &t);
+ if (err)
+ goto out;
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ if (err)
+ goto out;
+
+ if (t.err)
+ err = t.err;
+
+ /* verify that all SIP requests was sent to the first
+ * SIP-server.
+ */
+ ASSERT_TRUE(t.srvv[0]->n_register_req > 0);
+ ASSERT_EQ(tp, t.srvv[0]->tp_last);
+ ASSERT_TRUE(t.got_register_ok > 0);
+
+ out:
+ if (err) {
+ warning("selftest: ua_register test failed (%m)\n", err);
+ }
+ uag_event_unregister(ua_event_handler);
+
+ test_reset(&t);
+
+ ua_stop_all(true);
+ ua_close();
+
+ mem_deref(dnssrv);
+
+ return err;
+}
+
+
+int test_ua_register_dns(void)
+{
+ int err = 0;
+
+ err |= reg_dns(SIP_TRANSP_UDP);
+ TEST_ERR(err);
+ err |= reg_dns(SIP_TRANSP_TCP);
+ TEST_ERR(err);
+#ifdef USE_TLS
+ err |= reg_dns(SIP_TRANSP_TLS);
+ TEST_ERR(err);
+#endif
+
+ out:
+ return err;
+}
+
+
+#define USER "alfredh"
+#define PASS "pass%40word" /* NOTE: url-encoded */
+#define DOMAIN "localhost"
+
+static int reg_auth(enum sip_transp tp)
+{
+ struct sa laddr;
+ struct test t;
+ char aor[256];
+ int err;
+
+ memset(&t, 0, sizeof t);
+
+ err = sip_server_alloc(&t.srvv[0]);
+ if (err) {
+ warning("failed to create sip server (%d/%m)\n", err, err);
+ goto out;
+ }
+
+ err = domain_add(t.srvv[0], DOMAIN);
+ TEST_ERR(err);
+
+ err = user_add(domain_lookup(t.srvv[0], DOMAIN)->ht_usr,
+ "alfredh", "pass@word", DOMAIN);
+ TEST_ERR(err);
+
+ t.srvv[0]->auth_enabled = true;
+
+ err = sip_transp_laddr(t.srvv[0]->sip, &laddr, tp, NULL);
+ if (err)
+ return err;
+
+ /* NOTE: angel brackets needed to parse ;transport parameter */
+ if (re_snprintf(aor, sizeof(aor),
+ "<sip:%s:%s@%s>;outbound=\"sip:%J;transport=%s\"",
+ USER,
+ PASS,
+ DOMAIN,
+ &laddr,
+ sip_transp_name(tp)) < 0)
+ return ENOMEM;
+
+ err = ua_alloc(&t.ua, aor);
+ TEST_ERR(err);
+
+ err = uag_event_register(ua_event_handler, &t);
+ if (err)
+ goto out;
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ if (err)
+ goto out;
+
+ if (t.err) {
+ err = t.err;
+ goto out;
+ }
+
+ ASSERT_TRUE(t.srvv[0]->n_register_req > 0);
+ ASSERT_EQ(tp, t.srvv[0]->tp_last);
+ ASSERT_TRUE(t.got_register_ok > 0);
+
+ out:
+ if (err) {
+ warning("selftest: ua_register test failed (%m)\n", err);
+ }
+ uag_event_unregister(ua_event_handler);
+ test_reset(&t);
+
+
+ return err;
+}
+
+
+int test_ua_register_auth(void)
+{
+ int err;
+
+ err = ua_init("test", true, true, true, false);
+ TEST_ERR(err);
+
+ err |= reg_auth(SIP_TRANSP_UDP);
+ TEST_ERR(err);
+ err |= reg_auth(SIP_TRANSP_TCP);
+ TEST_ERR(err);
+#ifdef USE_TLS
+ err |= reg_auth(SIP_TRANSP_TLS);
+ TEST_ERR(err);
+#endif
+
+ out:
+ ua_stop_all(true);
+ ua_close();
+
+ return err;
+}
+
+
+static int reg_auth_dns(enum sip_transp tp)
+{
+ struct network *net = baresip_network();
+ struct dns_server *dnssrv = NULL;
+ struct test t;
+ const char *username = "alfredh";
+ const char *password = "password";
+ const char *domain = "test.invalid";
+ unsigned server_count = 2;
+ char aor[256];
+ char srv[256];
+ unsigned i;
+ unsigned total_req = 0;
+ int err;
+
+ memset(&t, 0, sizeof t);
+
+ /*
+ * Setup server-side mocks:
+ */
+
+ err = dns_server_alloc(&dnssrv, true);
+ TEST_ERR(err);
+
+ info("| DNS-server on %J\n", &dnssrv->addr);
+
+ /* NOTE: must be done before ua_init() */
+ err = net_use_nameserver(net, &dnssrv->addr);
+ TEST_ERR(err);
+
+ for (i=0; i<server_count; i++) {
+ struct sa sip_addr;
+ char arec[256];
+
+ err = sip_server_alloc(&t.srvv[i]);
+ if (err) {
+ warning("failed to create sip server (%d/%m)\n",
+ err, err);
+ goto out;
+ }
+
+ t.srvv[i]->instance = i;
+
+#if 1
+ /* Comment this out to have different random secrets
+ * on each SIP-Server instance */
+ t.srvv[i]->secret = 42;
+#endif
+
+ err = domain_add(t.srvv[i], domain);
+ if (err)
+ goto out;
+
+ err = user_add(domain_lookup(t.srvv[i], domain)->ht_usr,
+ username, password, domain);
+ TEST_ERR(err);
+
+ t.srvv[i]->auth_enabled = true;
+
+ err = sip_transp_laddr(t.srvv[i]->sip, &sip_addr, tp, NULL);
+ TEST_ERR(err);
+
+ info("| SIP-server on %J\n", &sip_addr);
+
+ re_snprintf(arec, sizeof(arec),
+ "alpha%u.%s", i+1, domain);
+
+ re_snprintf(srv, sizeof(srv),
+ "%s.%s", _sip_transp_srvid(tp), domain);
+ err = dns_server_add_srv(dnssrv, srv,
+ 20, 0, sa_port(&sip_addr),
+ arec);
+ TEST_ERR(err);
+
+ err = dns_server_add_a(dnssrv, arec, sa_in(&sip_addr));
+ TEST_ERR(err);
+ }
+ t.srvc = server_count;
+
+ /* NOTE: angel brackets needed to parse ;transport parameter */
+ if (re_snprintf(aor, sizeof(aor), "<sip:%s:%s@%s;transport=%s>",
+ username, password, domain, sip_transp_name(tp)) < 0)
+ return ENOMEM;
+
+ /*
+ * Start SIP client:
+ */
+
+ err = ua_init("test", true, true, true, false);
+ TEST_ERR(err);
+
+ err = ua_alloc(&t.ua, aor);
+ TEST_ERR(err);
+
+ err = uag_event_register(ua_event_handler, &t);
+ if (err)
+ goto out;
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ if (err)
+ goto out;
+
+ if (t.err) {
+ err = t.err;
+ goto out;
+ }
+
+ /* verify that all SIP requests was sent to the
+ * SIP-servers.
+ */
+ for (i=0; i<server_count; i++) {
+
+ total_req += t.srvv[i]->n_register_req;
+
+ if (t.srvv[i]->n_register_req) {
+ ASSERT_EQ(tp, t.srvv[i]->tp_last);
+ }
+ }
+ ASSERT_TRUE(total_req >= 2);
+ ASSERT_TRUE(t.got_register_ok > 0);
+
+ out:
+ if (err) {
+ warning("selftest: ua_register test failed (%m)\n", err);
+ }
+ uag_event_unregister(ua_event_handler);
+
+ test_reset(&t);
+
+ ua_stop_all(true);
+ ua_close();
+
+ mem_deref(dnssrv);
+
+ return err;
+}
+
+
+int test_ua_register_auth_dns(void)
+{
+ int err = 0;
+
+ err |= reg_auth_dns(SIP_TRANSP_UDP);
+ TEST_ERR(err);
+ err |= reg_auth_dns(SIP_TRANSP_TCP);
+ TEST_ERR(err);
+#ifdef USE_TLS
+ err |= reg_auth_dns(SIP_TRANSP_TLS);
+ TEST_ERR(err);
+#endif
+
+ out:
+ return err;
+}
+
+
+static void options_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct test *t = arg;
+ const struct sip_hdr *hdr;
+ struct pl content;
+ uint32_t clen;
+
+ ASSERT_EQ(MAGIC, t->magic);
+
+ if (err) {
+ test_abort(t, err);
+ return;
+ }
+ if (msg->scode != 200) {
+ test_abort(t, EPROTO);
+ return;
+ }
+
+ ++t->n_resp;
+
+ /* Verify SIP headers */
+
+ ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "INVITE"));
+ ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "ACK"));
+ ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "BYE"));
+ ASSERT_TRUE(sip_msg_hdr_has_value(msg, SIP_HDR_ALLOW, "CANCEL"));
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+ ASSERT_TRUE(hdr != NULL);
+ ASSERT_TRUE(hdr->val.l != 0);
+
+ ASSERT_EQ(0, pl_strcasecmp(&msg->ctyp.type, "application"));
+ ASSERT_EQ(0, pl_strcasecmp(&msg->ctyp.subtype, "sdp"));
+
+ clen = pl_u32(&msg->clen);
+ ASSERT_TRUE(clen > 0);
+
+ /* Verify the SDP content */
+
+ pl_set_mbuf(&content, msg->mb);
+
+ ASSERT_EQ(0, re_regex(content.p, content.l, "v=0"));
+ ASSERT_EQ(0, re_regex(content.p, content.l, "a=tool:baresip"));
+ ASSERT_EQ(0, re_regex(content.p, content.l, "m=audio"));
+
+ out:
+ if (err)
+ t->err = err;
+ re_cancel();
+}
+
+
+int test_ua_options(void)
+{
+ struct test t;
+ struct sa laddr;
+ char uri[256];
+ int n, err = 0;
+
+ test_init(&t);
+
+ err = ua_init("test", true, false, false, false);
+ TEST_ERR(err);
+
+ err = sip_transp_laddr(uag_sip(), &laddr, SIP_TRANSP_UDP, NULL);
+ TEST_ERR(err);
+
+ err = ua_alloc(&t.ua, "Foo <sip:user@127.0.0.1>;regint=0");
+ TEST_ERR(err);
+
+ n = re_snprintf(uri, sizeof(uri),
+ "sip:user@127.0.0.1:%u", sa_port(&laddr));
+ ASSERT_TRUE(n > 0);
+
+ err = ua_options_send(t.ua, uri, options_resp_handler, &t);
+ TEST_ERR(err);
+
+ /* run main-loop with timeout, wait for events */
+ err = re_main_timeout(5000);
+ if (err)
+ goto out;
+
+ TEST_ERR(t.err);
+
+ /* verify after test is complete */
+ ASSERT_EQ(1, t.n_resp);
+
+ out:
+ test_reset(&t);
+
+ ua_stop_all(true);
+ ua_close();
+
+ return err;
+}
diff --git a/test/video.c b/test/video.c
new file mode 100644
index 0000000..09c5e77
--- /dev/null
+++ b/test/video.c
@@ -0,0 +1,38 @@
+/**
+ * @file test/video.c Baresip selftest -- video
+ *
+ * Copyright (C) 2010 - 2017 Creytiv.com
+ */
+
+#include <re.h>
+#include <baresip.h>
+#include "test.h"
+
+
+#define DEBUG_MODULE "video"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+int test_video(void)
+{
+ int err = 0;
+
+ /* test with framerate of zero */
+ ASSERT_EQ(0, video_calc_rtp_timestamp(1, 0));
+
+ ASSERT_EQ( 0, video_calc_rtp_timestamp( 0, 30));
+ ASSERT_EQ( 3000, video_calc_rtp_timestamp( 1, 30));
+ ASSERT_EQ( 30000, video_calc_rtp_timestamp( 10, 30));
+ ASSERT_EQ( 300000, video_calc_rtp_timestamp( 100, 30));
+ ASSERT_EQ( 3000000, video_calc_rtp_timestamp( 1000, 30));
+ ASSERT_EQ( 30000000, video_calc_rtp_timestamp( 10000, 30));
+ ASSERT_EQ( 300000000, video_calc_rtp_timestamp( 100000, 30));
+ ASSERT_EQ(3000000000, video_calc_rtp_timestamp(1000000, 30));
+
+ ASSERT_EQ(4294965000, video_calc_rtp_timestamp(1431655, 30));
+ ASSERT_EQ( 704, video_calc_rtp_timestamp(1431656, 30));
+
+ out:
+ return err;
+}