IQ.Pilot Release Commit @ 24db8ae

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-25 22:13:17 -05:00
parent 2f0ec679ec
commit 31a37f5a3c
67 changed files with 6134 additions and 137 deletions

View File

@@ -15,6 +15,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"AthenadPid", {PERSISTENT, INT}},
{"AthenadUploadQueue", {PERSISTENT, JSON}},
{"AthenadRecentlyViewedRoutes", {PERSISTENT, STRING}},
{"IQUploaderDeferred", {PERSISTENT, JSON}},
{"BackupManagerK3_CreateBackup", {CLEAR_ON_MANAGER_START, BOOL}},
{"BackupManagerK3_RestoreVersion", {CLEAR_ON_MANAGER_START, STRING}},
{"BootCount", {PERSISTENT, INT}},
@@ -269,6 +270,32 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"iqMqbAccResume", {PERSISTENT, BOOL, "0"}},
{"iqMqbSteeringLockout", {PERSISTENT, BOOL, "0"}},
{"AllowLateralWhenLongUnavailable", {PERSISTENT, BOOL}},
{"IQEmacEnabled", {PERSISTENT, BOOL, "0"}},
{"IQEmacHost", {PERSISTENT, STRING}},
{"IQEmacModel", {PERSISTENT, STRING}},
{"IQEmacCatalogCache", {PERSISTENT, JSON}},
{"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}},
{"IQEgpuEnabled", {PERSISTENT, BOOL, "0"}},
{"MacModelStatus", {CLEAR_ON_MANAGER_START, JSON}},
{"MacModelLatencyMs", {CLEAR_ON_MANAGER_START, FLOAT}},
{"MacModelMissRate", {CLEAR_ON_MANAGER_START, FLOAT}},
{"MacModelPresent", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelReachable", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelCompiled", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}},
{"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, FLOAT}},
{"UsbGpuStatus", {CLEAR_ON_MANAGER_START, JSON}},
{"UsbGpuLatencyMs", {CLEAR_ON_MANAGER_START, FLOAT}},
{"UsbGpuActive", {CLEAR_ON_MANAGER_START, BOOL}},
{"UsbGpuFailed", {CLEAR_ON_MANAGER_START, BOOL}},
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START, BOOL}},
{"UsbGpuLastError", {CLEAR_ON_MANAGER_START, STRING}},
{"UsbGpuSetupProgress", {CLEAR_ON_MANAGER_START, FLOAT}},
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START, BOOL}},
{"UsbGpuLoading", {CLEAR_ON_MANAGER_START, BOOL}},
{"IQDynamicMode", {PERSISTENT, BOOL, "0"}},
{"IQDynamicBlendStockRadar", {PERSISTENT, BOOL, "0"}},

View File

