Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,10 @@ jobs:
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uas-multiple-amr.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uas-media-app-managed.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uas-early-update-app-managed.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uac-late-offer.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uas-late-offer.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uac-late-offer-dummy-codecs.xml
python3 run.py --exe ../../pjsip-apps/src/3rdparty_media_sample/alt_pjsua mod_sipp.py scripts-sipp/alt-pjsua-uas-late-offer-dummy-codecs.xml
- name: upload artifacts on failure
if: ${{ failure() }}
uses: actions/upload-artifact@v4
Expand Down
33 changes: 24 additions & 9 deletions pjsip-apps/src/3rdparty_media_sample/alt_pjsua_aud.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2008-2026 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -17,6 +17,7 @@
*/
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
#include "../pjsua/pjsua_app.h"

#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
# error The PJSUA_MEDIA_HAS_PJMEDIA should be declared as zero
Expand Down Expand Up @@ -173,14 +174,28 @@ static pjmedia_codec_factory_op alt_codec_factory_op =
/* Initialize third party media library. */
pj_status_t pjsua_aud_subsys_init()
{
/* Codec registration is intentionally omitted: pjsua now handles an
* empty codec registry gracefully, so 3rd-party media stacks do not
* need to register dummy codecs. The alt_codec_factory above is kept
* as reference documentation for implementors who do want to register.
*/
(void)alt_codec_factory;
(void)alt_codec_factory_op;

if (app_config.dummy_codecs) {
/* Enable dummy codecs if requested in the app config */
pjmedia_codec_mgr *codec_mgr;
pj_status_t status;

/* Register our "dummy" codecs */
alt_codec_factory.base.op = &alt_codec_factory_op;
codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
status = pjmedia_codec_mgr_register_factory(codec_mgr,
&alt_codec_factory.base);
if (status != PJ_SUCCESS)
return status;
}
else {
/* Codec registration is intentionally omitted: pjsua now handles an
* empty codec registry gracefully, so 3rd-party media stacks do not
* need to register dummy codecs. The alt_codec_factory above is kept
* as reference documentation for implementors who do want to register.
*/
(void)alt_codec_factory;
(void)alt_codec_factory_op;
}
/* TODO: initialize your evil library here */
return PJ_SUCCESS;
}
Expand Down
1 change: 1 addition & 0 deletions pjsip-apps/src/pjsua/pjsua_app_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ typedef struct pjsua_app_config
/* Custom SDP to inject via on_call_sdp_created (replaces generated SDP).
* Only available when PJSUA_MEDIA_HAS_PJMEDIA=0 (alt media backend). */
pj_str_t custom_sdp;
pj_bool_t dummy_codecs;
#endif

/* Set PJSUA_CALL_MEDIA_APP_MANAGED on the default call setting used for
Expand Down
11 changes: 10 additions & 1 deletion pjsip-apps/src/pjsua/pjsua_app_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ static void usage(void)
puts (" for this account as application-managed from the start,");
puts (" so pjsua never creates a media transport for them (even");
puts (" the initial incoming offer). See pjsua_acc_config.");
puts (" --dummy-codecs Register dummy codecs for the alternative media backend.");
#endif

#if PJSUA_HAS_VIDEO
Expand Down Expand Up @@ -466,7 +467,8 @@ static pj_status_t parse_args(int argc, char *argv[],
OPT_USE_CLI, OPT_CLI_TELNET_PORT, OPT_DISABLE_CLI_CONSOLE,
OPT_SERVER_AFFINITY, OPT_SERVER_FAILOVER, OPT_RESOLVER_FALLBACK
#if !PJSUA_MEDIA_HAS_PJMEDIA
, OPT_CUSTOM_SDP, OPT_SDP_PASSTHROUGH, OPT_ACC_MEDIA_APP_MANAGED
, OPT_CUSTOM_SDP, OPT_SDP_PASSTHROUGH, OPT_ACC_MEDIA_APP_MANAGED,
OPT_DUMMY_CODECS
#endif
};
struct pj_getopt_option long_options[] = {
Expand Down Expand Up @@ -639,6 +641,7 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "acc-media-app-managed", 0, 0, OPT_ACC_MEDIA_APP_MANAGED},
{ "custom-sdp", 1, 0, OPT_CUSTOM_SDP},
{ "sdp-passthrough", 0, 0, OPT_SDP_PASSTHROUGH},
{ "dummy-codecs", 0, 0, OPT_DUMMY_CODECS},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10. --dummy-codecs isn't registered anywhere else.

It's in the option table and parsed at line 1733, but absent from usage() (where --custom-sdp appears, line 188) and from the settings serializer near line 2802.

So it doesn't show up in --help, and it's silently dropped across an app restart or a config dump-and-reload.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

#endif
{ NULL, 0, 0, 0}
};
Expand Down Expand Up @@ -1811,6 +1814,9 @@ static pj_status_t parse_args(int argc, char *argv[],
cfg->custom_sdp.slen = (pj_ssize_t)(dst - cfg->custom_sdp.ptr);
break;
}
case OPT_DUMMY_CODECS:
cfg->dummy_codecs = PJ_TRUE;
break;
#endif /* !PJSUA_MEDIA_HAS_PJMEDIA */

