Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Something is persisting in the background after a call is answered, hung up, and then a new call is answered. It generates a lot of warning logs like these. The audio end point doesn't really have a way to discern if the RTP session it's trying to forward to is "closed" and as far as my code goes I don't think there should be anything left around after the first call ends.

It's probably some part of the Sip Sorcery stack I am using wrong.

So my question is, does anything in my SipClient stand out as obviously wrong for idling to handle incoming calls that would cause the SendRtpRaw warning?

2025-05-22 12:37:28.426 -05:00 [WRN] SendRtpRaw was called for a "audio" packet on a closed RTP session.
2025-05-22 12:37:28.427 -05:00 [DBG] [LocalVoiceAudioEndPoint] Forwarded encoded sample: 160 bytes, 160 RTP units

Here are the relevant parts of my sip client code,

using Serilog;
using SIPSorcery.Media;
using SIPSorcery.Net;
using SIPSorcery.SIP;
using SIPSorcery.SIP.App;
using SIPSorceryMedia.Abstractions;

public class SipClient
{
    private const int RegistrationExpirySeconds = 60;

    private readonly string _sipUsername;
    private readonly string _sipPassword;
    private readonly string _sipServer;
    private readonly string _sipFromName;

    private readonly MediaEndPoints _mediaEndPoints;

    private SIPTransport _sipTransport;
    private SIPUserAgent _userAgent;
    private SIPServerUserAgent? _pendingIncomingCall;
    private CancellationTokenSource _cts = new();

    public event Action<SipClient>? CallAnswer;
    public event Action<SipClient>? CallEnded;
    public event Action<SipClient, string>? StatusMessage;
    public event Action<SipClient>? RemotePutOnHold;
    public event Action<SipClient>? RemoteTookOffHold;

    private VoIPMediaSession? _mediaSession = null;
    private SIPRegistrationUserAgent _registrationAgent;

    // CTOR
    public SipClient(
        SIPTransport sipTransport,
        MediaEndPoints mediaEndPoints,
        SipConfig sipSettings)
    {
        _sipTransport = sipTransport;
        _sipUsername = sipSettings.Username;
        _sipPassword = sipSettings.Password;
        _sipServer = sipSettings.Server;
        _sipFromName = sipSettings.FromName;

        //m_audioOutDeviceIndex = audioOutDeviceIndex;
        _mediaEndPoints = mediaEndPoints ?? throw new ArgumentNullException(nameof(mediaEndPoints));
        if (mediaEndPoints.AudioSink == null || mediaEndPoints.AudioSource == null)
        {
            throw new ArgumentException("AudioSink and AudioSource must be provided in MediaEndPoints.", nameof(mediaEndPoints));
        }

        _sipTransport.SIPRequestInTraceEvent += (localSIPEndPoint, endPoint, request) =>
            Log.Debug($"SIP Request Received: {request.Method} from {endPoint}");

        _sipTransport.SIPResponseOutTraceEvent += (localSIPEndPoint, endPoint, response) =>
            Log.Debug($"SIP Response Sent: {response.Status} to {endPoint}");

        StunHelper.SetupStun();

        _userAgent = new SIPUserAgent(_sipTransport, null);
        _userAgent.ClientCallTrying += CallTrying;
        _userAgent.ClientCallRinging += CallRinging;
        _userAgent.ClientCallAnswered += CallAnswered;
        _userAgent.ClientCallFailed += CallFailed;
        _userAgent.OnCallHungup += CallFinished;
        _userAgent.ServerCallCancelled += IncomingCallCancelled;

        // Initialize registration agent
        _registrationAgent = new SIPRegistrationUserAgent(
            sipTransport,
            _sipUsername,
            _sipPassword,
            _sipServer,
            RegistrationExpirySeconds
        );

        _registrationAgent.RegistrationSuccessful += (uri, resp) =>
StatusMessage?.Invoke(this, $"Registration successful for {uri}. Expires: {resp.Header.Expires}");
        _registrationAgent.RegistrationFailed += (uri, resp, error) =>
            StatusMessage?.Invoke(this, $"Registration failed for {uri}: {error}");
        _registrationAgent.RegistrationTemporaryFailure += (uri, resp, error) =>
            StatusMessage?.Invoke(this, $"Registration temporary failure for {uri}: {error}");

    }