@@ -21,11 +21,21 @@ except Exception: # ProprietaryModuleMissing or import errors in stripped build
_private_base_urls = None
_private_auth = None
DEFAULT_TILE_BUNDLE_BASE_URL = "https://maps.konn3kt.com/iqosmd/v1"
# Tile bundles live as LFS objects in the PRIVATE repo IQ.Lvbs/iqmaps (R2 is gone).
# Anonymous access 404s by design; devices authenticate with the embedded read-only PAT
# carried by the closed-source updater bundle (same fetch account as the OS images).
DEFAULT_TILE_BUNDLE_BASE_URL = "https://git.konn3kt.com/IQ.Lvbs/iqmaps/raw/branch/master"
FALLBACK_TILE_BUNDLE_BASE_URL = "https://gitlvb.teallvbs.xyz/IQ.Lvbs/iqmaps/raw/branch/master"
# Gitea /raw NEVER returns LFS content -- it returns this pointer, and the real bytes come
# from the LFS batch API (see _resolve_object_url).
LFS_POINTER_MAGIC = b"version https://git-lfs"
BASE_URL_PARAM = "OfflineTilesBaseUrl"
PROGRESS_PARAM = "OfflineTilesDownloadProgress"
REQUEST_PARAM = "OfflineTilesDownloadRequest"
CHUNK_BYTES = 1 << 20
# must match scripts/iqpilot/tile_factory/upload_bundles_lfs.py
PART_BYTES = 90 * 1024 * 1024
HTTP_TIMEOUT_S = 30.0
STREAM_RETRIES = 8
@@ -44,9 +54,52 @@ def candidate_base_urls(params: Params) -> list[str]:
except Exception:
pass
urls.append(DEFAULT_TILE_BUNDLE_BASE_URL)
urls.append(FALLBACK_TILE_BUNDLE_BASE_URL)
return urls
def _maps_auth_module():
"""The read PAT lives in the compiled updater bundle (never in this open file)."""
try:
from iqpilot.system.proprietary_runtime._verified_import import import_verified_module
return import_verified_module("iqpilot_updater_private", "iqpilot_private.updater.git_remote")
except Exception:
pass
try:
import importlib
import os
import sys
root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
bundle_python = os.path.join(root, "artifacts", "iqpilot_updater_private", "python")
if os.path.isdir(bundle_python):
if bundle_python not in sys.path:
sys.path.insert(0, bundle_python)
return importlib.import_module("iqpilot_private.updater.git_remote")
except Exception:
pass
return None
def request_headers(url: str) -> dict:
mod = _maps_auth_module()
if mod is not None:
try:
headers = mod.map_tiles_headers(url)
if headers:
return headers
except Exception:
pass
try:
from iqpilot.common.git_creds import get_credentials
creds = get_credentials()
if creds and all(creds) and "/iq.lvbs/iqmaps" in url.lower():
import base64
return {"Authorization": "Basic " + base64.b64encode(f"{creds[0]}:{creds[1]}".encode()).decode()}
except Exception:
pass
return {}
def request_auth() -> tuple[str, str] | None:
if _private_auth is None:
return None
@@ -56,8 +109,67 @@ def request_auth() -> tuple[str, str] | None:
return None
def _lfs_endpoint(base_url: str) -> str:
"""<host>/<owner>/<repo>/raw/branch/<b> -> <host>/<owner>/<repo>.git/info/lfs"""
return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
def _resolve_oid_url(session: requests.Session, base_url: str, oid: str, size: int,
headers: dict) -> tuple[str, dict]:
"""Bundles are stored as bare LFS objects addressed by oid from the index -- no pointer
files, because committing one per part meant hundreds of concurrent commits per branch."""
batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
data=json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}),
headers={"Content-Type": "application/vnd.git-lfs+json",
"Accept": "application/vnd.git-lfs+json", **headers},
timeout=HTTP_TIMEOUT_S)
batch.raise_for_status()
entry = batch.json()["objects"][0]
if "actions" not in entry:
raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
action = entry["actions"]["download"]
return action["href"], action.get("header", {})
def _resolve_object_url(session: requests.Session, url: str, headers: dict) -> tuple[str, dict]:
"""Follow a Gitea LFS pointer to the real (pre-signed) object URL.
Returns the URL to stream plus any extra headers it needs. A plain host that serves the
bytes directly (local test server, static mirror) resolves to itself unchanged."""
probe = session.get(url, headers={**headers, "Accept-Encoding": None}, stream=True,
timeout=HTTP_TIMEOUT_S)
probe.raise_for_status()
if int(probe.headers.get("content-length") or 0) >= 1024:
probe.close()
return url, {}
body = probe.content
probe.close()
if not body.startswith(LFS_POINTER_MAGIC):
return url, {}
meta = dict(line.split(" ", 1) for line in body.decode().strip().splitlines() if " " in line)
oid = meta["oid"].split(":", 1)[1]
size = int(meta["size"])
lfs_base = url.split("/raw/", 1)[0] + ".git/info/lfs"
batch = session.post(f"{lfs_base}/objects/batch",
data=json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}),
headers={"Content-Type": "application/vnd.git-lfs+json",
"Accept": "application/vnd.git-lfs+json", **headers},
timeout=HTTP_TIMEOUT_S)
batch.raise_for_status()
action = batch.json()["objects"][0]["actions"]["download"]
return action["href"], action.get("header", {})
def fetch_index(base_url: str, session: requests.Session) -> dict:
response = session.get(f"{base_url}/index.json", timeout=HTTP_TIMEOUT_S, auth=request_auth())
index_url = f"{base_url}/index.json"
headers = request_headers(index_url)
# requests' auth= rewrites the Authorization header, so only fall back to it when the
# closed-source bundle gave us nothing.
response = session.get(index_url, timeout=HTTP_TIMEOUT_S, headers=headers,
auth=None if headers else request_auth())
response.raise_for_status()
index = response.json()
regions = index.get("regions")
@@ -164,7 +276,7 @@ class TileBundleDownloader:
ok = self._download_file(
selector, base_url, entry["path"], int(entry.get("bytes", 0)),
str(entry.get("sha256", "")).strip().lower(), night_path,
progress_offset, progress_total,
progress_offset, progress_total, int(entry.get("parts", 1)), entry.get("objects"),
)
if not ok:
return False
@@ -174,6 +286,7 @@ class TileBundleDownloader:
str(entry.get("day_sha256", "")).strip().lower(),
night_path.with_name("offline_day.mbtiles"),
progress_offset + int(entry.get("bytes", 0)), progress_total,
int(entry.get("day_parts", 1)), entry.get("day_objects"),
)
if not day_ok:
cloudlog.warning(f"iq_maps: day-style bundle failed for {selector}; night set installed")
@@ -183,8 +296,16 @@ class TileBundleDownloader:
def _download_file(self, selector: str, base_url: str, remote_path: str, expected_bytes: int,
expected_sha: str, final_path: Path,
progress_offset: int, progress_total: int) -> bool:
url = f"{base_url}/{remote_path.lstrip('/')}"
progress_offset: int, progress_total: int, parts: int = 1,
objects: list | None = None) -> bool:
# Bundles are published as <name>.pNN because Cloudflare caps proxied bodies at ~100MB.
# They stream back-to-back into ONE .part file: concatenating afterwards would need
# double the free space, which devices do not have.
base = f"{base_url}/{remote_path.lstrip('/')}"
if objects:
urls = [None] * len(objects) # resolved per-attempt from the oid
else:
urls = [base] if parts <= 1 else [f"{base}.p{i:02d}" for i in range(parts)]
part_path = final_path.with_name(final_path.name + ".part")
part_path.parent.mkdir(parents=True, exist_ok=True)
@@ -207,28 +328,45 @@ class TileBundleDownloader:
digest = hashlib.sha256()
resume_from = 0
headers = {"Range": f"bytes={resume_from}-"} if resume_from else {}
auth = request_auth()
response = self.session.get(url, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=auth)
if resume_from and response.status_code != 206:
digest = hashlib.sha256()
resume_from = 0
part_path.unlink(missing_ok=True)
if response.status_code == 416:
response = self.session.get(url, stream=True, timeout=HTTP_TIMEOUT_S, auth=auth)
response.raise_for_status()
# every part but the last is exactly PART_BYTES, so a byte offset maps to a part index
first_part = resume_from // PART_BYTES if len(urls) > 1 else 0
skip_in_part = resume_from - first_part * PART_BYTES if len(urls) > 1 else resume_from
downloaded = resume_from
mode = "ab" if resume_from else "wb"
with open(part_path, mode) as f:
for chunk in response.iter_content(chunk_size=CHUNK_BYTES):
if self._should_abort():
cloudlog.warning(f"iq_maps: tile bundle download cancelled for {selector}")
return False
f.write(chunk)
digest.update(chunk)
downloaded += len(chunk)
self._publish_progress(selector, progress_offset + downloaded, progress_total, active=True)
for index in range(first_part, len(urls)):
if objects:
url_headers = request_headers(base_url)
auth = None if url_headers else request_auth()
object_url, object_headers = _resolve_oid_url(
self.session, base_url, objects[index]["oid"], int(objects[index]["size"]),
url_headers)
else:
url = urls[index]
url_headers = request_headers(url)
auth = None if url_headers else request_auth()
# Re-resolve per part: a pre-signed LFS object URL can expire mid-download.
object_url, object_headers = _resolve_object_url(self.session, url, url_headers)
headers = dict(object_headers)
offset = skip_in_part if index == first_part else 0
if offset:
headers["Range"] = f"bytes={offset}-"
response = self.session.get(object_url, headers=headers, stream=True,
timeout=HTTP_TIMEOUT_S, auth=auth)
if offset and response.status_code != 206:
# server ignored the range: restart this whole file cleanly
f.close()
part_path.unlink(missing_ok=True)
raise requests.RequestException(f"range not honoured for part {index}")
response.raise_for_status()
for chunk in response.iter_content(chunk_size=CHUNK_BYTES):
if self._should_abort():
cloudlog.warning(f"iq_maps: tile bundle download cancelled for {selector}")
return False
f.write(chunk)
digest.update(chunk)
downloaded += len(chunk)
self._publish_progress(selector, progress_offset + downloaded, progress_total, active=True)
break
except requests.RequestException as exc:
last_error = exc

