Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pjmedia/include/pjmedia/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,24 @@ PJ_DECL(pj_status_t)
pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream );


/**
* Send a keep-alive packet (empty RTP plus RTCP) for the media stream on
* demand. This is useful to keep the media path / NAT binding alive while the
* stream is not being driven by put_frame() (e.g. when the local audio device
* has been disconnected from the stream but the session must stay up).
*
* Unlike the automatic keep-alive (PJMEDIA_STREAM_ENABLE_KA), which is only
* emitted from within put_frame(), this sends a keep-alive packet immediately
* regardless of the PJMEDIA_STREAM_ENABLE_KA build setting.
*
* @param stream The media stream.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t)
pjmedia_stream_send_keep_alive( pjmedia_stream *stream );


/**
* Get the RTP session information of the media stream. This function can be
* useful for app with custom media transport to inject/filter some
Expand Down
13 changes: 13 additions & 0 deletions pjmedia/src/pjmedia/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,19 @@ static void send_keep_alive_packet(pjmedia_stream *stream)
}
#endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */


PJ_DEF(pj_status_t) pjmedia_stream_send_keep_alive(pjmedia_stream *stream)
{
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0
send_keep_alive_packet(stream);
return PJ_SUCCESS;
#else
return PJ_ENOTSUP;
#endif
}


/*
* play_callback()
*
Expand Down
20 changes: 20 additions & 0 deletions pjsip/include/pjsua-lib/pjsua.h
Original file line number Diff line number Diff line change
Expand Up @@ -6474,6 +6474,26 @@ PJ_DECL(pj_status_t) pjsua_call_get_stream_stat(pjsua_call_id call_id,
unsigned med_idx,
pjsua_stream_stat *stat);

/**
* Send a keep-alive packet (empty RTP plus RTCP) for the specified audio
* media stream on demand. This keeps the media path / NAT binding alive while
* the stream is not being driven by the local sound device (e.g. audio has
* been parked but the SIP/media session must stay up).
*
* The keep-alive is sent under the pjsua lock and only if the stream is still
* active, so it is safe to call from an application thread concurrently with
* call teardown: if the stream has already been destroyed the call returns an
* error instead of touching freed memory.
*
* @param call_id The call identification.
* @param med_idx Media stream index.
*
* @return PJ_SUCCESS on success, or PJ_EINVALIDOP if the stream
* is not an active audio stream (e.g. already destroyed).
*/
PJ_DECL(pj_status_t) pjsua_call_send_stream_keep_alive(pjsua_call_id call_id,
unsigned med_idx);

/**
* Get media transport info for the specified media index.
*
Expand Down
16 changes: 15 additions & 1 deletion pjsip/include/pjsua2/call.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,21 @@ class Call
* @return The stream statistic.
*/
StreamStat getStreamStat(unsigned med_idx) const PJSUA2_THROW(Error);


/**
* Send a keep-alive packet (empty RTP plus RTCP) for the specified audio
* media stream on demand. Use this to keep the media path / NAT binding
* alive while the stream is not being driven by the local sound device
* (e.g. audio parked, but the SIP/media session must stay up).
*
* It is safe to call from an application thread concurrently with call
* teardown: the keep-alive is sent under the pjsua lock and only if the
* stream is still active, otherwise it throws an Error.
*
* @param med_idx Media stream index.
*/
void sendStreamKeepAlive(unsigned med_idx) PJSUA2_THROW(Error);

/**
* Get media transport info for the specified media index.
*
Expand Down
43 changes: 43 additions & 0 deletions pjsip/src/pjsua-lib/pjsua_aud.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,49 @@ PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
return status;
}

/*
* Send a keep-alive packet (empty RTP plus RTCP) for an audio media stream.
*
* Held under PJSUA_LOCK and revalidated each call, so it is mutually exclusive
* with pjsua_media_channel_deinit() (which destroys the stream under the same
* lock and nulls call_med->strm.a.stream). This makes it safe to call from an
* application thread without risking a use-after-free on the pjmedia stream.
*/
PJ_DEF(pj_status_t) pjsua_call_send_stream_keep_alive(pjsua_call_id call_id,
unsigned med_idx)
{
pjsua_call *call;
pjsua_call_media *call_med;
pj_status_t status = PJ_EINVALIDOP;

PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
PJ_EINVAL);

PJSUA_LOCK();

call = &pjsua_var.calls[call_id];

if (med_idx >= call->med_cnt)
goto on_return;

call_med = &call->media[med_idx];

/* Only send on an active audio stream that is still alive. Both
* call_med->state and call_med->strm.a.stream are mutated under PJSUA_LOCK
* during teardown, so observing them here can never race the destroy.
*/
if (call_med->type == PJMEDIA_TYPE_AUDIO &&
call_med->state == PJSUA_CALL_MEDIA_ACTIVE &&
call_med->strm.a.stream)
{
status = pjmedia_stream_send_keep_alive(call_med->strm.a.stream);
}

on_return:
PJSUA_UNLOCK();
return status;
}

/*
* Send DTMF digits to remote using RFC 2833 payload formats.
*/
Expand Down
5 changes: 5 additions & 0 deletions pjsip/src/pjsua2/call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,11 @@ StreamStat Call::getStreamStat(unsigned med_idx) const PJSUA2_THROW(Error)
return ss;
}

void Call::sendStreamKeepAlive(unsigned med_idx) PJSUA2_THROW(Error)
{
PJSUA2_CHECK_EXPR( pjsua_call_send_stream_keep_alive(id, med_idx) );
}

MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const
PJSUA2_THROW(Error)
{
Expand Down
Loading