default:
Expand Down Expand Up @@ -2914,6 +2920,9 @@ int write_settings(pjsua_app_config *config, char *buf, pj_size_t max)
cfg_add_str(&cfg, max, &escaped);
cfg_add(&cfg, max, "\"\n");
}
if (config->dummy_codecs) {
cfg_add(&cfg, max, "--dummy-codecs\n");
}
#endif /* !PJSUA_MEDIA_HAS_PJMEDIA */

return (int)cfg.slen;
Expand Down
90 changes: 76 additions & 14 deletions pjsip/include/pjsua-lib/pjsua.h
Original file line number Diff line number Diff line change
Expand Up @@ -1780,15 +1780,18 @@ typedef struct pjsua_callback
* Otherwise, by default the re-INVITE will be answered automatically
* after the callback returns.
*
* For an application-managed call receiving a re-INVITE without SDP,
* this callback is also called with a NULL offer. By default, the active
* local SDP is re-offered. Application may set \a async to PJ_TRUE and
* For a re-INVITE without SDP, this callback is called with a NULL offer.
* This also applies to calls that are not application-managed when this
* callback is registered. By default, the active local SDP is re-offered.
* Application may set \a async to PJ_TRUE and
* later send the response with #pjsua_call_answer_with_sdp(), supplying
* its own local SDP offer. On the asynchronous path, a successful final
* response requires an application-supplied offer; the active local SDP
* is not automatically re-offered.
* On the synchronous offerless path, any 2xx callback code is treated
* as 200. Codes outside 200-699 are rejected with 500.
* as 200, and final rejection codes (300-699) are accepted. Codes outside
* 200-699 are rejected with 500. For a re-INVITE containing SDP, only
* 200 and 488 are valid callback codes.
*
* Remarks: If manually answering at a later timing, application may
* need to monitor on_call_tsx_state() callback to check whether
Expand All @@ -1808,15 +1811,17 @@ typedef struct pjsua_callback
* as pjsua deactivates it there, see on_call_sdp_created().
*
* @param call_id The call index.
* @param offer Remote offer, or NULL for an application-managed
* call receiving a re-INVITE without SDP.
* @param offer Remote offer, or NULL for a re-INVITE without SDP.
* @param rdata The received re-INVITE request.
* @param reserved Reserved param, currently not used.
* @param async On input, it is PJ_FALSE. Set to PJ_TRUE if
* app wants to manually answer the re-INVITE.
* @param code Status code to be returned for answering the
* offer. On input, it contains status code 200.
* Currently, valid values are only 200 and 488.
* For a re-INVITE containing SDP, valid values are 200
* and 488. For a synchronous offerless re-INVITE, 2xx
* is treated as 200, 300-699 rejects the request, and
* values outside 200-699 are changed to 500.
* @param opt The current call setting, application can update
* this setting for answering the offer.
*/
Expand Down Expand Up @@ -2479,6 +2484,28 @@ typedef struct pjsua_callback
*/
void (*on_auth_challenge)(pjsua_on_auth_challenge_param *param);