View File

@@ -48,6 +48,7 @@ CTRL_FLAG_MISSING_INPUTS = 1 << 0
CTRL_FLAG_RK_OVERRUN = 1 << 1
SMOOTH_STEER_W0 = 46.0
SMOOTH_STEER_ZETA = 1.0
SMOOTH_STEER_HOLD_FRAMES = 50
LAT_SMOOTH_SECONDS = 0.0
@@ -100,6 +101,7 @@ class Controls(IQControlsLayer):
self.VM = VehicleModel(self.CP)
self.smooth_steer = PT2Filter(SMOOTH_STEER_W0, SMOOTH_STEER_ZETA, DT_CTRL)
self.smooth_steer_inactive_frames = SMOOTH_STEER_HOLD_FRAMES
self.is_curvature_car = self.CP.steerControlType == car.CarParams.SteerControlType.curvatureDEPRECATED
if self.is_curvature_car and self.params.get("EnableSmoothSteer") is None:
self.params.put_bool("EnableSmoothSteer", True)
@@ -235,7 +237,8 @@ class Controls(IQControlsLayer):
else:
new_desired_curvature = model_v2.action.desiredCurvature
if self.is_curvature_car and self.enable_smooth_steer and CC.latActive:
self.smooth_steer_inactive_frames = 0 if CC.latActive else self.smooth_steer_inactive_frames + 1
if self.is_curvature_car and self.enable_smooth_steer and self.smooth_steer_inactive_frames < SMOOTH_STEER_HOLD_FRAMES:
new_desired_curvature = self.smooth_steer.update(new_desired_curvature)
else:
self.smooth_steer.reset(new_desired_curvature)

