Промежуточный коммит со звонками

This commit is contained in:
2026-03-26 00:31:35 +05:00
parent bc7efbfbd9
commit 3fffbd0392
2 changed files with 31 additions and 134 deletions

View File

@@ -72,13 +72,6 @@ struct GeneratedTsState {
uint32_t next_step = 960; // 20 ms @ 48 kHz (default Opus packetization) uint32_t next_step = 960; // 20 ms @ 48 kHz (default Opus packetization)
}; };
struct AdditionalTsState {
bool initialized64 = false;
bool initialized32 = false;
uint64_t base64 = 0;
uint32_t base32 = 0;
};
static inline uint16_t load16_be(const uint8_t* p) { static inline uint16_t load16_be(const uint8_t* p) {
return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]); return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]);
} }
@@ -90,27 +83,6 @@ static inline uint32_t load32_be(const uint8_t* p) {
((uint32_t)p[3]); ((uint32_t)p[3]);
} }
static inline uint64_t load64_be(const uint8_t* p) {
return ((uint64_t)p[0] << 56) |
((uint64_t)p[1] << 48) |
((uint64_t)p[2] << 40) |
((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) |
((uint64_t)p[5] << 16) |
((uint64_t)p[6] << 8) |
((uint64_t)p[7]);
}
static inline void store64_be(uint8_t* p, uint64_t v) {
p[0] = (uint8_t)(v >> 56);
p[1] = (uint8_t)(v >> 48);
p[2] = (uint8_t)(v >> 40);
p[3] = (uint8_t)(v >> 32);
p[4] = (uint8_t)(v >> 24);
p[5] = (uint8_t)(v >> 16);
p[6] = (uint8_t)(v >> 8);
p[7] = (uint8_t)(v);
}
static bool parse_rtp_packet(const uint8_t* data, size_t len, ParsedRtpPacket* out) { static bool parse_rtp_packet(const uint8_t* data, size_t len, ParsedRtpPacket* out) {
if (!data || !out || len < 12) return false; if (!data || !out || len < 12) return false;
@@ -216,27 +188,22 @@ static bool fill_nonce_from_rtp_frame(const uint8_t* data,
static bool fill_nonce_from_additional_data(const uint8_t* data, static bool fill_nonce_from_additional_data(const uint8_t* data,
size_t len, size_t len,
uint8_t nonce[24], uint8_t nonce[24],
AdditionalTsState* ts_state,
bool normalize_timestamps,
bool* used_normalized,
bool* used_rtp_header) { bool* used_rtp_header) {
if (used_normalized) *used_normalized = false;
if (used_rtp_header) *used_rtp_header = false; if (used_rtp_header) *used_rtp_header = false;
if (!data || len < 8) return false; if (!data || len < 8) return false;
// Common native WebRTC layout: additional_data is RTP header bytes. // Desktop-compatible path: additional_data contains encoded frame timestamp
// as 8-byte BE value. Use it directly as nonce[0..7].
if (len == 8) {
memcpy(nonce, data, 8);
return true;
}
// Legacy native WebRTC layout: additional_data can be RTP header bytes.
if (len >= 12) { if (len >= 12) {
const uint8_t version = (data[0] >> 6) & 0x03; const uint8_t version = (data[0] >> 6) & 0x03;
if (version == 2) { if (version == 2) {
uint32_t ts = load32_be(data + 4); uint32_t ts = load32_be(data + 4);
if (normalize_timestamps && ts_state) {
if (!ts_state->initialized32) {
ts_state->initialized32 = true;
ts_state->base32 = ts;
}
ts = (uint32_t)(ts - ts_state->base32);
if (used_normalized) *used_normalized = true;
}
nonce[4] = (uint8_t)(ts >> 24); nonce[4] = (uint8_t)(ts >> 24);
nonce[5] = (uint8_t)(ts >> 16); nonce[5] = (uint8_t)(ts >> 16);
nonce[6] = (uint8_t)(ts >> 8); nonce[6] = (uint8_t)(ts >> 8);
@@ -246,17 +213,8 @@ static bool fill_nonce_from_additional_data(const uint8_t* data,
} }
} }
// Generic 8-byte timestamp layout (desktop's nonce[0..7] layout). // Generic fallback: first 8 bytes as BE timestamp-like payload.
uint64_t ts = load64_be(data); memcpy(nonce, data, 8);
if (normalize_timestamps && ts_state) {
if (!ts_state->initialized64) {
ts_state->initialized64 = true;
ts_state->base64 = ts;
}
ts = (uint64_t)(ts - ts_state->base64);
if (used_normalized) *used_normalized = true;
}
store64_be(nonce, ts);
return true; return true;
} }
@@ -401,7 +359,6 @@ public:
bool nonce_from_rtp_header = false; bool nonce_from_rtp_header = false;
bool nonce_from_generated_ts = false; bool nonce_from_generated_ts = false;
bool nonce_from_additional_data = false; bool nonce_from_additional_data = false;
bool nonce_from_additional_normalized = false;
bool additional_was_rtp_header = false; bool additional_was_rtp_header = false;
uint32_t generated_ts_used = 0; uint32_t generated_ts_used = 0;
@@ -411,9 +368,6 @@ public:
additional_data.data(), additional_data.data(),
additional_data.size(), additional_data.size(),
nonce, nonce,
&additional_ts_,
true,
&nonce_from_additional_normalized,
&additional_was_rtp_header); &additional_was_rtp_header);
if (!nonce_from_additional_data) { if (!nonce_from_additional_data) {
nonce_from_rtp_header = nonce_from_rtp_header =
@@ -464,9 +418,7 @@ public:
: (nonce_from_generated_ts : (nonce_from_generated_ts
? "gen" ? "gen"
: (nonce_from_additional_data : (nonce_from_additional_data
? (additional_was_rtp_header ? (additional_was_rtp_header ? "ad-rtp" : "raw-abs")
? (nonce_from_additional_normalized ? "ad-rtp-norm" : "ad-rtp")
: (nonce_from_additional_normalized ? "raw-norm" : "raw-abs"))
: "raw-abs")); : "raw-abs"));
LOGI("ENC frame#%d: sz=%zu ad=%zu hdr=%zu mode=%s nonce=%02x%02x%02x%02x", LOGI("ENC frame#%d: sz=%zu ad=%zu hdr=%zu mode=%s nonce=%02x%02x%02x%02x",
n, frame.size(), additional_data.size(), header_size, mode, n, frame.size(), additional_data.size(), header_size, mode,
@@ -490,7 +442,6 @@ private:
mutable std::atomic<int> diag_count_{0}; mutable std::atomic<int> diag_count_{0};
mutable RtpProbeState rtp_probe_; mutable RtpProbeState rtp_probe_;
mutable GeneratedTsState generated_ts_; mutable GeneratedTsState generated_ts_;
mutable AdditionalTsState additional_ts_;
uint8_t key_[32]; uint8_t key_[32];
}; };
@@ -533,17 +484,12 @@ public:
bool nonce_from_rtp_header = false; bool nonce_from_rtp_header = false;
bool nonce_from_generated_ts = false; bool nonce_from_generated_ts = false;
bool nonce_from_additional_data = false; bool nonce_from_additional_data = false;
bool nonce_from_additional_normalized = false;
bool additional_was_rtp_header = false; bool additional_was_rtp_header = false;
bool used_absolute_additional_fallback = false;
uint32_t generated_ts_used = 0; uint32_t generated_ts_used = 0;
nonce_from_additional_data = fill_nonce_from_additional_data( nonce_from_additional_data = fill_nonce_from_additional_data(
additional_data.data(), additional_data.data(),
additional_data.size(), additional_data.size(),
nonce, nonce,
&additional_ts_,
true,
&nonce_from_additional_normalized,
&additional_was_rtp_header); &additional_was_rtp_header);
if (!nonce_from_additional_data) { if (!nonce_from_additional_data) {
nonce_from_rtp_header = nonce_from_rtp_header =
@@ -581,55 +527,6 @@ public:
rosetta_xchacha20_xor(frame.data(), encrypted_frame.data(), encrypted_frame.size(), nonce, key_); rosetta_xchacha20_xor(frame.data(), encrypted_frame.data(), encrypted_frame.size(), nonce, key_);
} }
// additional_data on Android can be absolute RTP-ish timestamp, while
// desktop nonce source is normalized stream timestamp. If normalized
// nonce gives implausible Opus, retry with absolute additional_data.
if (!nonce_from_generated_ts &&
nonce_from_additional_data &&
encrypted_frame.size() > 0 &&
additional_data.size() >= 8) {
const uint8_t* payload_ptr = frame.data() + header_size;
const size_t payload_size = encrypted_frame.size() - header_size;
if (!is_plausible_opus_packet(payload_ptr, payload_size)) {
uint8_t nonce_abs[24] = {0};
bool abs_norm = false;
bool abs_rtp = false;
if (fill_nonce_from_additional_data(
additional_data.data(),
additional_data.size(),
nonce_abs,
nullptr,
false,
&abs_norm,
&abs_rtp) &&
memcmp(nonce_abs, nonce, 24) != 0) {
if (nonce_from_rtp_header && header_size <= encrypted_frame.size()) {
if (header_size > 0) {
memcpy(frame.data(), encrypted_frame.data(), header_size);
}
rosetta_xchacha20_xor(
frame.data() + header_size,
encrypted_frame.data() + header_size,
payload_size,
nonce_abs,
key_);
} else {
rosetta_xchacha20_xor(
frame.data(),
encrypted_frame.data(),
encrypted_frame.size(),
nonce_abs,
key_);
}
payload_ptr = frame.data() + header_size;
if (is_plausible_opus_packet(payload_ptr, payload_size)) {
memcpy(nonce, nonce_abs, 24);
used_absolute_additional_fallback = true;
}
}
}
}
if (nonce_from_generated_ts) { if (nonce_from_generated_ts) {
bool plausible = is_plausible_opus_packet(frame.data(), encrypted_frame.size()); bool plausible = is_plausible_opus_packet(frame.data(), encrypted_frame.size());
@@ -669,13 +566,8 @@ public:
mode = "rtp"; mode = "rtp";
} else if (nonce_from_generated_ts) { } else if (nonce_from_generated_ts) {
mode = used_generated_resync ? "gen-resync" : "gen"; mode = used_generated_resync ? "gen-resync" : "gen";
} else if (used_absolute_additional_fallback) {
mode = additional_was_rtp_header ? "ad-rtp-abs-fb" : "raw-abs-fb";
} else if (nonce_from_additional_data) { } else if (nonce_from_additional_data) {
mode = mode = additional_was_rtp_header ? "ad-rtp" : "raw-abs";
additional_was_rtp_header
? (nonce_from_additional_normalized ? "ad-rtp-norm" : "ad-rtp")
: (nonce_from_additional_normalized ? "raw-norm" : "raw-abs");
} else { } else {
mode = "raw-abs"; mode = "raw-abs";
} }
@@ -702,7 +594,6 @@ private:
mutable std::atomic<int> diag_count_{0}; mutable std::atomic<int> diag_count_{0};
mutable RtpProbeState rtp_probe_; mutable RtpProbeState rtp_probe_;
mutable GeneratedTsState generated_ts_; mutable GeneratedTsState generated_ts_;
mutable AdditionalTsState additional_ts_;
uint8_t key_[32]; uint8_t key_[32];
}; };

View File

@@ -234,7 +234,12 @@ object CallManager {
} }
fun endCall() { fun endCall() {
breadcrumb("UI: endCall requested") val callerTrace =
Throwable()
.stackTrace
.take(6)
.joinToString(" | ") { "${it.className}.${it.methodName}:${it.lineNumber}" }
breadcrumb("UI: endCall requested by $callerTrace")
resetSession(reason = null, notifyPeer = true) resetSession(reason = null, notifyPeer = true)
} }
@@ -605,29 +610,30 @@ object CallManager {
onCallConnected() onCallConnected()
} }
PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.FAILED,
PeerConnection.PeerConnectionState.CLOSED -> { PeerConnection.PeerConnectionState.CLOSED,
disconnectResetJob?.cancel()
disconnectResetJob = null
// Dispatch to our scope — this callback fires on WebRTC thread
scope.launch {
resetSession(reason = "Connection lost", notifyPeer = false)
}
}
PeerConnection.PeerConnectionState.DISCONNECTED -> { PeerConnection.PeerConnectionState.DISCONNECTED -> {
// Desktop tolerates short network dips; do not kill call immediately. // Desktop flow is signal-driven. Keep call alive across short
// transport glitches and only fallback-reset after timeout.
disconnectResetJob?.cancel() disconnectResetJob?.cancel()
disconnectResetJob = disconnectResetJob =
scope.launch { scope.launch {
delay(5_000L) val timeoutMs =
when (newState) {
PeerConnection.PeerConnectionState.DISCONNECTED -> 15_000L
PeerConnection.PeerConnectionState.FAILED -> 12_000L
PeerConnection.PeerConnectionState.CLOSED -> 12_000L
else -> 12_000L
}
delay(timeoutMs)
val pcState = peerConnection?.connectionState() val pcState = peerConnection?.connectionState()
if (pcState == PeerConnection.PeerConnectionState.DISCONNECTED || if (pcState == PeerConnection.PeerConnectionState.DISCONNECTED ||
pcState == PeerConnection.PeerConnectionState.FAILED || pcState == PeerConnection.PeerConnectionState.FAILED ||
pcState == PeerConnection.PeerConnectionState.CLOSED pcState == PeerConnection.PeerConnectionState.CLOSED
) { ) {
breadcrumb("PC: DISCONNECTED timeout → reset") breadcrumb("PC: $newState timeout ($timeoutMs ms) → reset")
resetSession(reason = "Connection lost", notifyPeer = false) resetSession(reason = "Connection lost", notifyPeer = false)
} else { } else {
breadcrumb("PC: DISCONNECTED recovered (state=$pcState)") breadcrumb("PC: $newState recovered (state=$pcState)")
} }
} }
} }