/**
* Notify application when the framework needs to send an ACK request
* after it receives an incoming 2xx response for INVITE (e.g. when the
* 2xx response carries a late SDP offer). Implementing this callback
* alone does not transfer responsibility for sending the ACK. The
* framework continues to send it automatically unless this callback
* returns PJ_TRUE. When it returns PJ_TRUE, the application is
* responsible for creating and sending the ACK itself, using
* #pjsua_call_send_ack(). This allows an SDP answer to be attached to
* the ACK or the ACK transmission to be delayed, e.g. while waiting for
* the SDP answer in 3PCC scenarios.
*
* @param call_id The call index.
* @param rdata The received 2xx response that triggered the need to
* send an ACK request.
* @return PJ_TRUE to suppress the automatic ack request sending.
* PJ_FALSE (default) to keep current behavior, automatic
* ACK sending by the framework will proceed as usual.
*/
pj_bool_t (*on_call_send_ack)(pjsua_call_id call_id,
pjsip_rx_data *rdata);

} pjsua_callback;


Expand Down Expand Up @@ -6934,17 +6961,25 @@ PJ_DECL(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id,

/**
* Same as #pjsua_call_answer2() but this function will set the local SDP
* first before sending the response. For application-managed calls
* (#PJSUA_CALL_MEDIA_APP_MANAGED), the SDP is used as an offer when answering
* an offerless INVITE or re-INVITE, and as an answer otherwise. For ordinary
* calls, the SDP is used only as an answer to a pending remote offer.
* before sending the response. The SDP is used as an offer when answering
* an offerless INVITE or re-INVITE, and as an answer to a pending remote
* offer otherwise. For ordinary initial offerless calls, media is initialized
* before installing the supplied offer. A pending local offer cannot be
* replaced by this function.
* Supplying SDP with a final error response (code >= 300) returns
* PJ_EINVALIDOP without changing call state.
* An application-managed answer must have the same media-line count as the
* remote offer; otherwise PJMEDIA_SDPNEG_EMISMEDIA is returned.
*
* For calls whose media is not application-managed, supplying a local SDP
* offer in a provisional (1xx) response requires 100rel to be active
* (#PJSIP_INV_REQUIRE_100REL). Otherwise PJ_EINVALIDOP is returned.
* Codes 100, 180, and 181 are rejected for such offers even with 100rel active.
* These restrictions apply to SDP offers, not answers to remote offers.
*
* @param call_id Incoming call identification.
* @param sdp Local SDP answer, or a local SDP offer for an
* application-managed call answering an offerless
* INVITE or re-INVITE.
* @param sdp Local SDP answer, or a local SDP offer when answering
* an offerless INVITE or re-INVITE.
* @param opt Optional call setting.
* @param code Status code, (100-699).
* @param reason Optional reason phrase. If NULL, default text
Expand All @@ -6965,6 +7000,33 @@ pjsua_call_answer_with_sdp(pjsua_call_id call_id,
const pjsua_msg_data *msg_data);


/**
* Create and send an ACK request for the 2xx response, optionally with an
* SDP answer \a sdp attached to it. Application would normally use this
* function from within the #pjsua_callback.on_call_send_ack() callback,
* to manually take over the ACK transmission, e.g. to attach an SDP answer
* to the ACK for a late SDP offer received in the 2xx response.
* This function is safe to call synchronously from the
* #pjsua_callback.on_call_send_ack() callback when \a call_id identifies the
* call whose ACK triggered the callback. A different call ID is rejected with
* PJ_EINVALIDOP to avoid acquiring the PJSUA lock while holding the callback
* call's dialog lock.
*
* @param call_id Call identification.
* @param cseq CSeq of the 2xx response that triggered the need to
* send an ACK request; normally this is the CSeq of
* the \a rdata received in the on_call_send_ack()
* callback.
* @param sdp Optional SDP answer to be attached to the ACK
* request. May be NULL if no SDP body is required.
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjsua_call_send_ack(pjsua_call_id call_id,
int cseq,
const pjmedia_sdp_session *sdp);


/**
* Hangup call by using method that is appropriate according to the
* call state. This function is different than answering the call with
Expand Down
11 changes: 11 additions & 0 deletions pjsip/include/pjsua-lib/pjsua_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ struct pjsua_call
/**< Allocated size of siprec_metadata */

pjmedia_av_sync *av_sync; /**< Media stream synchronizer */
pjmedia_sdp_session *answer_sdp;/**< Application SDP pending media init.*/
};