View File

@@ -13,7 +13,8 @@ def dmonitoringd_thread():
sm = messaging.SubMaster(['driverStateV2', 'extrinsicsCalibration', 'carState', 'selfdriveState', 'modelV2',
'carControl'], poll='driverStateV2')
DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM"))
DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM"),
force_rhd=params.get_bool("ForceRHDForBSM"))
demo_mode=False
# 20Hz <- dmonitoringmodeld
@@ -36,10 +37,11 @@ def dmonitoringd_thread():
# load live always-on toggle
if sm['driverStateV2'].frameId % 40 == 1:
DM.always_on = params.get_bool("AlwaysOnDM")
DM.force_rhd = params.get_bool("ForceRHDForBSM")
demo_mode = params.get_bool("IsDriverViewEnabled")
# save rhd virtual toggle every 5 mins
if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and
if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and not DM.force_rhd and
DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and
DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)):
params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right)

View File

@@ -145,7 +145,7 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib):
class DriverMonitoring:
def __init__(self, rhd_saved=False, settings=None, always_on=False):
def __init__(self, rhd_saved=False, settings=None, always_on=False, force_rhd=False):
# init policy settings
self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type())
@@ -158,6 +158,7 @@ class DriverMonitoring:
self.blink = DriverBlink()
self.always_on = always_on
self.force_rhd = force_rhd
self.distracted_types = []
self.driver_distracted = False
self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON)
@@ -268,18 +269,20 @@ class DriverMonitoring:
def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False):
rhd_pred = driver_state.wheelOnRightProb
# calibrates only when there's movement and either face detected
if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or
if not self.force_rhd and car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or
driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD):
self.wheelpos.prob_offseter.push_and_update(rhd_pred)
self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT
if self.wheelpos.prob_calibrated or demo_mode:
if self.force_rhd:
self.wheel_on_right = True
elif self.wheelpos.prob_calibrated or demo_mode:
self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD
else:
self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished
# make sure no switching when engaged
if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode:
if not self.force_rhd and op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode:
self.wheel_on_right = self.wheel_on_right_last
driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData
if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition,

