├── .gitignore ├── Account.cs ├── Call.cs ├── LICENSE ├── Linphone.cs ├── Phone.cs ├── README.md ├── lib └── linphone.zip └── sipdotnet.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # General 3 | ############################################################################## 4 | 5 | # OS junk files 6 | [Tt]humbs.db 7 | *.DS_Store 8 | 9 | # Visual Studio / MonoDevelop 10 | *.[Oo]bj 11 | *.exe 12 | *.dll 13 | *.pdb 14 | *.user 15 | *.aps 16 | *.pch 17 | *.vspscc 18 | *.vssscc 19 | *_i.c 20 | *_p.c 21 | *.ncb 22 | *.suo 23 | *.tlb 24 | *.tlh 25 | *.bak 26 | *.ilk 27 | *.log 28 | *.lib 29 | *.sbr 30 | *.sdf 31 | *.opensdf 32 | *.resources 33 | *.res 34 | ipch/ 35 | obj/ 36 | [Bb]in 37 | [Dd]ebug*/ 38 | [Rr]elease*/ 39 | Ankh.NoLoad 40 | *.gpState 41 | .vscode/ 42 | *.exp 43 | 44 | # Tooling 45 | _ReSharper*/ 46 | *.resharper 47 | [Tt]est[Rr]esult* 48 | *.orig 49 | *.rej 50 | 51 | # NuGet packages 52 | !.nuget/* 53 | [Pp]ackages/* 54 | ![Pp]ackages/repositories.config 55 | 56 | # Temporary Files 57 | ~.* 58 | ~$* 59 | 60 | # Autotools-generated files 61 | /Makefile 62 | Makefile.in 63 | aclocal.m4 64 | autom4te.cache 65 | /build/ 66 | config.cache 67 | config.guess 68 | config.h 69 | config.h.in 70 | config.log 71 | config.status 72 | config.sub 73 | configure 74 | configure.scan 75 | cygconfig.h 76 | depcomp 77 | install-sh 78 | libtool 79 | ltmain.sh 80 | missing 81 | mkinstalldirs 82 | releases 83 | stamp-h 84 | stamp-h1 85 | stamp-h.in 86 | /test-driver 87 | *~ 88 | *.swp 89 | *.o 90 | .deps 91 | 92 | # Libtool 93 | libtool.m4 94 | lt~obsolete.m4 95 | ltoptions.m4 96 | ltsugar.m4 97 | ltversion.m4 98 | 99 | # Dolt (libtool replacement) 100 | doltlibtool 101 | doltcompile 102 | 103 | # pkg-config 104 | *.pc 105 | 106 | # Emacs 107 | semantic.cache 108 | 109 | # gtags 110 | GPATH 111 | GRTAGS 112 | GSYMS 113 | GTAGS 114 | 115 | # Doxygen 116 | docs/doxygen* 117 | docs/perlmod* 118 | 119 | 120 | ############################################################################## 121 | # Mono-specific patterns 122 | ############################################################################## 123 | 124 | .dirstamp 125 | compile 126 | mono.h 127 | mono-*.tar.* 128 | tmpinst-dir.stamp 129 | msvc/scripts/inputs/ 130 | extensions-config.h 131 | 132 | -------------------------------------------------------------------------------- /Account.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace sipdotnet 5 | { 6 | public class Account 7 | { 8 | string username, password, server; 9 | int port = 5060, id; 10 | 11 | public string Username { 12 | get { 13 | return username; 14 | } 15 | set { 16 | username = value; 17 | } 18 | } 19 | 20 | public string Password { 21 | get { 22 | return password; 23 | } 24 | set { 25 | password = value; 26 | } 27 | } 28 | 29 | public string Server { 30 | get { 31 | return server; 32 | } 33 | set { 34 | server = value; 35 | } 36 | } 37 | 38 | public int Id { 39 | get { 40 | return id; 41 | } 42 | set { 43 | id = value; 44 | } 45 | } 46 | 47 | public int Port { 48 | get { 49 | return port; 50 | } 51 | set { 52 | port = value; 53 | } 54 | } 55 | 56 | public string Identity 57 | { 58 | get { return "sip:" + this.username + "@" + this.server; } 59 | } 60 | 61 | public Account(string username, string password, string server, int port) 62 | { 63 | Debug.Assert(!String.IsNullOrEmpty(username), "User cannot be empty."); 64 | Debug.Assert(!String.IsNullOrEmpty(server), "Server cannot be empty."); 65 | 66 | this.username = username; 67 | this.password = password; 68 | this.server = server; 69 | this.port = port; 70 | } 71 | 72 | public Account(string username, string password, string server) : this(username, password, server, 5060) { } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Call.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace sipdotnet 4 | { 5 | public class Call 6 | { 7 | public enum CallType 8 | { 9 | None, 10 | Incoming, 11 | Outcoming 12 | }; 13 | 14 | protected CallType calltype = CallType.None; 15 | 16 | public CallType GetCallType () 17 | { 18 | return this.calltype; 19 | } 20 | 21 | public enum CallState 22 | { 23 | None, 24 | Loading, 25 | Active, 26 | Completed, 27 | Error 28 | }; 29 | 30 | protected CallState callstate = CallState.None; 31 | 32 | public CallState GetState () 33 | { 34 | return this.callstate; 35 | } 36 | 37 | protected string from; 38 | 39 | public string GetFrom () 40 | { 41 | return this.from; 42 | } 43 | 44 | protected string to; 45 | 46 | public string GetTo () 47 | { 48 | return this.to; 49 | } 50 | 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /Linphone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using System.Collections.Generic; 5 | 6 | namespace sipdotnet 7 | { 8 | public class Linphone 9 | { 10 | 11 | #if (WINDOWS) 12 | const string LIBNAME = "linphone.dll"; 13 | #else 14 | const string LIBNAME = "liblinphone"; 15 | #endif 16 | 17 | #region Import 18 | 19 | /// 20 | /// Disable a sip transport 21 | /// 22 | const int LC_SIP_TRANSPORT_DISABLED = 0; 23 | 24 | /// 25 | /// Randomly chose a sip port for this transport 26 | /// 27 | const int LC_SIP_TRANSPORT_RANDOM = -1; 28 | 29 | /// 30 | /// Don't create any server socket for this transport, ie don't bind on any port 31 | /// 32 | const int LC_SIP_TRANSPORT_DONTBIND = -2; 33 | 34 | /// 35 | /// Linphone core SIP transport ports 36 | /// http://www.linphone.org/docs/liblinphone/struct__LinphoneSipTransports.html 37 | /// 38 | struct LCSipTransports 39 | { 40 | /// 41 | /// UDP port to listening on, negative value if not set 42 | /// 43 | public int udp_port; 44 | 45 | /// 46 | /// TCP port to listening on, negative value if not set 47 | /// 48 | public int tcp_port; 49 | 50 | /// 51 | /// DTLS port to listening on, negative value if not set 52 | /// 53 | public int dtls_port; 54 | 55 | /// 56 | /// TLS port to listening on, negative value if not set 57 | /// 58 | public int tls_port; 59 | }; 60 | 61 | /// 62 | /// Describes proxy registration states 63 | /// http://www.linphone.org/docs/liblinphone/group__proxies.html 64 | /// 65 | public enum LinphoneRegistrationState 66 | { 67 | /// 68 | /// Initial state for registrations 69 | /// 70 | LinphoneRegistrationNone, 71 | 72 | /// 73 | /// Registration is in progress 74 | /// 75 | LinphoneRegistrationProgress, 76 | 77 | /// 78 | /// Registration is successful 79 | /// 80 | LinphoneRegistrationOk, 81 | 82 | /// 83 | /// Unregistration succeeded 84 | /// 85 | LinphoneRegistrationCleared, 86 | 87 | /// 88 | /// Registration failed 89 | /// 90 | LinphoneRegistrationFailed 91 | }; 92 | 93 | /// 94 | /// Logging level 95 | /// https://github.com/BelledonneCommunications/ortp/blob/master/include/ortp/logging.h 96 | /// https://github.com/BelledonneCommunications/bctoolbox/blob/master/include/bctoolbox/logging.h 97 | /// 98 | public enum OrtpLogLevel 99 | { 100 | DEBUG = 1, 101 | TRACE = 1 << 1, 102 | MESSAGE = 1 << 2, 103 | WARNING = 1 << 3, 104 | ERROR = 1 << 4, 105 | FATAL = 1 << 5, 106 | END = 1 << 6 107 | }; 108 | 109 | /// 110 | /// Represents the different state a call can reach into 111 | /// http://www.linphone.org/docs/liblinphone/group__call__control.html 112 | /// 113 | public enum LinphoneCallState 114 | { 115 | /// 116 | /// Initial call state 117 | /// 118 | LinphoneCallIdle, 119 | 120 | /// 121 | /// This is a new incoming call 122 | /// 123 | LinphoneCallIncomingReceived, 124 | 125 | /// 126 | /// An outgoing call is started 127 | /// 128 | LinphoneCallOutgoingInit, 129 | 130 | /// 131 | /// An outgoing call is in progress 132 | /// 133 | LinphoneCallOutgoingProgress, 134 | 135 | /// 136 | /// An outgoing call is ringing at remote end 137 | /// 138 | LinphoneCallOutgoingRinging, 139 | 140 | /// 141 | /// An outgoing call is proposed early media 142 | /// 143 | LinphoneCallOutgoingEarlyMedia, 144 | 145 | /// 146 | /// Connected, the call is answered 147 | /// 148 | LinphoneCallConnected, 149 | 150 | /// 151 | /// The media streams are established and running 152 | /// 153 | LinphoneCallStreamsRunning, 154 | 155 | /// 156 | /// The call is pausing at the initiative of local end 157 | /// 158 | LinphoneCallPausing, 159 | 160 | /// 161 | /// The call is paused, remote end has accepted the pause 162 | /// 163 | LinphoneCallPaused, 164 | 165 | /// 166 | /// The call is being resumed by local end 167 | /// 168 | LinphoneCallResuming, 169 | 170 | /// 171 | /// 173 | LinphoneCallRefered, 174 | 175 | /// 176 | /// The call encountered an error 177 | /// 178 | LinphoneCallError, 179 | 180 | /// 181 | /// The call ended normally 182 | /// 183 | LinphoneCallEnd, 184 | 185 | /// 186 | /// The call is paused by remote end 187 | /// 188 | LinphoneCallPausedByRemote, 189 | 190 | /// 191 | /// The call's parameters change is requested by remote end, used for example when video is added by remote 192 | /// 193 | LinphoneCallUpdatedByRemote, 194 | 195 | /// 196 | /// We are proposing early media to an incoming call 197 | /// 198 | LinphoneCallIncomingEarlyMedia, 199 | 200 | /// 201 | /// A call update has been initiated by us 202 | /// 203 | LinphoneCallUpdating, 204 | 205 | /// 206 | /// The call object is no more retained by the core 207 | /// 208 | LinphoneCallReleased 209 | }; 210 | 211 | /// 212 | /// Holds all callbacks that the application should implement. None is mandatory. 213 | /// http://www.linphone.org/docs/liblinphone/struct__LinphoneCoreVTable.html 214 | /// 215 | struct LinphoneCoreVTable 216 | { 217 | /// 218 | /// Notifies global state changes 219 | /// 220 | public IntPtr global_state_changed; 221 | 222 | /// 223 | /// Notifies registration state changes 224 | /// 225 | public IntPtr registration_state_changed; 226 | 227 | /// 228 | /// Notifies call state changes 229 | /// 230 | public IntPtr call_state_changed; 231 | 232 | /// 233 | /// Notify received presence events 234 | /// 235 | public IntPtr notify_presence_received; 236 | 237 | /// 238 | /// Notify received presence events 239 | /// 240 | public IntPtr notify_presence_received_for_uri_or_tel; 241 | 242 | /// 243 | /// Notify about pending presence subscription request 244 | /// 245 | public IntPtr new_subscription_requested; 246 | 247 | /// 248 | /// Ask the application some authentication information 249 | /// 250 | public IntPtr auth_info_requested; 251 | 252 | /// 253 | /// Ask the application some authentication information 254 | /// 255 | public IntPtr authentication_requested; 256 | 257 | /// 258 | /// Notifies that call log list has been updated 259 | /// 260 | public IntPtr call_log_updated; 261 | 262 | /// 263 | /// A message is received, can be text or external body 264 | /// 265 | public IntPtr message_received; 266 | 267 | /// 268 | /// An encrypted message is received but we can't decrypt it 269 | /// 270 | public IntPtr message_received_unable_decrypt; 271 | 272 | /// 273 | /// An is-composing notification has been received 274 | /// 275 | public IntPtr is_composing_received; 276 | 277 | /// 278 | /// A dtmf has been received received 279 | /// 280 | public IntPtr dtmf_received; 281 | 282 | /// 283 | /// An out of call refer was received 284 | /// 285 | public IntPtr refer_received; 286 | 287 | /// 288 | /// Notifies on change in the encryption of call streams 289 | /// 290 | public IntPtr call_encryption_changed; 291 | 292 | /// 293 | /// Notifies when a transfer is in progress 294 | /// 295 | public IntPtr transfer_state_changed; 296 | 297 | /// 298 | /// A LinphoneFriend's BuddyInfo has changed 299 | /// 300 | public IntPtr buddy_info_updated; 301 | 302 | /// 303 | /// Notifies on refreshing of call's statistics. 304 | /// 305 | public IntPtr call_stats_updated; 306 | 307 | /// 308 | /// Notifies an incoming informational message received. 309 | /// 310 | public IntPtr info_received; 311 | 312 | /// 313 | /// Notifies subscription state change 314 | /// 315 | public IntPtr subscription_state_changed; 316 | 317 | /// 318 | /// Notifies a an event notification, see linphone_core_subscribe() 319 | /// 320 | public IntPtr notify_received; 321 | 322 | /// 323 | /// Notifies publish state change (only from #LinphoneEvent api) 324 | /// 325 | public IntPtr publish_state_changed; 326 | 327 | /// 328 | /// Notifies configuring status changes 329 | /// 330 | public IntPtr configuring_status; 331 | 332 | /// 333 | /// Callback that notifies various events with human readable text (deprecated) 334 | /// 335 | [System.Obsolete] 336 | public IntPtr display_status; 337 | 338 | /// 339 | /// Callback to display a message to the user (deprecated) 340 | /// 341 | [System.Obsolete] 342 | public IntPtr display_message; 343 | 344 | /// 345 | /// Callback to display a warning to the user (deprecated) 346 | /// 347 | [System.Obsolete] 348 | public IntPtr display_warning; 349 | 350 | [System.Obsolete] 351 | public IntPtr display_url; 352 | 353 | /// 354 | /// Notifies the application that it should show up 355 | /// 356 | [System.Obsolete] 357 | public IntPtr show; 358 | 359 | /// 360 | /// Use #message_received instead
A text message has been received 361 | ///
362 | [System.Obsolete] 363 | public IntPtr text_received; 364 | 365 | /// 366 | /// Callback to store file received attached to a LinphoneChatMessage 367 | /// 368 | [System.Obsolete] 369 | public IntPtr file_transfer_recv; 370 | 371 | /// 372 | /// Callback to collect file chunk to be sent for a LinphoneChatMessage 373 | /// 374 | [System.Obsolete] 375 | public IntPtr file_transfer_send; 376 | 377 | /// 378 | /// Callback to indicate file transfer progress 379 | /// 380 | [System.Obsolete] 381 | public IntPtr file_transfer_progress_indication; 382 | 383 | /// 384 | /// Callback to report IP network status (I.E up/down) 385 | /// 386 | public IntPtr network_reachable; 387 | 388 | /// 389 | /// Callback to upload collected logs 390 | /// 391 | public IntPtr log_collection_upload_state_changed; 392 | 393 | /// 394 | /// Callback to indicate log collection upload progress 395 | /// 396 | public IntPtr log_collection_upload_progress_indication; 397 | 398 | public IntPtr friend_list_created; 399 | 400 | public IntPtr friend_list_removed; 401 | 402 | /// 403 | /// User data associated with the above callbacks 404 | /// 405 | public IntPtr user_data; 406 | }; 407 | 408 | #region Initializing 409 | 410 | // http://www.linphone.org/docs/liblinphone/group__initializing.html 411 | 412 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 413 | [System.Obsolete] 414 | static extern void linphone_core_enable_logs (IntPtr FILE); 415 | 416 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 417 | [System.Obsolete] 418 | static extern void linphone_core_disable_logs (); 419 | 420 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 421 | [System.Obsolete] 422 | static extern IntPtr linphone_core_new (IntPtr vtable, string config_path, string factory_config_path, IntPtr userdata); 423 | 424 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 425 | static extern void linphone_core_unref (IntPtr lc); 426 | 427 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 428 | static extern void linphone_core_iterate (IntPtr lc); 429 | 430 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 431 | static extern void linphone_core_set_log_level (OrtpLogLevel loglevel); 432 | 433 | #endregion 434 | 435 | #region Proxies 436 | 437 | // http://www.linphone.org/docs/liblinphone/group__proxies.html 438 | 439 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 440 | static extern IntPtr linphone_core_create_proxy_config (IntPtr lc); 441 | 442 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 443 | static extern int linphone_proxy_config_set_identity (IntPtr obj, string identity); 444 | 445 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 446 | static extern int linphone_proxy_config_set_server_addr (IntPtr obj, string server_addr); 447 | 448 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 449 | static extern void linphone_proxy_config_enable_register (IntPtr obj, bool val); 450 | 451 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 452 | static extern int linphone_core_add_proxy_config (IntPtr lc, IntPtr cfg); 453 | 454 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 455 | static extern void linphone_core_set_default_proxy_config (IntPtr lc, IntPtr config); 456 | 457 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 458 | [System.Obsolete] 459 | static extern int linphone_core_get_default_proxy (IntPtr lc, ref IntPtr config); 460 | 461 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 462 | static extern bool linphone_proxy_config_is_registered (IntPtr config); 463 | 464 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 465 | static extern void linphone_proxy_config_edit (IntPtr config); 466 | 467 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 468 | static extern int linphone_proxy_config_done (IntPtr config); 469 | 470 | #endregion 471 | 472 | #region Network 473 | 474 | // http://www.linphone.org/docs/liblinphone/group__network__parameters.html 475 | 476 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 477 | static extern int linphone_core_set_sip_transports (IntPtr lc, IntPtr tr_config); 478 | 479 | #endregion 480 | 481 | #region SIP 482 | 483 | // http://www.linphone.org/docs/liblinphone/group__linphone__address.html 484 | 485 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 486 | [System.Obsolete] 487 | static extern void linphone_address_destroy (IntPtr u); 488 | 489 | #endregion 490 | 491 | #region Miscenalleous 492 | 493 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 494 | static extern void linphone_core_set_user_agent (IntPtr lc, string ua_name, string version); 495 | 496 | #endregion 497 | 498 | #region Calls 499 | 500 | // http://www.linphone.org/docs/liblinphone/group__call__control.html 501 | 502 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 503 | static extern IntPtr linphone_core_create_call_params (IntPtr lc, IntPtr call); 504 | 505 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 506 | static extern void linphone_call_params_enable_video (IntPtr lc, bool enabled); 507 | 508 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 509 | static extern void linphone_call_params_enable_early_media_sending (IntPtr lc, bool enabled); 510 | 511 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 512 | static extern IntPtr linphone_core_invite_with_params (IntPtr lc, string url, IntPtr callparams); 513 | 514 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 515 | static extern void linphone_call_params_unref (IntPtr callparams); 516 | 517 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 518 | static extern int linphone_core_terminate_call (IntPtr lc, IntPtr call); 519 | 520 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 521 | static extern IntPtr linphone_call_ref(IntPtr call); 522 | 523 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 524 | static extern void linphone_call_unref (IntPtr call); 525 | 526 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 527 | static extern int linphone_core_terminate_all_calls (IntPtr lc); 528 | 529 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 530 | static extern IntPtr linphone_call_get_remote_address_as_string (IntPtr call); 531 | 532 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 533 | static extern int linphone_core_accept_call_with_params (IntPtr lc, IntPtr call, IntPtr callparams); 534 | 535 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 536 | static extern void linphone_call_params_set_record_file (IntPtr callparams, string filename); 537 | 538 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 539 | static extern IntPtr linphone_call_params_get_record_file (IntPtr callparams); 540 | 541 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 542 | static extern void linphone_call_params_enable_audio (IntPtr callparams, bool enabled); 543 | 544 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 545 | static extern int linphone_call_send_dtmfs (IntPtr call, string dtmfs); 546 | 547 | #endregion 548 | 549 | #region Authentication 550 | 551 | // http://www.linphone.org/docs/liblinphone/group__authentication.html 552 | 553 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 554 | static extern void linphone_core_add_auth_info (IntPtr lc, IntPtr info); 555 | 556 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 557 | static extern IntPtr linphone_auth_info_new (string username, string userid, string passwd, string ha1, string realm, string domain); 558 | 559 | #endregion 560 | 561 | #region Calls miscenalleous 562 | 563 | // http://www.linphone.org/docs/liblinphone/group__call__misc.html 564 | 565 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 566 | static extern void linphone_call_start_recording (IntPtr call); 567 | 568 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 569 | static extern void linphone_call_stop_recording (IntPtr call); 570 | 571 | #endregion 572 | 573 | #region Media 574 | 575 | // http://www.linphone.org/docs/liblinphone/group__media__parameters.html 576 | 577 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 578 | static extern void linphone_core_set_play_file (IntPtr lc, string file); 579 | 580 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 581 | static extern void linphone_core_set_record_file (IntPtr lc, string file); 582 | 583 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 584 | static extern void linphone_core_set_ring (IntPtr lc, string file); 585 | 586 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 587 | static extern void linphone_core_set_remote_ringback_tone (IntPtr lc, string file); 588 | 589 | [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)] 590 | static extern void linphone_core_set_ringback (IntPtr lc, string file); 591 | 592 | #endregion 593 | 594 | #endregion 595 | 596 | class LinphoneCall : Call 597 | { 598 | IntPtr linphoneCallPtr; 599 | 600 | public IntPtr LinphoneCallPtr { 601 | get { 602 | return linphoneCallPtr; 603 | } 604 | set { 605 | linphoneCallPtr = value; 606 | } 607 | } 608 | 609 | public void SetCallType (CallType type) 610 | { 611 | this.calltype = type; 612 | } 613 | 614 | public void SetCallState (CallState state) 615 | { 616 | this.callstate = state; 617 | } 618 | 619 | public void SetFrom (string from) 620 | { 621 | this.from = from; 622 | } 623 | 624 | public void SetTo (string to) 625 | { 626 | this.to = to; 627 | } 628 | } 629 | 630 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 631 | delegate void LinphoneCoreRegistrationStateChangedCb (IntPtr lc, IntPtr cfg, LinphoneRegistrationState cstate, string message); 632 | 633 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 634 | delegate void LinphoneCoreCallStateChangedCb (IntPtr lc, IntPtr call, LinphoneCallState cstate, string message); 635 | 636 | LinphoneCoreRegistrationStateChangedCb registration_state_changed; 637 | LinphoneCoreCallStateChangedCb call_state_changed; 638 | IntPtr linphoneCore, callsDefaultParams, proxy_cfg, auth_info, t_configPtr, vtablePtr; 639 | Thread coreLoop; 640 | bool running = true; 641 | string identity, server_addr; 642 | LinphoneCoreVTable vtable; 643 | LCSipTransports t_config; 644 | 645 | List calls = new List (); 646 | 647 | LinphoneCall FindCall (IntPtr call) 648 | { 649 | return calls.Find (delegate(LinphoneCall obj) { 650 | return (obj.LinphoneCallPtr == call); 651 | }); 652 | } 653 | 654 | void SetTimeout (Action callback, int miliseconds) 655 | { 656 | System.Timers.Timer timeout = new System.Timers.Timer (); 657 | timeout.Interval = miliseconds; 658 | timeout.AutoReset = false; 659 | timeout.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) => { 660 | callback (); 661 | }; 662 | timeout.Start (); 663 | } 664 | 665 | public void CreatePhone (string username, string password, string server, int port, string agent, string version) 666 | { 667 | #if (TRACE) 668 | linphone_core_enable_logs (IntPtr.Zero); 669 | linphone_core_set_log_level (OrtpLogLevel.DEBUG); 670 | #else 671 | linphone_core_disable_logs (); 672 | linphone_core_set_log_level (OrtpLogLevel.END); 673 | #endif 674 | 675 | running = true; 676 | registration_state_changed = new LinphoneCoreRegistrationStateChangedCb(OnRegistrationChanged); 677 | call_state_changed = new LinphoneCoreCallStateChangedCb(OnCallStateChanged); 678 | 679 | #pragma warning disable 0612 680 | vtable = new LinphoneCoreVTable() 681 | { 682 | global_state_changed = IntPtr.Zero, 683 | registration_state_changed = Marshal.GetFunctionPointerForDelegate(registration_state_changed), 684 | call_state_changed = Marshal.GetFunctionPointerForDelegate(call_state_changed), 685 | notify_presence_received = IntPtr.Zero, 686 | notify_presence_received_for_uri_or_tel = IntPtr.Zero, 687 | new_subscription_requested = IntPtr.Zero, 688 | auth_info_requested = IntPtr.Zero, 689 | authentication_requested = IntPtr.Zero, 690 | call_log_updated = IntPtr.Zero, 691 | message_received = IntPtr.Zero, 692 | message_received_unable_decrypt = IntPtr.Zero, 693 | is_composing_received = IntPtr.Zero, 694 | dtmf_received = IntPtr.Zero, 695 | refer_received = IntPtr.Zero, 696 | call_encryption_changed = IntPtr.Zero, 697 | transfer_state_changed = IntPtr.Zero, 698 | buddy_info_updated = IntPtr.Zero, 699 | call_stats_updated = IntPtr.Zero, 700 | info_received = IntPtr.Zero, 701 | subscription_state_changed = IntPtr.Zero, 702 | notify_received = IntPtr.Zero, 703 | publish_state_changed = IntPtr.Zero, 704 | configuring_status = IntPtr.Zero, 705 | display_status = IntPtr.Zero, 706 | display_message = IntPtr.Zero, 707 | display_warning = IntPtr.Zero, 708 | display_url = IntPtr.Zero, 709 | show = IntPtr.Zero, 710 | text_received = IntPtr.Zero, 711 | file_transfer_recv = IntPtr.Zero, 712 | file_transfer_send = IntPtr.Zero, 713 | file_transfer_progress_indication = IntPtr.Zero, 714 | network_reachable = IntPtr.Zero, 715 | log_collection_upload_state_changed = IntPtr.Zero, 716 | log_collection_upload_progress_indication = IntPtr.Zero, 717 | friend_list_created = IntPtr.Zero, 718 | friend_list_removed = IntPtr.Zero, 719 | user_data = IntPtr.Zero 720 | }; 721 | #pragma warning restore 0612 722 | 723 | vtablePtr = Marshal.AllocHGlobal(Marshal.SizeOf(vtable)); 724 | Marshal.StructureToPtr(vtable, vtablePtr, false); 725 | 726 | linphoneCore = linphone_core_new(vtablePtr, null, null, IntPtr.Zero); 727 | 728 | coreLoop = new Thread(LinphoneMainLoop); 729 | coreLoop.IsBackground = false; 730 | coreLoop.Start(); 731 | 732 | t_config = new LCSipTransports() 733 | { 734 | udp_port = LC_SIP_TRANSPORT_RANDOM, 735 | tcp_port = LC_SIP_TRANSPORT_RANDOM, 736 | dtls_port = LC_SIP_TRANSPORT_RANDOM, 737 | tls_port = LC_SIP_TRANSPORT_RANDOM 738 | }; 739 | t_configPtr = Marshal.AllocHGlobal(Marshal.SizeOf(t_config)); 740 | Marshal.StructureToPtr (t_config, t_configPtr, false); 741 | linphone_core_set_sip_transports (linphoneCore, t_configPtr); 742 | 743 | linphone_core_set_user_agent (linphoneCore, agent, version); 744 | 745 | callsDefaultParams = linphone_core_create_call_params (linphoneCore, IntPtr.Zero); 746 | linphone_call_params_enable_video (callsDefaultParams, false); 747 | linphone_call_params_enable_audio (callsDefaultParams, true); 748 | linphone_call_params_enable_early_media_sending (callsDefaultParams, true); 749 | 750 | identity = "sip:" + username + "@" + server; 751 | server_addr = "sip:" + server + ":" + port.ToString(); 752 | 753 | auth_info = linphone_auth_info_new (username, null, password, null, null, null); 754 | linphone_core_add_auth_info (linphoneCore, auth_info); 755 | 756 | proxy_cfg = linphone_core_create_proxy_config(linphoneCore); 757 | linphone_proxy_config_set_identity (proxy_cfg, identity); 758 | linphone_proxy_config_set_server_addr (proxy_cfg, server_addr); 759 | linphone_proxy_config_enable_register (proxy_cfg, true); 760 | linphone_core_add_proxy_config (linphoneCore, proxy_cfg); 761 | linphone_core_set_default_proxy_config (linphoneCore, proxy_cfg); 762 | } 763 | 764 | public void DestroyPhone () 765 | { 766 | if (RegistrationStateChangedEvent != null) 767 | RegistrationStateChangedEvent(LinphoneRegistrationState.LinphoneRegistrationProgress); // disconnecting 768 | 769 | linphone_core_terminate_all_calls (linphoneCore); 770 | 771 | SetTimeout (delegate { 772 | linphone_call_params_unref (callsDefaultParams); 773 | 774 | if (linphone_proxy_config_is_registered (proxy_cfg)) { 775 | linphone_proxy_config_edit (proxy_cfg); 776 | linphone_proxy_config_enable_register (proxy_cfg, false); 777 | linphone_proxy_config_done (proxy_cfg); 778 | } 779 | 780 | SetTimeout (delegate { 781 | running = false; 782 | }, 10000); 783 | 784 | }, 5000); 785 | } 786 | 787 | void LinphoneMainLoop() 788 | { 789 | while (running) 790 | { 791 | linphone_core_iterate(linphoneCore); // roll 792 | System.Threading.Thread.Sleep(100); 793 | } 794 | 795 | linphone_core_unref(linphoneCore); 796 | 797 | if (vtablePtr != IntPtr.Zero) 798 | Marshal.FreeHGlobal(vtablePtr); 799 | if (t_configPtr != IntPtr.Zero) 800 | Marshal.FreeHGlobal(t_configPtr); 801 | registration_state_changed = null; 802 | call_state_changed = null; 803 | linphoneCore = callsDefaultParams = proxy_cfg = auth_info = t_configPtr = IntPtr.Zero; 804 | coreLoop = null; 805 | identity = null; 806 | server_addr = null; 807 | 808 | if (RegistrationStateChangedEvent != null) 809 | RegistrationStateChangedEvent(LinphoneRegistrationState.LinphoneRegistrationCleared); 810 | 811 | } 812 | 813 | public void SendDTMFs (Call call, string dtmfs) 814 | { 815 | if (call == null) 816 | throw new ArgumentNullException("call"); 817 | 818 | if (linphoneCore == IntPtr.Zero || !running) 819 | { 820 | if (ErrorEvent != null) 821 | ErrorEvent(call, "Cannot make or receive calls when Linphone Core is not working."); 822 | return; 823 | } 824 | 825 | LinphoneCall linphonecall = (LinphoneCall)call; 826 | linphone_call_send_dtmfs (linphonecall.LinphoneCallPtr, dtmfs); 827 | } 828 | 829 | public void SetRingbackSound (string file) 830 | { 831 | if (linphoneCore == IntPtr.Zero || !running) 832 | { 833 | if (ErrorEvent != null) 834 | ErrorEvent(null, "Cannot modify configuration when Linphone Core is not working."); 835 | return; 836 | } 837 | 838 | linphone_core_set_ringback (linphoneCore, file); 839 | } 840 | 841 | public void SetIncomingRingSound (string file) 842 | { 843 | if (linphoneCore == IntPtr.Zero || !running) 844 | { 845 | if (ErrorEvent != null) 846 | ErrorEvent(null, "Cannot modify configuration when Linphone Core is not working."); 847 | return; 848 | } 849 | 850 | linphone_core_set_ring (linphoneCore, file); 851 | } 852 | 853 | public void TerminateCall (Call call) 854 | { 855 | if (call == null) 856 | throw new ArgumentNullException ("call"); 857 | 858 | if (linphoneCore == IntPtr.Zero || !running) { 859 | if (ErrorEvent != null) 860 | ErrorEvent (call, "Cannot make or receive calls when Linphone Core is not working."); 861 | return; 862 | } 863 | 864 | LinphoneCall linphonecall = (LinphoneCall) call; 865 | linphone_core_terminate_call (linphoneCore, linphonecall.LinphoneCallPtr); 866 | } 867 | 868 | public void TerminateAllCalls() 869 | { 870 | if (linphoneCore == IntPtr.Zero || !running) { 871 | if (ErrorEvent != null) 872 | ErrorEvent (null, "Cannot terminate calls when Linphone Core is not working."); 873 | return; 874 | } 875 | linphone_core_terminate_all_calls(linphoneCore); 876 | } 877 | 878 | public void MakeCall (string uri) 879 | { 880 | if (linphoneCore == IntPtr.Zero || !running) { 881 | if (ErrorEvent != null) 882 | ErrorEvent (null, "Cannot make or receive calls when Linphone Core is not working."); 883 | return; 884 | } 885 | 886 | IntPtr call = linphone_core_invite_with_params (linphoneCore, uri, callsDefaultParams); 887 | 888 | if (call == IntPtr.Zero) { 889 | if (ErrorEvent != null) 890 | ErrorEvent (null, "Cannot call."); 891 | return; 892 | } 893 | 894 | linphone_call_ref(call); 895 | } 896 | 897 | public void MakeCallAndRecord (string uri, string filename) 898 | { 899 | if (linphoneCore == IntPtr.Zero || !running) { 900 | if (ErrorEvent != null) 901 | ErrorEvent (null, "Cannot make or receive calls when Linphone Core is not working."); 902 | return; 903 | } 904 | 905 | linphone_call_params_set_record_file (callsDefaultParams, filename); 906 | 907 | IntPtr call = linphone_core_invite_with_params (linphoneCore, uri, callsDefaultParams); 908 | if (call == IntPtr.Zero) { 909 | if (ErrorEvent != null) 910 | ErrorEvent (null, "Cannot call."); 911 | return; 912 | } 913 | 914 | linphone_call_ref(call); 915 | linphone_call_start_recording (call); 916 | } 917 | 918 | public void ReceiveCallAndRecord (Call call, string filename) 919 | { 920 | if (call == null) 921 | throw new ArgumentNullException ("call"); 922 | 923 | if (linphoneCore == IntPtr.Zero || !running) { 924 | if (ErrorEvent != null) 925 | ErrorEvent (call, "Cannot make or receive calls when Linphone Core is not working."); 926 | return; 927 | } 928 | 929 | LinphoneCall linphonecall = (LinphoneCall) call; 930 | linphone_call_ref(linphonecall.LinphoneCallPtr); 931 | linphone_call_params_set_record_file (callsDefaultParams, filename); 932 | linphone_core_accept_call_with_params (linphoneCore, linphonecall.LinphoneCallPtr, callsDefaultParams); 933 | linphone_call_start_recording (linphonecall.LinphoneCallPtr); 934 | } 935 | 936 | public void ReceiveCall (Call call) 937 | { 938 | if (call == null) 939 | throw new ArgumentNullException ("call"); 940 | 941 | if (linphoneCore == IntPtr.Zero || !running) { 942 | if (ErrorEvent != null) 943 | ErrorEvent (call, "Cannot receive call when Linphone Core is not working."); 944 | return; 945 | } 946 | 947 | LinphoneCall linphonecall = (LinphoneCall) call; 948 | linphone_call_ref(linphonecall.LinphoneCallPtr); 949 | linphone_call_params_set_record_file (callsDefaultParams, null); 950 | linphone_core_accept_call_with_params (linphoneCore, linphonecall.LinphoneCallPtr, callsDefaultParams); 951 | } 952 | 953 | public delegate void RegistrationStateChangedDelegate (LinphoneRegistrationState state); 954 | public event RegistrationStateChangedDelegate RegistrationStateChangedEvent; 955 | 956 | public delegate void CallStateChangedDelegate (Call call); 957 | public event CallStateChangedDelegate CallStateChangedEvent; 958 | 959 | public delegate void ErrorDelegate (Call call, string message); 960 | public event ErrorDelegate ErrorEvent; 961 | 962 | void OnRegistrationChanged (IntPtr lc, IntPtr cfg, LinphoneRegistrationState cstate, string message) 963 | { 964 | if (linphoneCore == IntPtr.Zero || !running) return; 965 | #if (TRACE) 966 | Console.WriteLine("OnRegistrationChanged: {0}", cstate); 967 | #endif 968 | if (RegistrationStateChangedEvent != null) 969 | RegistrationStateChangedEvent (cstate); 970 | } 971 | 972 | void OnCallStateChanged (IntPtr lc, IntPtr call, LinphoneCallState cstate, string message) 973 | { 974 | if (linphoneCore == IntPtr.Zero || !running) return; 975 | #if (TRACE) 976 | Console.WriteLine("OnCallStateChanged: {0}", cstate); 977 | #endif 978 | 979 | Call.CallState newstate = Call.CallState.None; 980 | Call.CallType newtype = Call.CallType.None; 981 | string from = ""; 982 | string to = ""; 983 | IntPtr addressStringPtr; 984 | 985 | // detecting direction, state and source-destination data by state 986 | switch (cstate) { 987 | case LinphoneCallState.LinphoneCallIncomingReceived: 988 | case LinphoneCallState.LinphoneCallIncomingEarlyMedia: 989 | newstate = Call.CallState.Loading; 990 | newtype = Call.CallType.Incoming; 991 | addressStringPtr = linphone_call_get_remote_address_as_string(call); 992 | if (addressStringPtr != IntPtr.Zero) from = Marshal.PtrToStringAnsi(addressStringPtr); 993 | to = identity; 994 | break; 995 | 996 | case LinphoneCallState.LinphoneCallConnected: 997 | case LinphoneCallState.LinphoneCallStreamsRunning: 998 | case LinphoneCallState.LinphoneCallPausedByRemote: 999 | case LinphoneCallState.LinphoneCallUpdatedByRemote: 1000 | newstate = Call.CallState.Active; 1001 | break; 1002 | 1003 | case LinphoneCallState.LinphoneCallOutgoingInit: 1004 | case LinphoneCallState.LinphoneCallOutgoingProgress: 1005 | case LinphoneCallState.LinphoneCallOutgoingRinging: 1006 | case LinphoneCallState.LinphoneCallOutgoingEarlyMedia: 1007 | newstate = Call.CallState.Loading; 1008 | newtype = Call.CallType.Outcoming; 1009 | addressStringPtr = linphone_call_get_remote_address_as_string(call); 1010 | if (addressStringPtr != IntPtr.Zero) to = Marshal.PtrToStringAnsi(addressStringPtr); 1011 | from = this.identity; 1012 | break; 1013 | 1014 | case LinphoneCallState.LinphoneCallError: 1015 | newstate = Call.CallState.Error; 1016 | break; 1017 | 1018 | case LinphoneCallState.LinphoneCallReleased: 1019 | case LinphoneCallState.LinphoneCallEnd: 1020 | newstate = Call.CallState.Completed; 1021 | if (linphone_call_params_get_record_file (callsDefaultParams) != IntPtr.Zero) 1022 | linphone_call_stop_recording (call); 1023 | break; 1024 | 1025 | default: 1026 | break; 1027 | } 1028 | 1029 | IntPtr callref = linphone_call_ref(call); 1030 | 1031 | LinphoneCall existCall = FindCall (callref); 1032 | 1033 | if (existCall == null) { 1034 | existCall = new LinphoneCall (); 1035 | existCall.SetCallState (newstate); 1036 | existCall.SetCallType (newtype); 1037 | existCall.SetFrom (from); 1038 | existCall.SetTo (to); 1039 | existCall.LinphoneCallPtr = callref; 1040 | 1041 | calls.Add (existCall); 1042 | 1043 | if (CallStateChangedEvent != null) 1044 | CallStateChangedEvent (existCall); 1045 | } else { 1046 | if (existCall.GetState () != newstate) { 1047 | existCall.SetCallState (newstate); 1048 | if (CallStateChangedEvent != null) 1049 | CallStateChangedEvent(existCall); 1050 | } 1051 | } 1052 | 1053 | if (cstate == LinphoneCallState.LinphoneCallReleased) 1054 | { 1055 | linphone_call_unref(existCall.LinphoneCallPtr); 1056 | calls.Remove(existCall); 1057 | } 1058 | } 1059 | 1060 | } 1061 | } 1062 | 1063 | -------------------------------------------------------------------------------- /Phone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace sipdotnet 5 | { 6 | public class Phone 7 | { 8 | public enum ConnectState 9 | { 10 | Disconnected, // idle 11 | Progress, // registering on server 12 | Connected // successfull registered 13 | }; 14 | ConnectState connectState; 15 | 16 | public ConnectState CurrentConnectState { 17 | get { 18 | return connectState; 19 | } 20 | } 21 | 22 | public enum LineState 23 | { 24 | Free, // no active calls 25 | Busy // any call 26 | }; 27 | LineState lineState; 28 | 29 | public LineState CurrentLineState { 30 | get { 31 | return lineState; 32 | } 33 | } 34 | 35 | public enum Error 36 | { 37 | /// 38 | /// Registration error 39 | /// 40 | RegisterFailed, 41 | 42 | /// 43 | /// Trying to make/receive call while another call is active 44 | /// 45 | LineIsBusyError, 46 | 47 | /// 48 | /// Trying to connect while connected / connecting or disconnect when not connected 49 | /// 50 | OrderError, 51 | 52 | /// 53 | /// Call failed 54 | /// 55 | CallError, 56 | UnknownError 57 | }; 58 | 59 | /// 60 | /// Successful registered 61 | /// 62 | public delegate void OnPhoneConnected (); 63 | 64 | /// 65 | /// Successful unregistered 66 | /// 67 | public delegate void OnPhoneDisconnected (); 68 | 69 | /// 70 | /// Phone is ringing 71 | /// 72 | /// 73 | public delegate void OnIncomingCall (Call call); 74 | 75 | /// 76 | /// Link is established 77 | /// 78 | /// 79 | public delegate void OnCallActive (Call call); 80 | 81 | /// 82 | /// Call completed 83 | /// 84 | /// 85 | public delegate void OnCallCompleted (Call call); 86 | 87 | /// 88 | /// Error notification 89 | /// 90 | /// 91 | /// 92 | public delegate void OnError (Call call, Error error); 93 | 94 | public event OnPhoneConnected PhoneConnectedEvent; 95 | public event OnPhoneDisconnected PhoneDisconnectedEvent; 96 | public event OnIncomingCall IncomingCallEvent; 97 | public event OnCallActive CallActiveEvent; 98 | public event OnCallCompleted CallCompletedEvent; 99 | public event OnError ErrorEvent; 100 | 101 | Account account; 102 | 103 | public Account Account { 104 | get { 105 | return account; 106 | } 107 | } 108 | 109 | string useragent = "liblinphone"; 110 | 111 | public string Useragent { 112 | get { 113 | return useragent; 114 | } 115 | set { 116 | useragent = value; 117 | } 118 | } 119 | 120 | string version = "6.0.0"; 121 | 122 | public string Version { 123 | get { 124 | return version; 125 | } 126 | set { 127 | version = value; 128 | } 129 | } 130 | 131 | Linphone linphone; 132 | 133 | public Phone (Account account) 134 | { 135 | Debug.Assert (null != account, "Phone requires an Account to make calls."); 136 | this.account = account; 137 | linphone = new Linphone (); 138 | linphone.RegistrationStateChangedEvent += (Linphone.LinphoneRegistrationState state) => { 139 | switch (state) { 140 | case Linphone.LinphoneRegistrationState.LinphoneRegistrationProgress: 141 | connectState = ConnectState.Progress; 142 | break; 143 | 144 | case Linphone.LinphoneRegistrationState.LinphoneRegistrationFailed: 145 | linphone.DestroyPhone(); 146 | if (ErrorEvent != null) ErrorEvent (null, Error.RegisterFailed); 147 | break; 148 | 149 | case Linphone.LinphoneRegistrationState.LinphoneRegistrationCleared: 150 | connectState = ConnectState.Disconnected; 151 | if (PhoneDisconnectedEvent != null) PhoneDisconnectedEvent(); 152 | break; 153 | 154 | case Linphone.LinphoneRegistrationState.LinphoneRegistrationOk: 155 | connectState = ConnectState.Connected; 156 | if (PhoneConnectedEvent != null) PhoneConnectedEvent(); 157 | break; 158 | 159 | case Linphone.LinphoneRegistrationState.LinphoneRegistrationNone: 160 | default: 161 | break; 162 | } 163 | }; 164 | 165 | linphone.ErrorEvent += (call, message) => { 166 | Console.WriteLine ("Error: {0}", message); 167 | if (ErrorEvent != null) ErrorEvent (call, Error.UnknownError); 168 | }; 169 | 170 | linphone.CallStateChangedEvent += (Call call) => { 171 | Call.CallState state = call.GetState(); 172 | 173 | switch (state) { 174 | case Call.CallState.Active: 175 | lineState = LineState.Busy; 176 | if (CallActiveEvent != null) 177 | CallActiveEvent (call); 178 | break; 179 | 180 | case Call.CallState.Loading: 181 | lineState = LineState.Busy; 182 | if (call.GetCallType () == Call.CallType.Incoming) 183 | if (IncomingCallEvent != null) 184 | IncomingCallEvent (call); 185 | break; 186 | 187 | case Call.CallState.Error: 188 | this.lineState = LineState.Free; 189 | if (ErrorEvent != null) 190 | ErrorEvent(null, Error.CallError); 191 | break; 192 | 193 | case Call.CallState.Completed: 194 | default: 195 | this.lineState = LineState.Free; 196 | if (CallCompletedEvent != null) 197 | CallCompletedEvent (call); 198 | break; 199 | } 200 | 201 | }; 202 | } 203 | 204 | public void Connect () 205 | { 206 | if (connectState == ConnectState.Disconnected) 207 | { 208 | connectState = ConnectState.Progress; 209 | linphone.CreatePhone(account.Username, Account.Password, Account.Server, Account.Port, Useragent, Version); 210 | } 211 | else 212 | if (ErrorEvent != null) ErrorEvent(null, Error.OrderError); 213 | } 214 | 215 | public void Disconnect () 216 | { 217 | if (connectState == ConnectState.Connected) 218 | linphone.DestroyPhone (); 219 | else 220 | if (ErrorEvent != null) ErrorEvent (null, Error.OrderError); 221 | } 222 | 223 | public void MakeCall (string sipUriOrPhone) 224 | { 225 | if (string.IsNullOrEmpty(sipUriOrPhone)) 226 | throw new ArgumentNullException ("sipUriOrPhone"); 227 | 228 | if (connectState != ConnectState.Connected) 229 | throw new InvalidOperationException("not connected"); 230 | 231 | if (lineState == LineState.Free) 232 | linphone.MakeCall (sipUriOrPhone); 233 | else { 234 | if (ErrorEvent != null) 235 | ErrorEvent (null, Error.LineIsBusyError); 236 | } 237 | } 238 | 239 | public void MakeCallAndRecord (string sipUriOrPhone, string filename) 240 | { 241 | if (string.IsNullOrEmpty(sipUriOrPhone)) 242 | throw new ArgumentNullException ("sipUriOrPhone"); 243 | 244 | if (string.IsNullOrEmpty(filename)) 245 | throw new ArgumentNullException ("filename"); 246 | 247 | if (connectState != ConnectState.Connected) 248 | throw new InvalidOperationException("not connected"); 249 | 250 | if (lineState == LineState.Free) 251 | linphone.MakeCallAndRecord (sipUriOrPhone, filename); 252 | else { 253 | if (ErrorEvent != null) 254 | ErrorEvent (null, Error.LineIsBusyError); 255 | } 256 | } 257 | 258 | public void ReceiveCallAndRecord (Call call, string filename) 259 | { 260 | if (connectState != ConnectState.Connected) 261 | throw new InvalidOperationException("not connected"); 262 | if (call == null) 263 | throw new ArgumentNullException ("call"); 264 | if (string.IsNullOrEmpty(filename)) 265 | throw new ArgumentNullException ("filename"); 266 | 267 | linphone.ReceiveCallAndRecord (call, filename); 268 | } 269 | 270 | public void ReceiveCall (Call call) 271 | { 272 | if (connectState != ConnectState.Connected) 273 | throw new InvalidOperationException("not connected"); 274 | if (call == null) 275 | throw new ArgumentNullException ("call"); 276 | 277 | linphone.ReceiveCall (call); 278 | } 279 | 280 | public void TerminateCall (Call call) 281 | { 282 | if (connectState != ConnectState.Connected) 283 | throw new InvalidOperationException("not connected"); 284 | if (call == null) 285 | throw new ArgumentNullException ("call"); 286 | 287 | linphone.TerminateCall (call); 288 | } 289 | 290 | public void SendDTMFs (Call call, string dtmfs) 291 | { 292 | if (connectState != ConnectState.Connected) 293 | throw new InvalidOperationException("not connected"); 294 | if (call == null) 295 | throw new ArgumentNullException("call"); 296 | if (string.IsNullOrEmpty(dtmfs)) 297 | throw new ArgumentNullException("dtmfs"); 298 | 299 | linphone.SendDTMFs (call, dtmfs); 300 | } 301 | 302 | public void SetIncomingRingSound (string filename) 303 | { 304 | if (linphone == null) 305 | throw new InvalidOperationException("not connected"); 306 | 307 | linphone.SetIncomingRingSound (filename); 308 | } 309 | 310 | public void SetRingbackSound (string filename) 311 | { 312 | if (linphone == null) 313 | throw new InvalidOperationException("not connected"); 314 | 315 | linphone.SetRingbackSound (filename); 316 | } 317 | 318 | } 319 | } 320 | 321 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sipdotnet 2 | ========= 3 | 4 | Small .NET wrapper for [liblinphone](http://www.linphone.org/eng/documentation/dev/liblinphone-free-sip-voip-sdk.html) library. You can use it for simple interface for SIP telephony. 5 | 6 | Using 7 | ----- 8 | 9 | You can access to wrapped liblinphone actions and events by `Phone` class. Only that you need is the SIP account (`Account` class). 10 | 11 | For using library from .NET code you need to copy linphone dlls ([liblinphone and dependencies](https://github.com/bedefaced/sipdotnet#requirements)) to directory with your EXE file (or use, for example, [Add item](https://msdn.microsoft.com/en-us/library/9f4t9t92(v=vs.100).aspx) dialog in Visual Studio (and select *Copy to Output*: `Copy if newer` or `Copy always` in `Properties` window of added dlls)) and then add `sipdotnet.dll` (or the whole project) as dependency to your solution. 12 | 13 | Current available functionality: 14 | 15 | - SIP-proxy connection control: 16 | - `Connect` 17 | - `Disconnect` 18 | - `Useragent` and `Version` definition 19 | - Call / Register / Error events: 20 | - `PhoneConnectedEvent` 21 | - `PhoneDisconnectedEvent` 22 | - `IncomingCallEvent` 23 | - `CallActiveEvent` 24 | - `CallCompletedEvent` 25 | - `ErrorEvent` 26 | - Make / receive / terminate / record calls: 27 | - `MakeCall` 28 | - `MakeCallAndRecord` 29 | - `ReceiveCall` 30 | - `ReceiveCallAndRecord` 31 | - `TerminateCall` 32 | - `TerminateAllCalls` 33 | 34 | - Utilities: 35 | - `SendDTMFs` (sending DTMF-tones) 36 | - `SetIncomingRingSound` and `SetRingbackSound` (sound that is heard when it's ringing to remote party) 37 | 38 | Example 39 | ------- 40 | ```cs 41 | Account account = new Account ("username", "password", "server"); 42 | Phone phone = new Phone (account); 43 | phone.PhoneConnectedEvent += delegate() { 44 | Console.WriteLine("Phone connected. Calling..."); 45 | phone.MakeCallAndRecord("phonenumber", "/tmp/filename.wav"); 46 | }; 47 | phone.CallActiveEvent += delegate(Call call) { 48 | Console.WriteLine("Answered. Call is active!"); 49 | }; 50 | phone.CallCompletedEvent += delegate(Call call) { 51 | Console.WriteLine("Completed."); 52 | }; 53 | phone.Connect (); // connecting 54 | Console.ReadLine (); 55 | Console.WriteLine("Disconnecting..."); 56 | phone.Disconnect (); // terminate all calls and disconnect 57 | ``` 58 | 59 | Requirements 60 | ------------ 61 | 62 | * .NET 4.0 framework on Windows or Linux (>= Mono 3.2.8) 63 | * last available (4.1.1) liblinphone library binaries installed 64 | 65 | Liblinphone on Windows 66 | ---------------------- 67 | 68 | Due to backwardness of [SDK binaries](http://www.linphone.org/technical-corner/liblinphone/downloads) version it's _recommended_ to use dlls from [Linphone desktop build](http://www.linphone.org/technical-corner/linphone/downloads). You can use my zipped collection in [lib](https://github.com/bedefaced/sipdotnet/blob/master/lib) directory or collect necessary dlls yourself using such tools as [Dependency Walker](http://www.dependencywalker.com/) against 'linphone.dll'. 69 | 70 | Liblinphone on Linux 71 | -------------------- 72 | 1) Install [Mono](http://www.mono-project.com/download/#download-lin) 73 | 2) Build manually from [sources](https://github.com/BelledonneCommunications/linphone), or use [my bash script](https://gist.github.com/bedefaced/3dc4e58c700dada43054f49a3053dcad) for Ubuntu 16.04. 74 | 75 | License 76 | ------- 77 | [LGPLv3](http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License) (see `LICENSE` file) 78 | -------------------------------------------------------------------------------- /lib/linphone.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bedefaced/sipdotnet/08602970ae053c9875abc6bedd99d285fffa1e0e/lib/linphone.zip -------------------------------------------------------------------------------- /sipdotnet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.0 7 | 2.0 8 | {BCB250F8-F74C-4768-8833-0F13FC8659B8} 9 | Library 10 | sipdotnet 11 | sipdotnet 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug 18 | DEBUG;WINDOWS 19 | prompt 20 | 4 21 | false 22 | true 23 | AnyCPU 24 | 25 | 26 | full 27 | true 28 | bin\Release 29 | prompt 30 | 4 31 | false 32 | true 33 | WINDOWS 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | --------------------------------------------------------------------------------