PJ_INLINE(pj_bool_t) pjsua_call_media_is_app_managed(
Expand Down Expand Up @@ -941,6 +942,11 @@ pj_status_t pjsua_acc_get_uac_dlg_addr(pjsua_acc_id acc_id,
*/
pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata);

pj_status_t pjsua_call_validate_initial_local_offer(
const pjsua_call *call,
const pjsua_call_setting *opt,
const pjmedia_sdp_session *sdp);

/*
* Media channel.
*/
Expand Down Expand Up @@ -1057,6 +1063,11 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg);
*/
pj_status_t pjsua_call_subsys_start(void);

/**
* Destroy call subsystem resources.
*/
void pjsua_call_subsys_destroy(void);

/**
* Init media subsystems.
*/
Expand Down
72 changes: 72 additions & 0 deletions pjsip/include/pjsua2/call.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ struct SdpSession
void *pjSdpSession;

public:
/**
* Default constructor
*/
SdpSession() : pjSdpSession(nullptr) {}

/**
* Convert from pjsip
*/
Expand Down Expand Up @@ -833,6 +838,36 @@ struct OnCallSdpCreatedParam
SdpSession remSdp;
};

/**
* This structure contains parameters for Call::onCallSendAck() callback.
*/
struct OnCallSendAckParam
{
/**
* The 2xx response for INVITE that triggered the need to send an ACK
* request.
*/
SipRxData rdata;

/**
* Output: application sets this to true if it has sent (or will send)
* the ACK itself, to suppress the automatic ACK sending. Default is false,
* i.e. the library sends the ACK after this callback returns.
*/
bool suppressSendAck;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4. This documentation belongs to a different callback, and following it sends two ACKs.

"suppress the automatic session termination and keep the call alive ... the library will terminate the call as per default behavior" is copy-pasted from a terminate-session parameter. The field actually suppresses the automatic ACK.

The practical consequence: an application that reads this will call sendAck() with its own SDP answer and leave suppressSendAck false, believing the flag concerns call termination. pjsua_call_on_send_ack() then sees skip_sending_ack == false and sends a second ACK — pjsip_inv_create_ack() dec-refs the one just sent and puts a duplicate on the wire.

Suggest something like: "Output: application sets this to true if it has sent (or will send) the ACK itself, to suppress the automatic ACK sending. Default is false, i.e. the library sends the ACK after this callback returns."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed comment


/**
* The CSeq of the 2xx response.
*/
pj_int32_t cseq;

/**
* Default constructor
*/
OnCallSendAckParam() : suppressSendAck(false), cseq(-1)
{}
};

/**
* This structure contains parameters for Call::onStreamPreCreate()
* callback.
Expand Down Expand Up @@ -1770,6 +1805,27 @@ class Call
*/
void hangup(const CallOpParam &prm) PJSUA2_THROW(Error);