View File

@@ -247,6 +247,22 @@ class TestMonitoring:
dm._update_states(ds, [0., 0., 0.], 20.0, True, False)
assert dm.wheel_on_right
def test_state_update_forces_rhd(self):
dm = DriverMonitoring(force_rhd=True)
dm.wheel_on_right_last = False
dm.wheelpos.prob_offseter.filtered_stat.M = 0.0
dm.wheelpos.prob_offseter.filtered_stat.n = dm.settings._WHEELPOS_FILTER_MIN_COUNT + 1
ds = log.DriverStateV2.new_message()
ds.wheelOnRightProb = 0.0
set_driver_data(ds.rightDriverData)
dm._update_states(ds, [0., 0., 0.], 20.0, True, False)
assert dm.wheel_on_right
assert dm.face_detected
assert dm.wheelpos.prob_offseter.filtered_stat.n == dm.settings._WHEELPOS_FILTER_MIN_COUNT + 1
assert dm.get_state_packet().driverMonitoringState.isRHD
def test_state_update_rejects_incomplete_driver_data(self):
dm = DriverMonitoring()
dm._update_states(log.DriverStateV2.new_message(), [0., 0., 0.], 0.0, False, False)

View File

@@ -93,6 +93,33 @@ class EngagedConfirmationButton(BigButton):
self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red))
class ForceOffroadButton(BigButton):
def __init__(self):
self._offroad_icon = gui_app.texture("icons/iq/square-parking.png", 64, 64)
super().__init__(tr("force\noffroad"), "", self._offroad_icon)
self.set_press_effect_enabled(False)
self._label.set_font_size(40)
self._label.set_line_height(0.95)
self.set_click_callback(self._on_click)
self._sync_from_params()
def _forced(self) -> bool:
return ui_state.params.get_bool("IQAlwaysOffroad")
def _on_click(self):
forced = self._forced()
action = tr("disable force offroad") if forced else tr("force offroad")
_engaged_confirmation_click(lambda: ui_state.params.put_bool("IQAlwaysOffroad", not forced),
action, self._offroad_icon, exit_on_confirm=False)
def _sync_from_params(self):
self.set_text(tr("disable\nforce offroad") if self._forced() else tr("force\noffroad"))
def _update_state(self):
super()._update_state()
self._sync_from_params()
class DeviceInfoLayoutMici(Widget):
def __init__(self):
super().__init__()
@@ -226,6 +253,7 @@ class DeviceLayoutMici(NavScroller):
terms_btn,
regulatory_btn,
reset_calibration_btn,
ForceOffroadButton(),
reboot_btn,
self._power_off_btn,
])

View File

@@ -1830,6 +1830,31 @@ msgstr ""
msgid "power off"
msgstr ""
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr ""
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr ""
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
#: openpilot/selfdrive/ui/mici/layouts/onboarding.py:100
#, python-brace-format
msgid ""