    public void StartRegistration()
    {
        _registrationAgent.Start();
        StatusMessage?.Invoke(this, $"Registration attempt for {_sipUsername}@{_sipServer} started.");
    }

    public void Shutdown()
    {
        Hangup();
        _mediaSession?.Dispose();
        _mediaSession = null;
        _registrationAgent?.Stop();
        _sipTransport.Shutdown();
    }

    public void Accept(SIPRequest sipRequest)
    {
        _pendingIncomingCall = _userAgent.AcceptCall(sipRequest);
    }

    public async Task<bool> Answer()
    {
        if (_pendingIncomingCall == null)
        {
            StatusMessage?.Invoke(this, $"There was no pending call available to answer.");
            return false;
        }
        else
        {
            var sipRequest = _pendingIncomingCall.ClientTransaction.TransactionRequest;

            bool hasAudio = true;
            bool hasVideo = false;

            if (sipRequest.Body != null)
            {
                SDP offerSDP = SDP.ParseSDPDescription(sipRequest.Body);
                //offerSDP.AddressOrHost = StunHelper.PublicIPAddress;
                hasAudio = offerSDP.Media.Any(x => x.Media == SDPMediaTypesEnum.audio && x.MediaStreamStatus != MediaStreamStatusEnum.Inactive);
                hasVideo = offerSDP.Media.Any(x => x.Media == SDPMediaTypesEnum.video && x.MediaStreamStatus != MediaStreamStatusEnum.Inactive);
            }

            _mediaSession?.Dispose();
            _mediaSession = CreateMediaSession();

            bool result = await _userAgent.Answer(_pendingIncomingCall, _mediaSession);
            _pendingIncomingCall = null;

            if (result)
            {
                CallAnswer?.Invoke(this);
            }

            return result;
        }
    }

    public void Hangup()
    {
        if (_userAgent.IsCallActive)
        {
            _userAgent.Hangup();
            CallFinished(null);
            CallEnded?.Invoke(this);
        }
    }

    private VoIPMediaSession CreateMediaSession()
    {
        var voipMediaSession = new VoIPMediaSession(_mediaEndPoints);
        //voipMediaSession.AcceptRtpFromAny = false;
        Log.Information($"[{GetType().Name}] Created with AudioSink={_mediaEndPoints.AudioSink.GetType().Name}, AcceptRtpFromAny={voipMediaSession.AcceptRtpFromAny}");
        return voipMediaSession;
    }

    private void CallTrying(ISIPClientUserAgent uac, SIPResponse sipResponse)
    {
        StatusMessage?.Invoke(this, "Call trying: " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + ".");
    }

    private void CallRinging(ISIPClientUserAgent uac, SIPResponse sipResponse)
    {
        StatusMessage?.Invoke(this, "Call ringing: " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + ".");
    }

    private void CallFailed(ISIPClientUserAgent uac, string errorMessage, SIPResponse? failureResponse)
    {
        StatusMessage?.Invoke(this, "Call failed: " + errorMessage + ".");
        CallFinished(null);
    }

    private void CallAnswered(ISIPClientUserAgent uac, SIPResponse sipResponse)
    {
        StatusMessage?.Invoke(this, "Call answered: " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + ".");
        CallAnswer?.Invoke(this);
    }

    private void CallFinished(SIPDialogue? dialogue)
    {
        _mediaSession?.Dispose();
        _mediaSession = null;
        _pendingIncomingCall = null;
        CallEnded?.Invoke(this);
    }

    private void IncomingCallCancelled(ISIPServerUserAgent uas, SIPRequest cancelRequest)
    {
        CallFinished(null);
    }
}
You must be logged in to vote

Replies: 2 comments

Comment options

It'd be worth trying _mediaSession.Close in your CallFinished method.

You must be logged in to vote
0 replies
Comment options

I looked into that, the dispose() method calls that with it's own reason string.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.