/**
* Create and send an ACK request for the 2xx response, optionally with
* an SDP answer \a sdp attached to it. Application would normally call
* this from within onCallSendAck(), to manually take over the ACK
* transmission, e.g. to attach an SDP answer to the ACK for a late
* SDP offer received in the 2xx response.
* When called synchronously from onCallSendAck(), this method may only
* be used on the Call whose ACK triggered the callback; another Call
* fails with PJ_EINVALIDOP.
*
* @param cseq CSeq of the 2xx response that triggered the need to
* send an ACK request; normally this is the CSeq of
* the \a rdata received in the onCallSendAck()
* callback.
* @param sdp Optional SDP answer to be attached to the ACK
* request. If its wholeSdp is empty, no SDP body
* will be attached.
*/
void sendAck(int cseq, const SdpSession &sdp = SdpSession())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9. The cseq isn't reachable from PJSUA2.

OnCallSendAckParam carries only rdata, so an application overriding onCallSendAck() has to reach through the abstraction to get the CSeq:

((pjsip_rx_data*)prm.rdata.pjRxData)->msg_info.cseq->cseq

That's the one piece of raw PJSIP a pjsua2 user shouldn't need. Either add cseq to OnCallSendAckParam, or give sendAck() a default that derives it from the call's pending 2xx. Non-blocking, but worth settling before the API ships, since changing it later is a break.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added cseq into OnCallSendAckParam

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3. Mirror the same-call-id restriction here.

pjsua_call_send_ack() gained the restriction note in pjsua.h (lines 7005-7011) — thank you — but Call::sendAck() doesn't carry it.

A PJSUA2 application doing exactly the 3PCC bridging that the onCallSendAck() docs advertise will call sendAck() for the other leg and get an Error carrying PJ_EINVALIDOP with no explanation. One sentence mirroring the C doc would save them the hunt.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment

PJSUA2_THROW(Error);

/**
* Put the specified call on hold. This will send re-INVITE with the
* appropriate SDP to inform remote that the call is being put on hold.
Expand Down Expand Up @@ -2131,6 +2187,22 @@ class Call
virtual void onCallSdpCreated(OnCallSdpCreatedParam &prm)
{ PJ_UNUSED_ARG(prm); }

/**
* Notify application when the framework needs to send an ACK request
* after it receives an incoming 2xx response for INVITE (e.g. when the
* 2xx response carries a late SDP offer). The default implementation
* does nothing and lets the framework send the ACK. Application can
* set prm.suppressSendAck to true and then the application is responsible
* for sending the ACK.
*
* This callback is enabled only when
* UaConfig::enableCallSendAckCallback is set before Endpoint::libInit().
*
* @param prm Callback parameter.
*/
virtual void onCallSendAck(OnCallSendAckParam &prm)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4. The default implementation can throw through C frames.

sendAck() raises Error via PJSUA2_CHECK_RAISE_ERROR2, and Endpoint::on_call_send_ack() has no try/catch (the only catch in endpoint.cpp is at line 700). The exception therefore unwinds through inv_send_ack -> mod_inv_on_tsx_state -> the tsx/dialog callbacks — C frames, with the dialog lock held — giving std::terminate or at best a leaked lock.

This is reachable in normal operation: acquire_call() inside pjsua_call_send_ack() legitimately fails with PJ_ETIMEDOUT or PJSIP_ESESSIONTERMINATED. Every other pjsua2 default callback is a no-op and cannot throw, so the surrounding code isn't written to expect this. Please wrap the invocation in Endpoint::on_call_send_ack().

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default implementation does not throw.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8. This description doesn't match the implementation.

"The default implementation simply sends the ACK without any SDP answer, using sendAck()" — but the default body is { PJ_UNUSED_ARG(prm); }, and the send happens down in the C layer via pjsua_call_on_send_ack().

The behaviour is right; only the text is wrong. Combined with comment 4, though, an application author reading this header can't work out who owns the ACK — and an override that calls the base implementation expecting it to send will get nothing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed description

{ PJ_UNUSED_ARG(prm); }

/**
* Notify application when an audio media session is about to be created
* (as opposed to onStreamCreated(), which is called *after* the session
Expand Down
Loading
Loading