PJSip无法恢复已保持通话:报错PJ_EINVALIDOP(70013)求助
Let's break down why you're running into this PJ_EINVALIDOP error when trying to resume a held call, and walk through fixes to get it working.
First, here's the error you're seeing:
Unable to create re-INVITE: Invalid operation (PJ_EINVALIDOP) [status=70013] Error hanging up call: Invalid operation (PJ_EINVALIDOP) [status=70013]
And your current implementation for unholding:
pj_status_t pj_wrapper_unhold_call(pjsua_call_id call_id, char deviceId, char groupId, char *token) { pj_status_t status; if (!pj_wrapper_initialized) { return PJ_EGONE; } status = pjsua_call_reinvite(call_id, PJSUA_CALL_UNHOLD, NULL); if (status != PJ_SUCCESS) { pj_wrapper_show_error("Error hanging up call", status); } return status; }
Why This Error Happens
PJ_EINVALIDOP (status code 70013) basically means you're trying to perform an operation that doesn't make sense for the call's current state. For unholding, the most common triggers are:
- The call isn't actually held: You're trying to unhold a call that's either active, disconnected, or was never held properly in the first place.
- Call is in a transitional state: If the call is still connecting, disconnecting, or hasn't fully established media yet, a re-INVITE for unhold will fail.
- PJSIP doesn't recognize the held state: If you used a custom method to hold the call (instead of PJSIP's official
PJSUA_CALL_HOLDflag), PJSIP's internal state tracking won't know the call is held, so unhold is seen as invalid. - Media session isn't ready: If the call's media hasn't been set up yet (e.g., early media isn't active), you can't send a re-INVITE to unhold.
How to Fix It
Let's update your code to add validation and handle edge cases properly:
1. Add Call State & Media Validation
First, we'll check the call's current state and media status before attempting to unhold. This ensures we only run the unhold operation when it's valid:
pj_status_t pj_wrapper_unhold_call(pjsua_call_id call_id, char deviceId, char groupId, char *token) { pj_status_t status; pjsua_call_info call_info; if (!pj_wrapper_initialized) { return PJ_EGONE; } // Fetch the latest call state info status = pjsua_call_get_info(call_id, &call_info); if (status != PJ_SUCCESS) { pj_wrapper_show_error("Failed to retrieve call info", status); return status; } // Make sure the call is in an active, confirmed state if (call_info.state != PJSUA_CALL_STATE_CONFIRMED) { pj_wrapper_show_error("Cannot unhold: call is not active", PJ_EINVALIDOP); return PJ_EINVALIDOP; } // Verify the call is actually held if (call_info.media_status != PJSUA_CALL_MEDIA_HOLD) { pj_wrapper_show_error("Cannot unhold: call is not in held state", PJ_EINVALIDOP); return PJ_EINVALIDOP; } // Now safely attempt to unhold status = pjsua_call_reinvite(call_id, PJSUA_CALL_UNHOLD, NULL); if (status != PJ_SUCCESS) { pj_wrapper_show_error("Error unholding call", status); // Fixed the error message here! } return status; }
Note: I fixed the error message from "Error hanging up call" to "Error unholding call" since that's what this function is actually doing—this will save you confusion when debugging later.
2. Ensure Hold Was Initiated Correctly
Double-check that when you held the call, you used PJSIP's official API:
// Example of correct hold implementation pj_status_t hold_status = pjsua_call_reinvite(call_id, PJSUA_CALL_HOLD, NULL);
If you used a custom SDP modification to hold the call, PJSIP won't track that state internally, so it'll reject the unhold request. Stick to the official PJSUA_CALL_HOLD flag for consistency.
3. Handle Transitional States Gracefully
If your app triggers unhold right after holding the call, add a small delay or listen for PJSIP's on_call_state callback to confirm the call has fully entered the held state before trying to unhold. This avoids trying to unhold mid-state-transition.
内容的提问来源于stack exchange,提问作者Ishu