View File

@@ -3386,6 +3386,20 @@ msgstr "الجهاز"
msgid "device ID"
msgstr "معرّف الجهاز"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"إلغاء\n"
"فرض التوقف"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "إلغاء فرض وضع التوقف"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3543,11 +3557,26 @@ msgstr "لـ \"{}\""
msgid "for private branch updates"
msgstr "لتحديثات الفروع الخاصة"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"فرض\n"
"وضع التوقف"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "فرض واجهة mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "فرض وضع التوقف"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3453,6 +3453,20 @@ msgstr "gerät"
msgid "device ID"
msgstr "geräte-ID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"parkmodus\n"
"freigeben"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "parkmodus freigeben"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3610,11 +3624,26 @@ msgstr "für „{}“"
msgid "for private branch updates"
msgstr "für Updates aus privaten Branches"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"parkmodus\n"
"erzwingen"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "mici-oberfläche erzwingen"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "parkmodus erzwingen"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3411,6 +3411,20 @@ msgstr "device"
msgid "device ID"
msgstr "device ID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"disable\n"
"force offroad"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "disable force offroad"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3568,11 +3582,26 @@ msgstr "for \"{}\""
msgid "for private branch updates"
msgstr "for private branch updates"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"force\n"
"offroad"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "force mici UI"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "force offroad"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3458,6 +3458,20 @@ msgstr "dispositivo"
msgid "device ID"
msgstr "ID del dispositivo"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"salir de\n"
"aparcado"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "desactivar aparcado forzado"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3615,11 +3629,26 @@ msgstr "para «{}»"
msgid "for private branch updates"
msgstr "para actualizaciones de ramas privadas"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"forzar\n"
"aparcado"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "forzar interfaz mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "forzar aparcado"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3475,6 +3475,20 @@ msgstr "appareil"
msgid "device ID"
msgstr "ID appareil"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"quitter\n"
"hors route"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "désactiver hors route forcé"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3632,11 +3646,26 @@ msgstr "pour \"{}\""
msgid "for private branch updates"
msgstr "pour les mises à jour de branches privées"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"forcer\n"
"hors route"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "forcer l'interface mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "forcer hors route"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3281,6 +3281,20 @@ msgstr "デバイス"
msgid "device ID"
msgstr "デバイスID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"強制オフロード\n"
"解除"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "強制オフロードを解除"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3438,11 +3452,26 @@ msgstr "「{}」向け"
msgid "for private branch updates"
msgstr "プライベートブランチの更新用"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"強制\n"
"オフロード"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "mici UIを強制"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "強制オフロード"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3303,6 +3303,20 @@ msgstr "기기"
msgid "device ID"
msgstr "기기 ID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"강제 오프로드\n"
"해제"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "강제 오프로드 해제"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3460,11 +3474,26 @@ msgstr "\"{}\"용"
msgid "for private branch updates"
msgstr "비공개 브랜치 업데이트용"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"강제\n"
"오프로드"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "mici UI 강제"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "강제 오프로드"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3450,6 +3450,20 @@ msgstr "urządzenie"
msgid "device ID"
msgstr "ID urządzenia"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"wyłącz\n"
"wymuszenie"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "wyłącz wymuszony postój"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3607,11 +3621,26 @@ msgstr "dla „{}”"
msgid "for private branch updates"
msgstr "do aktualizacji z prywatnych gałęzi"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"wymuś\n"
"postój"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "wymuś interfejs mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "wymuś postój"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3452,6 +3452,20 @@ msgstr "dispositivo"
msgid "device ID"
msgstr "ID do dispositivo"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"sair de\n"
"estacionado"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "desativar estacionado forçado"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3609,11 +3623,26 @@ msgstr "para \"{}\""
msgid "for private branch updates"
msgstr "para atualizações de branches privadas"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"forçar\n"
"estacionado"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "forçar interface mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "forçar estacionado"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3392,6 +3392,20 @@ msgstr "อุปกรณ์"
msgid "device ID"
msgstr "รหัสอุปกรณ์"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"ปิดโหมด\n"
"จอดบังคับ"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "ปิดโหมดจอดแบบบังคับ"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3549,11 +3563,26 @@ msgstr "สำหรับ \"{}\""
msgid "for private branch updates"
msgstr "สำหรับการอัปเดตจากสาขาส่วนตัว"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"บังคับ\n"
"โหมดจอด"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "บังคับใช้อินเทอร์เฟซ mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "บังคับโหมดจอด"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3427,6 +3427,20 @@ msgstr "cihaz"
msgid "device ID"
msgstr "cihaz kimliği"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"zorlamayı\n"
"kapat"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "park zorlamasını kapat"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3584,11 +3598,26 @@ msgstr "\"{}\" için"
msgid "for private branch updates"
msgstr "özel dal güncellemeleri için"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"park modunu\n"
"zorla"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "mici arayüzünü zorla"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "park modunu zorla"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3436,6 +3436,20 @@ msgstr "пристрій"
msgid "device ID"
msgstr "ID пристрою"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"вимкнути\n"
"примус"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "вимкнути примусовий офроуд"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3593,11 +3607,26 @@ msgstr "для \"{}\""
msgid "for private branch updates"
msgstr "для оновлень із приватних гілок"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"офроуд\n"
"примусово"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "примусовий інтерфейс mici"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "примусовий офроуд"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3260,6 +3260,20 @@ msgstr "设备"
msgid "device ID"
msgstr "设备 ID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"关闭\n"
"强制驻车"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "关闭强制驻车"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3417,11 +3431,26 @@ msgstr "用于“{}”"
msgid "for private branch updates"
msgstr "用于私有分支更新"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"强制\n"
"驻车"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "强制使用 mici 界面"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "强制驻车"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -3262,6 +3262,20 @@ msgstr "裝置"
msgid "device ID"
msgstr "裝置 ID"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"disable\n"
"force offroad"
msgstr ""
"關閉\n"
"強制駐車"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "disable force offroad"
msgstr "關閉強制駐車"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:56
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:190
#: openpilot/selfdrive/ui/mici/layouts/settings/network/network_layout.py:199
@@ -3419,11 +3433,26 @@ msgstr "適用於「{}」"
msgid "for private branch updates"
msgstr "用於私有分支更新"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:99
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:116
#, python-brace-format
msgid ""
"force\n"
"offroad"
msgstr ""
"強制\n"
"駐車"
#: openpilot/selfdrive/ui/mici/layouts/settings/display.py:28
#, python-brace-format
msgid "force mici UI"
msgstr "強制使用 mici 介面"
#: openpilot/selfdrive/ui/mici/layouts/settings/device.py:111
#, python-brace-format
msgid "force offroad"
msgstr "強制駐車"
#: openpilot/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py:247
#, python-brace-format
msgid "forgetting..."

View File

@@ -13,7 +13,7 @@ from iqpilot.selfdrive.ui.ui_state import ui_state
_FPS_OVERRIDE = os.getenv("FPS")
UI_OFFROAD_FPS = int(os.getenv("UI_OFFROAD_FPS", _FPS_OVERRIDE or "60"))
UI_ONROAD_FPS = int(os.getenv("UI_ONROAD_FPS", _FPS_OVERRIDE or "20"))
UI_ONROAD_FPS = int(os.getenv("UI_ONROAD_FPS", _FPS_OVERRIDE or "60"))
def main():

View File

@@ -83,6 +83,7 @@ class WebRTCBaseStream(abc.ABC):
self._audio_track_state: List[Tuple[Track, Any, RtpPacketizationConfig]] = []
self._receiver_reports: Dict[str, RtcpReceiverReport] = {}
self._receiver_report_tracks: Dict[str, Tuple[Track, int]] = {}
self._negotiated_tracks: Dict[str, Track] = {}
self.incoming_media_ready_event = asyncio.Event()
self.messaging_channel_ready_event = asyncio.Event()
@@ -206,7 +207,11 @@ class WebRTCBaseStream(abc.ABC):
def _add_producer_tracks(self, remote_sdp: Optional[str] = None):
for track in self.outgoing_video_tracks:
media, ssrc, payload_type, cname = self._make_video_media(track, remote_sdp or "")
rtc_track = self.peer_connection.add_track(media)
rtc_track = self._negotiated_tracks.get(media.mid())
if rtc_track is not None:
rtc_track.set_description(media)
else:
rtc_track = self.peer_connection.add_track(media)
rtp_config = RtpPacketizationConfig(ssrc, cname, payload_type, H264RtpPacketizer.CLOCK_RATE)
rtp_config.start_timestamp = random.randint(0, 0xFFFFFFFF)
@@ -232,6 +237,8 @@ class WebRTCBaseStream(abc.ABC):
# same MID, which leaves libdatachannel stuck in signalling/ICE negotiation.
rtc_track = self.incoming_audio_tracks[0]
negotiated_media = rtc_track.description()
negotiated_media.clear_ssrcs()
negotiated_media.remove_attribute("msid")
negotiated_media.add_ssrc(ssrc, cname, "audio", "audio")
rtc_track.set_description(negotiated_media)
else:
@@ -276,6 +283,7 @@ class WebRTCBaseStream(abc.ABC):
def _on_incoming_track(self, track: Track):
self._log_debug("got track: %s", track.mid())
self._negotiated_tracks[track.mid()] = track
media_type = track.description().type()
if media_type == "audio":
if self.expected_incoming_audio:
@@ -478,6 +486,7 @@ class WebRTCBaseStream(abc.ABC):
self._audio_track_state.clear()
self._receiver_reports.clear()
self._receiver_report_tracks.clear()
self._negotiated_tracks.clear()
@abc.abstractmethod
async def start(self) -> RTCSessionDescription:

View File

@@ -19,8 +19,17 @@ async def test_native_video_audio_and_data_channel():
async def connect(offer):
nonlocal answer_session
answer_session = StreamSession(offer.sdp, offer.video, [], [], [], debug_mode=True)
browser_offer = offer.sdp.replace("profile-level-id=42e01f", "profile-level-id=640c1f", 1)
audio_offset = browser_offer.index("m=audio")
browser_offer = browser_offer[:audio_offset] + browser_offer[audio_offset:].replace(
"a=recvonly",
"a=sendrecv\r\na=msid:ios-microphone ios-audio-track\r\na=ssrc:123456 cname:ios-audio\r\na=ssrc:123456 msid:ios-microphone ios-audio-track",
1,
)
answer_session = StreamSession(browser_offer, offer.video, [], [], [], debug_mode=True)
answer = await answer_session.get_answer()
assert not any(line.startswith("m=video 0 ") for line in answer.sdp.splitlines())
assert answer_session.stream._track_state[0][0] is answer_session.stream._negotiated_tracks["road"]
answer_session.start()
return RTCSessionDescription(answer.sdp, answer.type)
@@ -56,9 +65,16 @@ async def test_native_duplex_audio_negotiation():
async def connect(offer):
nonlocal answer_session
sendrecv_offer = offer.sdp.replace("a=recvonly", "a=sendrecv", 1)
sendrecv_offer = offer.sdp.replace(
"a=recvonly",
"a=sendrecv\r\na=msid:ios-microphone ios-audio-track\r\na=ssrc:123456 cname:ios-audio\r\na=ssrc:123456 msid:ios-microphone ios-audio-track",
1,
)
answer_session = StreamSession(sendrecv_offer, offer.video, [], [], [], debug_mode=True)
answer = await answer_session.get_answer()
assert "ios-microphone" not in answer.sdp
assert "ios-audio-track" not in answer.sdp
assert answer.sdp.count("a=msid:audio audio") == 1
answer_session.start()
return RTCSessionDescription(answer.sdp, answer.type)