Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ddb7fc764 | |||
| 22ced0c558 | |||
| ef4e02e354 | |||
| 02f25f83c4 | |||
| ceb3481cdc | |||
| bae022db43 | |||
| 1e36d7ec23 | |||
| 6f3b1b1d2f | |||
| 7a1e157c9c | |||
| 2d819c784b | |||
| 1eb8d41454 | |||
| 2331aa00a0 | |||
| dfb7b7404f | |||
| 9ac334b7cf | |||
| 3cbb81f9f1 | |||
| 3b47412100 |
@@ -15,7 +15,7 @@ ClearPilot is a custom fork of **FrogPilot** (itself a fork of comma.ai's openpi
|
||||
- **Native dashcamd**: C++ process capturing raw camera frames via VisionIPC with OMX H.264 hardware encoding
|
||||
- **Standstill power saving**: model inference throttled to 1fps and fan quieted when car is stopped
|
||||
- **ClearPilot menu**: sidebar settings panel replacing stock home screen (Home, Dashcam, Debug panels)
|
||||
- **Status window**: live system stats (temp, fan, storage, RAM, WiFi, VPN, GPS, telemetry status)
|
||||
- **Status window**: live system stats (temp, fan, storage, RAM, WiFi, VPN, GPS, telemetry, dashcam status)
|
||||
- **Debug button (LFA)**: steering wheel button repurposed for screen toggle and future UI actions
|
||||
- **Telemetry system**: diff-based CSV logger via ZMQ IPC, toggleable from Debug panel
|
||||
- **Bench mode**: `--bench` flag for onroad UI testing without car connected
|
||||
@@ -101,7 +101,7 @@ ClearPilot uses memory params (`/dev/shm/params/d/`) for UI toggles that should
|
||||
- **Python access**: Use `Params("/dev/shm/params")`
|
||||
- **Defaults**: Set in `manager_init()` via `Params("/dev/shm/params").put(key, value)`
|
||||
- **UI toggles**: Use `ToggleControl` with manual `toggleFlipped` lambda that writes via `Params("/dev/shm/params")`. Do NOT use `ParamControl` for memory params — it reads/writes persistent params only
|
||||
- **Current memory params**: `TelemetryEnabled` (default "0"), `VpnEnabled` (default "1"), `ModelStandby` (default "0"), `ScreenDisplayMode`
|
||||
- **Current memory params**: `TelemetryEnabled` (default "0"), `VpnEnabled` (default "1"), `ModelStandby` (default "0"), `ScreenDisplayMode`, `DashcamState` (default "stopped"), `DashcamFrames` (default "0")
|
||||
- **IMPORTANT — method names differ between C++ and Python**: C++ uses camelCase (`putBool`, `getBool`, `getInt`), Python uses snake_case (`put_bool`, `get_bool`, `get_int`). This is a common source of silent failures — the wrong casing compiles/runs but doesn't work.
|
||||
|
||||
### Building Native (C++) Processes
|
||||
@@ -195,6 +195,43 @@ The UI process runs a ZMQ REP server at `ipc:///tmp/clearpilot_ui_rpc`. Send `"d
|
||||
- **`showDriverView` overriding transitions (fixed)**: was forcing `slayout` to onroad/home every frame at 20Hz, overriding park/drive logic. Fixed to only act when not in started state.
|
||||
- **Sidebar appearing during onroad transition (fixed)**: `MainWindow::closeSettings()` was re-enabling the sidebar. Fixed by not calling `closeSettings` during `offroadTransition`.
|
||||
|
||||
## Performance Profiling
|
||||
|
||||
Use `py-spy` to find CPU hotspots in any Python process. It's installed at `/home/comma/.local/bin/py-spy`. (If missing: `su - comma -c "/usr/local/pyenv/versions/3.11.4/bin/pip install py-spy"`.)
|
||||
|
||||
```bash
|
||||
# Find the target pid
|
||||
ps -eo pid,cmd | grep -E "selfdrive.controls.controlsd" | grep -v grep
|
||||
|
||||
# Record 10s of stacks at 200Hz, raw (folded) format
|
||||
/home/comma/.local/bin/py-spy record -o /tmp/ctrl.txt --pid <PID> --duration 10 --rate 200 --format raw
|
||||
|
||||
# Aggregate: which line of step() is consuming the most samples
|
||||
awk -F';' '{
|
||||
for(i=1;i<=NF;i++) if ($i ~ /step \(selfdrive\/controls\/controlsd.py/) step_line=i;
|
||||
if (step_line && step_line < NF) {
|
||||
n=split($NF, parts, " "); count=parts[n];
|
||||
caller = $(step_line+1);
|
||||
sum[caller] += count;
|
||||
}
|
||||
step_line=0;
|
||||
} END { for (c in sum) printf "%6d %s\n", sum[c], c }' /tmp/ctrl.txt | sort -rn | head -15
|
||||
|
||||
# Aggregate by a source file — shows hottest lines in that file
|
||||
awk -F';' '{
|
||||
for(i=1;i<=NF;i++) if ($i ~ /carstate\.py:/) {
|
||||
match($i, /:[0-9]+/); ln = substr($i, RSTART+1, RLENGTH-1);
|
||||
n=split($NF, parts, " "); count=parts[n];
|
||||
sum[ln] += count;
|
||||
}
|
||||
} END { for (l in sum) printf "%5d line %s\n", sum[l], l }' /tmp/ctrl.txt | sort -rn | head -15
|
||||
|
||||
# Quick stack dump (single sample, no recording)
|
||||
/home/comma/.local/bin/py-spy dump --pid <PID>
|
||||
```
|
||||
|
||||
**Known performance trap — hot `Params` writes**: `Params.put()` does `mkstemp` + `fsync` + `flock` + `rename` + `fsync_dir`. At 100Hz even on tmpfs the `flock` contention is ruinous. Cache the last-written value and skip writes when unchanged. Found this pattern in `carstate.py` and `controlsd.py` — controlsd went from 69% → 28% CPU after gating writes.
|
||||
|
||||
## Session Logging
|
||||
|
||||
Per-process stderr and an aggregate event log are captured in `/data/log2/current/`.
|
||||
@@ -245,20 +282,21 @@ A single `session.log` in each session directory records major events:
|
||||
- **Segment length**: 3 minutes per file
|
||||
- **Save path**: `/data/media/0/videos/YYYYMMDD-HHMMSS/YYYYMMDD-HHMMSS.mp4` (trip directories)
|
||||
- **GPS subtitles**: companion `.srt` file per segment with 1Hz entries (speed MPH, lat/lon, UTC timestamp)
|
||||
- **Trip lifecycle**: starts recording on launch with 10-min idle timer; car in drive cancels timer; park/off restarts timer; ignition cycle = new trip
|
||||
- **Trip lifecycle**: waits in WAITING state until valid system time + GPS fix + car in drive; records until car parked 10 min or ignition off; then returns to WAITING
|
||||
- **Graceful shutdown**: thermald sets `DashcamShutdown` param, dashcamd closes segment and acks within 15s
|
||||
- **Storage**: ~56 MB per 3-minute segment at 2500 kbps
|
||||
- **Storage**: ~56 MB per 3-minute segment at 2500 kbps (verified: actual bitrate ~2570 kbps)
|
||||
- **Crash handler**: SIGSEGV/SIGABRT handler writes backtrace to `/tmp/dashcamd_crash.log`
|
||||
- **Storage device**: WDC SDINDDH4-128G UFS 2.1 — automotive grade, ~384 TB write endurance, no concern for continuous writes
|
||||
|
||||
### Key Differences from Old Screen Recorder
|
||||
### OmxEncoder
|
||||
|
||||
| | Old (screen recorder) | New (dashcamd) |
|
||||
|---|---|---|
|
||||
| Source | `QWidget::grab()` screen capture | Raw NV12 from VisionIPC |
|
||||
| Resolution | 1440x720 | 1928x1208 |
|
||||
| Works with screen off | No (needs visible widget) | Yes (independent of UI) |
|
||||
| Process type | Part of UI process | Standalone native process |
|
||||
| Encoder input | RGBA -> NV12 conversion | NV12 direct (added `encode_frame_nv12`) |
|
||||
The OMX encoder (`selfdrive/frogpilot/screenrecorder/omx_encoder.cc`) was ported from upstream FrogPilot with the following key properties:
|
||||
|
||||
- Each encoder instance calls `OMX_Init()` in constructor and `OMX_Deinit()` in destructor — manages its own OMX lifecycle
|
||||
- Constructor takes 5 args: `(path, width, height, fps, bitrate)` — no h265/downscale params
|
||||
- `encoder_close()` calls `av_write_trailer` + ffmpeg faststart remux (`-movflags +faststart`)
|
||||
- Destructor has null guards and error handling on all OMX state transitions
|
||||
- ClearPilot addition: `encode_frame_nv12()` for direct NV12 input (dashcamd), alongside original `encode_frame_rgba()` (screen recorder)
|
||||
|
||||
### Key Files
|
||||
|
||||
@@ -266,7 +304,7 @@ A single `session.log` in each session directory records major events:
|
||||
|------|------|
|
||||
| `selfdrive/clearpilot/dashcamd.cc` | Main dashcam process — VisionIPC -> OMX encoder |
|
||||
| `selfdrive/clearpilot/SConscript` | Build config for dashcamd |
|
||||
| `selfdrive/frogpilot/screenrecorder/omx_encoder.cc` | OMX encoder (added `encode_frame_nv12` method) |
|
||||
| `selfdrive/frogpilot/screenrecorder/omx_encoder.cc` | OMX encoder (upstream FrogPilot port + `encode_frame_nv12`) |
|
||||
| `selfdrive/frogpilot/screenrecorder/omx_encoder.h` | Encoder header |
|
||||
| `selfdrive/manager/process_config.py` | dashcamd registered as NativeProcess, camerad always_run, encoderd disabled |
|
||||
| `system/loggerd/deleter.py` | Trip-aware storage rotation (oldest trip first, then segments within active trip) |
|
||||
@@ -275,12 +313,14 @@ A single `session.log` in each session directory records major events:
|
||||
|
||||
- `DashcamDebug` — when `"1"`, dashcamd runs even without car connected (for bench testing)
|
||||
- `DashcamShutdown` — set by thermald before power-off, dashcamd acks by clearing it
|
||||
- `DashcamState` (memory param) — "stopped", "waiting", or "recording" — published every 5s
|
||||
- `DashcamFrames` (memory param) — per-trip encoded frame count, resets each trip — published every 5s
|
||||
|
||||
## Standstill Power Saving
|
||||
|
||||
When `carState.standstill` is true:
|
||||
|
||||
- **modeld**: skips GPU inference on 19/20 frames (1fps vs 20fps), reports 0 frame drops to avoid triggering `modeldLagging` in controlsd
|
||||
- **modeld**: skips GPU inference on 19/20 frames (1fps vs 20fps), reports 0 frame drops to avoid triggering `modeldLagging` in controlsd. Runs full 20fps during calibration (`liveCalibration.calStatus != calibrated`)
|
||||
- **dmonitoringmodeld**: same 1fps throttle, added `carState` subscription
|
||||
- **Fan controller**: uses offroad clamps (0-30%) instead of onroad (30-100%) at standstill; thermal protection still active via feedforward if temp > 60°C
|
||||
|
||||
@@ -321,6 +361,12 @@ The Hyundai Tucson's LFA steering wheel button cycles through 5 display modes vi
|
||||
|
||||
**Not in drive (parked/off):** any except 3 → 3 (screen off), state 3 → 0 (auto-normal)
|
||||
|
||||
**Shift to drive from screen off:** auto-resets to mode 0 (auto-normal) via `controlsd`
|
||||
|
||||
**Shift to park from nightrider:** auto-switches to mode 3 (screen off) via `home.cc`
|
||||
|
||||
**Tap screen while screen off:** resets to mode 0 (auto-normal) via `window.cc` touch handler
|
||||
|
||||
### Nightrider Mode
|
||||
|
||||
- Camera feed suppressed (OpenGL clears to black instead of rendering camera texture)
|
||||
@@ -357,7 +403,10 @@ Display power is managed by `Device::updateWakefulness()` in `selfdrive/ui/ui.cc
|
||||
|
||||
- **Ignition off (offroad)**: screen blanks after `ScreenTimeout` seconds (default 120) of no touch. Tapping wakes it.
|
||||
- **Ignition on (onroad)**: screen stays on unconditionally — ignition=true short-circuits the timeout check.
|
||||
- **Debug button (LFA)**: cycles through display modes including screen off (state 3). Only state 3 calls `Hardware::set_display_power(false)`.
|
||||
- **ScreenDisplayMode 3 override**: `updateWakefulness` checks `ScreenDisplayMode` first — if mode 3, calls `setAwake(false)` unconditionally, preventing ignition-on from overriding screen-off.
|
||||
- **Debug button (LFA)**: cycles through display modes including screen off (state 3).
|
||||
- **Park transition**: always shows splash screen; if coming from nightrider mode, auto-switches to screen off (mode 3) via `home.cc`.
|
||||
- **Touch wake**: tapping screen while in mode 3 resets to mode 0 via `window.cc` event filter.
|
||||
|
||||
## Offroad UI (ClearPilot Menu)
|
||||
|
||||
@@ -500,7 +549,7 @@ Power On
|
||||
- **GPS data**: logged directly by telemetryd via cereal `gpsLocation` subscription at 1Hz — group: `gps` (latitude, longitude, speed, altitude, bearing, accuracy)
|
||||
- **CSV location**: `/data/log2/current/telemetry.csv` (or session directory)
|
||||
- **Onroad overlay**: when telemetry enabled, status bar shows temp, fan %, model FPS, and STANDSTILL indicator
|
||||
- **Speed limit**: `speed_limit.calculated` is the final computed speed limit value (in vehicle units, MPH when `is_metric=False`). This is the value that will be used for the future speed limit warning chime feature
|
||||
- **Speed limit**: processed by `selfdrive/clearpilot/speed_logic.py` (SpeedState class), converts m/s to display units, writes to memory params. Cruise warning signs appear when cruise set speed exceeds speed limit by threshold (10 mph if limit >= 50, 7 mph if < 50) or is 5+ mph under. Ding sound plays when warning sign appears or speed limit changes while visible (30s cooldown)
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
|
||||
+6
-1
@@ -110,6 +110,8 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"CurrentBootlog", PERSISTENT},
|
||||
{"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||
{"DashcamDebug", PERSISTENT},
|
||||
{"DashcamFrames", PERSISTENT},
|
||||
{"DashcamState", PERSISTENT},
|
||||
{"DashcamShutdown", CLEAR_ON_MANAGER_START},
|
||||
{"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||
{"DisablePowerDown", PERSISTENT},
|
||||
@@ -192,12 +194,15 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"RecordFrontLock", PERSISTENT}, // for the internal fleet
|
||||
{"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},
|
||||
{"RouteCount", PERSISTENT},
|
||||
{"ShutdownTouchReset", PERSISTENT},
|
||||
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
|
||||
{"SshEnabled", PERSISTENT},
|
||||
{"TermsVersion", PERSISTENT},
|
||||
{"TelemetryEnabled", PERSISTENT},
|
||||
{"Timezone", PERSISTENT},
|
||||
{"TrainingVersion", PERSISTENT},
|
||||
{"LatRequested", PERSISTENT},
|
||||
{"ModelFps", PERSISTENT},
|
||||
{"ModelStandby", PERSISTENT},
|
||||
{"ModelStandbyTs", PERSISTENT},
|
||||
{"UbloxAvailable", PERSISTENT},
|
||||
@@ -246,7 +251,6 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"CarMake", PERSISTENT},
|
||||
{"CarModel", PERSISTENT},
|
||||
|
||||
{"CarCruiseDisplayActual", PERSISTENT},
|
||||
{"CarSpeedLimit", PERSISTENT},
|
||||
|
||||
{"CarSpeedLimitWarning", PERSISTENT},
|
||||
@@ -288,6 +292,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"CEStopLights", PERSISTENT},
|
||||
{"CEStopLightsLead", PERSISTENT},
|
||||
{"Compass", PERSISTENT},
|
||||
{"ClearpilotShowHealthMetrics", PERSISTENT},
|
||||
{"ConditionalExperimental", PERSISTENT},
|
||||
{"CrosstrekTorque", PERSISTENT},
|
||||
{"CurrentHolidayTheme", PERSISTENT},
|
||||
|
||||
@@ -57,6 +57,10 @@ class CarController(CarControllerBase):
|
||||
self.car_fingerprint = CP.carFingerprint
|
||||
self.last_button_frame = 0
|
||||
|
||||
# CLEARPILOT: cache Params instance — create_steering_messages was building
|
||||
# a new one every 10ms (100Hz), causing needless allocation + file opens
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
def update(self, CC, CS, now_nanos, frogpilot_variables):
|
||||
actuators = CC.actuators
|
||||
hud_control = CC.hudControl
|
||||
@@ -112,7 +116,8 @@ class CarController(CarControllerBase):
|
||||
hda2_long = hda2 and self.CP.openpilotLongitudinalControl
|
||||
|
||||
# steering control
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_steer))
|
||||
no_lat_lane_change = self.params_memory.get_int("no_lat_lane_change", 1)
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_steer, no_lat_lane_change))
|
||||
|
||||
# prevent LFA from activating on HDA2 by sending "no lane lines detected" to ADAS ECU
|
||||
if self.frame % 5 == 0 and hda2:
|
||||
|
||||
@@ -48,6 +48,10 @@ class CarState(CarStateBase):
|
||||
self.is_metric = False
|
||||
self.buttons_counter = 0
|
||||
|
||||
# CLEARPILOT: cache to avoid per-cycle atomic writes to /dev/shm (eats CPU via fsync/flock)
|
||||
self._prev_car_speed_limit = None
|
||||
self._prev_car_is_metric = None
|
||||
|
||||
self.cruise_info = {}
|
||||
|
||||
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
|
||||
@@ -210,11 +214,15 @@ class CarState(CarStateBase):
|
||||
self.lkas_previously_enabled = self.lkas_enabled
|
||||
self.lkas_enabled = cp.vl["BCM_PO_11"]["LFA_Pressed"]
|
||||
|
||||
# self.params_memory.put_int("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam))
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_conv)
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
self.params_memory.put_float("CarCruiseDisplayActual", cp_cruise.vl["SCC11"]["VSetDis"])
|
||||
|
||||
# CLEARPILOT: gate on change — see same fix in update_canfd
|
||||
car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_conv
|
||||
if car_speed_limit != self._prev_car_speed_limit:
|
||||
self.params_memory.put_float("CarSpeedLimit", car_speed_limit)
|
||||
self._prev_car_speed_limit = car_speed_limit
|
||||
if self.is_metric != self._prev_car_is_metric:
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
self._prev_car_is_metric = self.is_metric
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
@@ -417,11 +425,14 @@ class CarState(CarStateBase):
|
||||
# nonAdaptive = false,
|
||||
# speedCluster = 0 )
|
||||
|
||||
# print("Set limit")
|
||||
# print(self.calculate_speed_limit(cp, cp_cam))
|
||||
# self.params_memory.put_float("CarSpeedLimitLiteral", self.calculate_speed_limit(cp, cp_cam))
|
||||
self.params_memory.put_float("CarSpeedLimit", self.calculate_speed_limit(cp, cp_cam) * speed_factor)
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
# CLEARPILOT: gate on change — these writes run 100Hz, each is an atomic fsync/flock transaction
|
||||
car_speed_limit = self.calculate_speed_limit(cp, cp_cam) * speed_factor
|
||||
if car_speed_limit != self._prev_car_speed_limit:
|
||||
self.params_memory.put_float("CarSpeedLimit", car_speed_limit)
|
||||
self._prev_car_speed_limit = car_speed_limit
|
||||
if self.is_metric != self._prev_car_is_metric:
|
||||
self.params_memory.put("CarIsMetric", "1" if self.is_metric else "0")
|
||||
self._prev_car_is_metric = self.is_metric
|
||||
|
||||
# CLEARPILOT: telemetry logging — disabled, re-enable when needed
|
||||
# speed_limit_bus = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam
|
||||
|
||||
@@ -2,7 +2,6 @@ from openpilot.common.numpy_fast import clip
|
||||
from openpilot.selfdrive.car import CanBusBase
|
||||
from openpilot.selfdrive.car.hyundai.values import HyundaiFlags
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
class CanBus(CanBusBase):
|
||||
def __init__(self, CP, hda2=None, fingerprint=None) -> None:
|
||||
@@ -36,12 +35,9 @@ class CanBus(CanBusBase):
|
||||
return self._cam
|
||||
|
||||
|
||||
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_steer):
|
||||
|
||||
# Im sure there is a better way to do this
|
||||
params_memory = Params("/dev/shm/params")
|
||||
no_lat_lane_change = params_memory.get_int("no_lat_lane_change", 1)
|
||||
|
||||
def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_steer, no_lat_lane_change=0):
|
||||
# CLEARPILOT: no_lat_lane_change is passed in by the caller so we can hoist
|
||||
# the Params read out of the 100Hz hot path (was ~5% of carcontroller time)
|
||||
ret = []
|
||||
|
||||
values = {
|
||||
@@ -130,9 +126,7 @@ def create_buttons(packer, CP, CAN, cnt, btn):
|
||||
|
||||
def create_buttons_alt(packer, CP, CAN, cnt, btn, template):
|
||||
return
|
||||
params_memory = Params("/dev/shm/params")
|
||||
CarCruiseDisplayActual = params_memory.get_float("CarCruiseDisplayActual")
|
||||
|
||||
|
||||
values = {
|
||||
"COUNTER": cnt,
|
||||
"NEW_SIGNAL_1": 0.0,
|
||||
|
||||
Binary file not shown.
@@ -7,49 +7,39 @@
|
||||
* Trip directory structure:
|
||||
* /data/media/0/videos/YYYYMMDD-HHMMSS/ (trip directory, named at trip start)
|
||||
* YYYYMMDD-HHMMSS.mp4 (3-minute segments)
|
||||
* YYYYMMDD-HHMMSS.srt (GPS subtitle sidecar)
|
||||
*
|
||||
* Trip lifecycle state machine:
|
||||
*
|
||||
* On process start (after time-valid wait):
|
||||
* - Create trip directory, start recording immediately with 10-min idle timer
|
||||
* - If car is already in drive, timer is cancelled and recording continues
|
||||
* - If car stays parked/off for 10 minutes, trip ends
|
||||
* WAITING:
|
||||
* - Process starts in this state
|
||||
* - Waits for valid system time (year >= 2024) AND car in drive gear
|
||||
* - Transitions to RECORDING when both conditions met
|
||||
*
|
||||
* IDLE_TIMEOUT → RECORDING:
|
||||
* - Car enters drive gear before timer expires → cancel timer, resume recording
|
||||
* in the same trip (no new trip directory)
|
||||
* RECORDING:
|
||||
* - Actively encoding frames, car is in drive
|
||||
* - Car leaves drive → start 10-min idle timer → IDLE_TIMEOUT
|
||||
*
|
||||
* RECORDING → IDLE_TIMEOUT:
|
||||
* - Car leaves drive gear (park, off, neutral) → start 10-minute idle timer,
|
||||
* continue recording frames during the timeout period
|
||||
*
|
||||
* IDLE_TIMEOUT → TRIP_ENDED:
|
||||
* - 10-minute timer expires → close segment, trip is over
|
||||
*
|
||||
* TRIP_ENDED → RECORDING (new trip):
|
||||
* - Car enters drive gear → create new trip directory, start recording
|
||||
*
|
||||
* Any state → (new trip) on ignition off→on:
|
||||
* - Ignition transitions off→on → close current trip if active, create new trip
|
||||
* directory, start recording with 10-min idle timer. This applies even from
|
||||
* TRIP_ENDED — turning the car on always starts a new trip. If the car is in
|
||||
* park after ignition on, the idle timer runs; entering drive cancels it.
|
||||
* IDLE_TIMEOUT:
|
||||
* - Car left drive, still recording with timer running
|
||||
* - Car re-enters drive → cancel timer → RECORDING
|
||||
* - Timer expires → close trip → WAITING
|
||||
* - Ignition off → close trip → WAITING
|
||||
*
|
||||
* Graceful shutdown (DashcamShutdown param):
|
||||
* - thermald sets DashcamShutdown="1" before device power-off
|
||||
* - dashcamd closes current segment, sets DashcamShutdown="0" (ack), exits
|
||||
* - thermald waits up to 15s for ack, then proceeds with shutdown
|
||||
* - dashcamd closes current segment, acks, exits
|
||||
*
|
||||
* GPS subtitle track:
|
||||
* - Each .mp4 segment has a companion .srt subtitle file
|
||||
* - Updated at most once per second from gpsLocation cereal messages
|
||||
* - Format: "35 MPH | 44.9216°N 93.3260°W | 2026-04-13 05:19:00 UTC"
|
||||
* - Most players auto-detect .srt files alongside .mp4 files
|
||||
* Published params (memory, every 5s):
|
||||
* - DashcamState: "waiting" or "recording"
|
||||
* - DashcamFrames: per-trip encoded frame count (resets each trip)
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <signal.h>
|
||||
#include <execinfo.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
@@ -73,10 +63,9 @@ const double IDLE_TIMEOUT_SECONDS = 600.0; // 10 minutes
|
||||
ExitHandler do_exit;
|
||||
|
||||
enum TripState {
|
||||
IDLE, // no trip active, waiting for drive
|
||||
WAITING, // no trip, waiting for valid time + drive gear
|
||||
RECORDING, // actively recording, car in drive
|
||||
IDLE_TIMEOUT, // car parked/off, recording with 10-min timer
|
||||
TRIP_ENDED, // trip closed, waiting for next drive
|
||||
IDLE_TIMEOUT, // car left drive, recording with 10-min timer
|
||||
};
|
||||
|
||||
static std::string make_timestamp() {
|
||||
@@ -115,21 +104,26 @@ static std::string srt_time(int seconds) {
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void crash_handler(int sig) {
|
||||
FILE *f = fopen("/tmp/dashcamd_crash.log", "w");
|
||||
if (f) {
|
||||
fprintf(f, "CRASH: signal %d\n", sig);
|
||||
void *bt[30];
|
||||
int n = backtrace(bt, 30);
|
||||
backtrace_symbols_fd(bt, n, fileno(f));
|
||||
fclose(f);
|
||||
}
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
signal(SIGSEGV, crash_handler);
|
||||
signal(SIGABRT, crash_handler);
|
||||
setpriority(PRIO_PROCESS, 0, -10);
|
||||
|
||||
// Ensure base output directory exists
|
||||
mkdir(VIDEOS_BASE.c_str(), 0755);
|
||||
|
||||
// Wait for valid system time so trip/segment names have real timestamps
|
||||
LOGW("dashcamd: waiting for system time");
|
||||
while (!do_exit) {
|
||||
if (system_time_valid()) break;
|
||||
usleep(1000000); // 1 Hz poll
|
||||
}
|
||||
if (do_exit) return 0;
|
||||
LOGW("dashcamd: system time valid");
|
||||
|
||||
LOGW("dashcamd: started, connecting to camerad road stream");
|
||||
VisionIpcClient vipc("camerad", VISION_STREAM_ROAD, false);
|
||||
while (!do_exit && !vipc.connect(false)) {
|
||||
@@ -156,51 +150,50 @@ int main(int argc, char *argv[]) {
|
||||
// Subscribe to carState (gear), deviceState (ignition), gpsLocation (subtitles)
|
||||
SubMaster sm({"carState", "deviceState", "gpsLocation"});
|
||||
Params params;
|
||||
Params params_memory("/dev/shm/params");
|
||||
|
||||
// Trip state
|
||||
TripState state = IDLE;
|
||||
TripState state = WAITING;
|
||||
OmxEncoder *encoder = nullptr;
|
||||
std::string trip_dir;
|
||||
int frame_count = 0;
|
||||
int frame_count = 0; // per-segment (for rotation)
|
||||
int trip_frames = 0; // per-trip (published to params)
|
||||
int recv_count = 0;
|
||||
uint64_t segment_start_ts = 0;
|
||||
double idle_timer_start = 0.0;
|
||||
|
||||
// SRT subtitle state
|
||||
FILE *srt_file = nullptr;
|
||||
int srt_index = 0; // subtitle entry counter (1-based)
|
||||
int srt_segment_sec = 0; // seconds elapsed in current segment
|
||||
double last_srt_write = 0; // monotonic time of last SRT write
|
||||
int srt_index = 0;
|
||||
int srt_segment_sec = 0;
|
||||
double last_srt_write = 0;
|
||||
|
||||
// Ignition tracking for off→on detection
|
||||
// Ignition tracking
|
||||
bool prev_started = false;
|
||||
bool started_initialized = false;
|
||||
|
||||
// Param check throttle (don't hit filesystem every frame)
|
||||
// Param publish throttle
|
||||
int param_check_counter = 0;
|
||||
double last_param_write = 0;
|
||||
|
||||
// Helper: start a new trip with recording + optional idle timer
|
||||
// Publish initial state
|
||||
params_memory.put("DashcamState", "waiting");
|
||||
params_memory.put("DashcamFrames", "0");
|
||||
|
||||
LOGW("dashcamd: entering main loop in WAITING state");
|
||||
|
||||
// Helper: start a new trip
|
||||
auto start_new_trip = [&]() {
|
||||
// Close existing encoder if any
|
||||
if (encoder) {
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
encoder->encoder_close();
|
||||
}
|
||||
delete encoder;
|
||||
encoder = nullptr;
|
||||
}
|
||||
|
||||
trip_dir = VIDEOS_BASE + "/" + make_timestamp();
|
||||
mkdir(trip_dir.c_str(), 0755);
|
||||
LOGW("dashcamd: new trip %s", trip_dir.c_str());
|
||||
|
||||
encoder = new OmxEncoder(trip_dir.c_str(), width, height, CAMERA_FPS, BITRATE, false, false);
|
||||
encoder = new OmxEncoder(trip_dir.c_str(), width, height, CAMERA_FPS, BITRATE);
|
||||
|
||||
std::string seg_name = make_timestamp();
|
||||
LOGW("dashcamd: opening segment %s", seg_name.c_str());
|
||||
encoder->encoder_open((seg_name + ".mp4").c_str());
|
||||
|
||||
// Open companion SRT file
|
||||
std::string srt_path = trip_dir + "/" + seg_name + ".srt";
|
||||
srt_file = fopen(srt_path.c_str(), "w");
|
||||
srt_index = 0;
|
||||
@@ -208,38 +201,38 @@ int main(int argc, char *argv[]) {
|
||||
last_srt_write = 0;
|
||||
|
||||
frame_count = 0;
|
||||
trip_frames = 0;
|
||||
segment_start_ts = nanos_since_boot();
|
||||
state = RECORDING;
|
||||
|
||||
params_memory.put("DashcamState", "recording");
|
||||
params_memory.put("DashcamFrames", "0");
|
||||
};
|
||||
|
||||
// Helper: close current trip
|
||||
auto close_trip = [&]() {
|
||||
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||
if (encoder) {
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
encoder->encoder_close();
|
||||
LOGW("dashcamd: segment closed");
|
||||
}
|
||||
encoder->encoder_close();
|
||||
LOGW("dashcamd: segment closed");
|
||||
delete encoder;
|
||||
encoder = nullptr;
|
||||
}
|
||||
state = TRIP_ENDED;
|
||||
state = WAITING;
|
||||
frame_count = 0;
|
||||
trip_frames = 0;
|
||||
idle_timer_start = 0.0;
|
||||
LOGW("dashcamd: trip ended");
|
||||
};
|
||||
LOGW("dashcamd: trip ended, returning to WAITING");
|
||||
|
||||
// Start recording immediately — if the car is in drive, great; if parked/off,
|
||||
// the 10-min idle timer will stop the trip if drive is never detected.
|
||||
start_new_trip();
|
||||
idle_timer_start = nanos_since_boot() / 1e9;
|
||||
state = IDLE_TIMEOUT;
|
||||
LOGW("dashcamd: recording started, waiting for drive (10-min idle timer active)");
|
||||
params_memory.put("DashcamState", "waiting");
|
||||
params_memory.put("DashcamFrames", "0");
|
||||
};
|
||||
|
||||
while (!do_exit) {
|
||||
VisionBuf *buf = vipc.recv();
|
||||
if (buf == nullptr) continue;
|
||||
|
||||
// CLEARPILOT: skip frames to match target FPS (SOURCE_FPS -> CAMERA_FPS)
|
||||
// Skip frames to match target FPS (SOURCE_FPS -> CAMERA_FPS)
|
||||
recv_count++;
|
||||
if (SOURCE_FPS > CAMERA_FPS && (recv_count % (SOURCE_FPS / CAMERA_FPS)) != 0) continue;
|
||||
|
||||
@@ -257,25 +250,21 @@ int main(int argc, char *argv[]) {
|
||||
gear == cereal::CarState::GearShifter::LOW ||
|
||||
gear == cereal::CarState::GearShifter::MANUMATIC);
|
||||
|
||||
// Detect ignition off→on transition (new ignition cycle = new trip)
|
||||
if (started_initialized && !prev_started && started) {
|
||||
LOGW("dashcamd: ignition on — new cycle");
|
||||
// Detect ignition off → close any active trip
|
||||
if (started_initialized && prev_started && !started) {
|
||||
LOGW("dashcamd: ignition off");
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
close_trip();
|
||||
}
|
||||
// Start recording immediately, idle timer until drive is detected
|
||||
start_new_trip();
|
||||
idle_timer_start = now;
|
||||
state = IDLE_TIMEOUT;
|
||||
}
|
||||
prev_started = started;
|
||||
started_initialized = true;
|
||||
|
||||
// Check for graceful shutdown request (every ~1 second = 20 frames)
|
||||
// Check for graceful shutdown request (every ~1 second)
|
||||
if (++param_check_counter >= CAMERA_FPS) {
|
||||
param_check_counter = 0;
|
||||
if (params.getBool("DashcamShutdown")) {
|
||||
LOGW("dashcamd: shutdown requested, closing trip");
|
||||
LOGW("dashcamd: shutdown requested");
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
close_trip();
|
||||
}
|
||||
@@ -285,32 +274,30 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// State machine transitions
|
||||
// State machine
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
case TRIP_ENDED:
|
||||
if (in_drive) {
|
||||
case WAITING: {
|
||||
bool has_gps = sm.valid("gpsLocation") && sm["gpsLocation"].getGpsLocation().getHasFix();
|
||||
if (in_drive && system_time_valid() && has_gps) {
|
||||
start_new_trip();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RECORDING:
|
||||
if (!in_drive) {
|
||||
// Car left drive — start idle timeout
|
||||
idle_timer_start = now;
|
||||
state = IDLE_TIMEOUT;
|
||||
LOGW("dashcamd: car not in drive, starting 10-min idle timer");
|
||||
LOGW("dashcamd: car left drive, starting 10-min idle timer");
|
||||
}
|
||||
break;
|
||||
|
||||
case IDLE_TIMEOUT:
|
||||
if (in_drive) {
|
||||
// Back in drive — cancel timer, resume recording same trip
|
||||
idle_timer_start = 0.0;
|
||||
state = RECORDING;
|
||||
LOGW("dashcamd: back in drive, resuming trip");
|
||||
} else if ((now - idle_timer_start) >= IDLE_TIMEOUT_SECONDS) {
|
||||
// Timer expired — end trip
|
||||
LOGW("dashcamd: idle timeout expired");
|
||||
close_trip();
|
||||
}
|
||||
@@ -341,16 +328,28 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
uint64_t ts = nanos_since_boot() - segment_start_ts;
|
||||
|
||||
// Validate buffer before encoding
|
||||
if (buf->y == nullptr || buf->uv == nullptr || buf->width == 0 || buf->height == 0) {
|
||||
LOGE("dashcamd: invalid frame buf y=%p uv=%p %zux%zu, skipping", buf->y, buf->uv, buf->width, buf->height);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Feed NV12 frame directly to OMX encoder
|
||||
encoder->encode_frame_nv12(buf->y, y_stride, buf->uv, uv_stride, width, height, ts);
|
||||
frame_count++;
|
||||
trip_frames++;
|
||||
|
||||
// Publish state every 5 seconds
|
||||
if (now - last_param_write >= 5.0) {
|
||||
params_memory.put("DashcamFrames", std::to_string(trip_frames));
|
||||
last_param_write = now;
|
||||
}
|
||||
|
||||
// Write GPS subtitle at most once per second
|
||||
if (srt_file && (now - last_srt_write) >= 1.0) {
|
||||
last_srt_write = now;
|
||||
srt_index++;
|
||||
|
||||
// Read GPS data
|
||||
double lat = 0, lon = 0, speed_ms = 0;
|
||||
bool has_gps = sm.valid("gpsLocation") && sm["gpsLocation"].getGpsLocation().getHasFix();
|
||||
if (has_gps) {
|
||||
@@ -383,13 +382,11 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Clean exit
|
||||
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||
if (encoder) {
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
encoder->encoder_close();
|
||||
}
|
||||
delete encoder;
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
close_trip();
|
||||
}
|
||||
params_memory.put("DashcamState", "stopped");
|
||||
params_memory.put("DashcamFrames", "0");
|
||||
LOGW("dashcamd: stopped");
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -24,6 +24,21 @@ class SpeedState:
|
||||
self.prev_warning = ""
|
||||
self.prev_warning_speed_limit = 0
|
||||
|
||||
# Cache last-written param values — each put() is mkstemp+fsync+flock+rename.
|
||||
# Sentinel None so the first call always writes.
|
||||
self._w_has_speed = None
|
||||
self._w_speed_display = None
|
||||
self._w_speed_limit_display = None
|
||||
self._w_speed_unit = None
|
||||
self._w_is_metric = None
|
||||
self._w_cruise_warning = None
|
||||
self._w_cruise_warning_speed = None
|
||||
|
||||
def _put_if_changed(self, key, value, attr):
|
||||
if getattr(self, attr) != value:
|
||||
self.params_memory.put(key, value)
|
||||
setattr(self, attr, value)
|
||||
|
||||
def update(self, speed_ms: float, has_speed: bool, speed_limit_ms: float, is_metric: bool,
|
||||
cruise_speed_ms: float = 0.0, cruise_active: bool = False, cruise_standstill: bool = False):
|
||||
"""
|
||||
@@ -48,12 +63,12 @@ class SpeedState:
|
||||
|
||||
self.prev_speed_limit = speed_limit_int
|
||||
|
||||
# Write display-ready values to params_memory
|
||||
self.params_memory.put("ClearpilotHasSpeed", "1" if has_speed and speed_int > 0 else "0")
|
||||
self.params_memory.put("ClearpilotSpeedDisplay", str(speed_int) if has_speed and speed_int > 0 else "")
|
||||
self.params_memory.put("ClearpilotSpeedLimitDisplay", str(speed_limit_int) if speed_limit_int > 0 else "0")
|
||||
self.params_memory.put("ClearpilotSpeedUnit", unit)
|
||||
self.params_memory.put("ClearpilotIsMetric", "1" if is_metric else "0")
|
||||
# Write display-ready values to params_memory (gated on change)
|
||||
self._put_if_changed("ClearpilotHasSpeed", "1" if has_speed and speed_int > 0 else "0", "_w_has_speed")
|
||||
self._put_if_changed("ClearpilotSpeedDisplay", str(speed_int) if has_speed and speed_int > 0 else "", "_w_speed_display")
|
||||
self._put_if_changed("ClearpilotSpeedLimitDisplay", str(speed_limit_int) if speed_limit_int > 0 else "0", "_w_speed_limit_display")
|
||||
self._put_if_changed("ClearpilotSpeedUnit", unit, "_w_speed_unit")
|
||||
self._put_if_changed("ClearpilotIsMetric", "1" if is_metric else "0", "_w_is_metric")
|
||||
|
||||
# Cruise warning logic
|
||||
warning = ""
|
||||
@@ -61,7 +76,16 @@ class SpeedState:
|
||||
cruise_engaged = cruise_active and not cruise_standstill
|
||||
|
||||
if speed_limit_int >= 20 and cruise_engaged and cruise_int > 0:
|
||||
over_threshold = 10 if speed_limit_int >= 50 else 7
|
||||
# Tiers (warning fires at >= limit + threshold):
|
||||
# limit >= 50: +9 over ok, warn at +10 (e.g. 60 → warn at 70)
|
||||
# limit 26-49: +6 over ok, warn at +7 (e.g. 35 → warn at 42)
|
||||
# limit <= 25: +8 over ok, warn at +9 (e.g. 25 → warn at 34, so 33 is ok)
|
||||
if speed_limit_int >= 50:
|
||||
over_threshold = 10
|
||||
elif speed_limit_int <= 25:
|
||||
over_threshold = 9
|
||||
else:
|
||||
over_threshold = 7
|
||||
if cruise_int >= speed_limit_int + over_threshold:
|
||||
warning = "over"
|
||||
warning_speed = str(cruise_int)
|
||||
@@ -69,8 +93,8 @@ class SpeedState:
|
||||
warning = "under"
|
||||
warning_speed = str(cruise_int)
|
||||
|
||||
self.params_memory.put("ClearpilotCruiseWarning", warning)
|
||||
self.params_memory.put("ClearpilotCruiseWarningSpeed", warning_speed)
|
||||
self._put_if_changed("ClearpilotCruiseWarning", warning, "_w_cruise_warning")
|
||||
self._put_if_changed("ClearpilotCruiseWarningSpeed", warning_speed, "_w_cruise_warning_speed")
|
||||
|
||||
# Ding logic: play when warning sign appears or speed limit changes while visible
|
||||
should_ding = False
|
||||
|
||||
@@ -170,6 +170,9 @@ class Controls:
|
||||
self.current_alert_types = [ET.PERMANENT]
|
||||
self.logged_comm_issue = None
|
||||
self.not_running_prev = None
|
||||
self.lat_requested_ts = 0 # CLEARPILOT: timestamp when lat was first requested (ramp-up delay + comm issue suppression)
|
||||
self.prev_lat_requested = False
|
||||
self.prev_no_lat_lane_change = None # CLEARPILOT: gate no_lat_lane_change write on change
|
||||
self.steer_limited = False
|
||||
self.desired_curvature = 0.0
|
||||
self.experimental_mode = False
|
||||
@@ -455,7 +458,13 @@ class Controls:
|
||||
standby_ts = float(self.params_memory.get("ModelStandbyTs") or "0")
|
||||
except (ValueError, TypeError):
|
||||
standby_ts = 0
|
||||
model_suppress = (time.monotonic() - standby_ts) < 2.0
|
||||
now = time.monotonic()
|
||||
model_suppress = (now - standby_ts) < 2.0
|
||||
|
||||
# CLEARPILOT: suppress commIssue/location/params errors for 2s after lat was requested
|
||||
# Model FPS jumps from 4 to 20 on engage, causing transient freq check failures
|
||||
# lat_requested_ts is set in state_control on the False->True transition of LatRequested
|
||||
lat_engage_suppress = (now - self.lat_requested_ts) < 2.0
|
||||
|
||||
not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning}
|
||||
if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
|
||||
@@ -486,7 +495,7 @@ class Controls:
|
||||
# generic catch-all. ideally, a more specific event should be added above instead
|
||||
has_disable_events = self.events.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE))
|
||||
no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
|
||||
if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress:
|
||||
if (not self.sm.all_checks() or self.card.can_rcv_timeout) and no_system_errors and not model_suppress and not lat_engage_suppress:
|
||||
if not self.sm.all_alive():
|
||||
self.events.add(EventName.commIssue)
|
||||
elif not self.sm.all_freq_ok():
|
||||
@@ -507,13 +516,15 @@ class Controls:
|
||||
self.logged_comm_issue = None
|
||||
|
||||
if not (self.CP.notCar and self.joystick_mode):
|
||||
if not self.sm['liveLocationKalman'].posenetOK and not model_suppress:
|
||||
# CLEARPILOT: also suppress on lat engage — modeld 4fps→20fps transition causes
|
||||
# transient inputsOK/valid drops in locationd/paramsd downstream
|
||||
if not self.sm['liveLocationKalman'].posenetOK and not model_suppress and not lat_engage_suppress:
|
||||
self.events.add(EventName.posenetInvalid)
|
||||
if not self.sm['liveLocationKalman'].deviceStable:
|
||||
self.events.add(EventName.deviceFalling)
|
||||
if not self.sm['liveLocationKalman'].inputsOK and not model_suppress:
|
||||
if not self.sm['liveLocationKalman'].inputsOK and not model_suppress and not lat_engage_suppress:
|
||||
self.events.add(EventName.locationdTemporaryError)
|
||||
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress:
|
||||
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY) and not model_suppress and not lat_engage_suppress:
|
||||
self.events.add(EventName.paramsdTemporaryError)
|
||||
|
||||
# conservative HW alert. if the data or frequency are off, locationd will throw an error
|
||||
@@ -527,16 +538,8 @@ class Controls:
|
||||
if self.cruise_mismatch_counter > int(6. / DT_CTRL):
|
||||
self.events.add(EventName.cruiseMismatch)
|
||||
|
||||
# Check for FCW
|
||||
stock_long_is_braking = self.enabled and not self.CP.openpilotLongitudinalControl and CS.aEgo < -1.25
|
||||
model_fcw = self.sm['modelV2'].meta.hardBrakePredicted and not CS.brakePressed and not stock_long_is_braking
|
||||
planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled
|
||||
if planner_fcw or model_fcw:
|
||||
self.events.add(EventName.fcw)
|
||||
# self.fcw_random_event_triggered = True
|
||||
# elif self.fcw_random_event_triggered and self.random_events:
|
||||
# self.events.add(EventName.yourFrogTriedToKillMe)
|
||||
# self.fcw_random_event_triggered = False
|
||||
# CLEARPILOT: FCW disabled — car's own radar AEB works better and triggers reliably.
|
||||
# The comma FCW was producing false positives and adds nothing over the stock system.
|
||||
|
||||
for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
|
||||
try:
|
||||
@@ -684,8 +687,18 @@ class Controls:
|
||||
|
||||
# Check which actuators can be enabled
|
||||
standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill
|
||||
CC.latActive = (self.active or self.FPCC.alwaysOnLateral) and self.speed_check and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(not standstill or self.joystick_mode)
|
||||
lat_requested = (self.active or self.FPCC.alwaysOnLateral) and self.speed_check and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \
|
||||
(not standstill or self.joystick_mode)
|
||||
|
||||
# CLEARPILOT: tell modeld early so it can ramp to 20fps, then delay latActive by
|
||||
# 250ms (5 frames at 20fps) so downstream services stabilize before steering engages.
|
||||
now_ts = time.monotonic()
|
||||
if lat_requested != self.prev_lat_requested:
|
||||
self.params_memory.put_bool("LatRequested", lat_requested)
|
||||
if lat_requested:
|
||||
self.lat_requested_ts = now_ts
|
||||
self.prev_lat_requested = lat_requested
|
||||
CC.latActive = lat_requested and (now_ts - self.lat_requested_ts) >= 0.25
|
||||
CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl
|
||||
|
||||
actuators = CC.actuators
|
||||
@@ -700,11 +713,13 @@ class Controls:
|
||||
CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left
|
||||
CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right
|
||||
|
||||
if model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change:
|
||||
no_lat_lane_change = model_v2.meta.laneChangeState == LaneChangeState.laneChangeStarting and clearpilot_disable_lat_on_lane_change
|
||||
if no_lat_lane_change:
|
||||
CC.latActive = False
|
||||
self.params_memory.put_bool("no_lat_lane_change", True)
|
||||
else:
|
||||
self.params_memory.put_bool("no_lat_lane_change", False)
|
||||
# CLEARPILOT: only write on change — per-cycle atomic writes were eating ~15% CPU
|
||||
if no_lat_lane_change != self.prev_no_lat_lane_change:
|
||||
self.params_memory.put_bool("no_lat_lane_change", no_lat_lane_change)
|
||||
self.prev_no_lat_lane_change = no_lat_lane_change
|
||||
|
||||
if CS.leftBlinker or CS.rightBlinker:
|
||||
self.last_blinker_frame = self.sm.frame
|
||||
@@ -984,6 +999,11 @@ class Controls:
|
||||
while True:
|
||||
self.step()
|
||||
self.rk.monitor_time()
|
||||
# CLEARPILOT: accumulated debt from startup/blocking calls never recovers.
|
||||
# Reset the rate keeper when catastrophically behind — prevents a one-time
|
||||
# ~8s fingerprinting stall from poisoning the lag metric for the whole session.
|
||||
if self.rk.remaining < -1.0:
|
||||
self.rk._next_frame_time = time.monotonic() + self.rk._interval
|
||||
except SystemExit:
|
||||
e.set()
|
||||
t.join()
|
||||
@@ -1054,13 +1074,11 @@ class Controls:
|
||||
elif self.sm['modelV2'].meta.turnDirection == Desire.turnRight:
|
||||
self.events.add(EventName.turningRight)
|
||||
|
||||
if os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')) and self.crashed_timer < 10:
|
||||
# CLEARPILOT: check crash file once per second, not every cycle (stat syscall at 100Hz hurts)
|
||||
if self.sm.frame % 100 == 0:
|
||||
self._crashed_file_exists = os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt'))
|
||||
if getattr(self, '_crashed_file_exists', False) and self.crashed_timer < 10:
|
||||
self.events.add(EventName.openpilotCrashed)
|
||||
|
||||
# if self.random_events and not self.openpilot_crashed_triggered:
|
||||
# self.events.add(EventName.openpilotCrashedRandomEvents)
|
||||
# self.openpilot_crashed_triggered = True
|
||||
|
||||
self.crashed_timer += DT_CTRL
|
||||
|
||||
# if self.speed_limit_alert or self.speed_limit_confirmation:
|
||||
|
||||
@@ -143,6 +143,9 @@ class LongitudinalPlanner:
|
||||
self.params = Params()
|
||||
self.params_memory = Params("/dev/shm/params")
|
||||
|
||||
# CLEARPILOT: track model FPS for dynamic dt adjustment
|
||||
self.model_fps = 20
|
||||
|
||||
self.radarless_model = self.params.get("Model", encoding='utf-8') in RADARLESS_MODELS
|
||||
|
||||
self.release = get_short_branch() == "clearpilot"
|
||||
@@ -173,6 +176,19 @@ class LongitudinalPlanner:
|
||||
return x, v, a, j
|
||||
|
||||
def update(self, sm):
|
||||
# CLEARPILOT: read actual model FPS and adjust dt accordingly
|
||||
model_fps_raw = self.params_memory.get("ModelFps")
|
||||
if model_fps_raw is not None:
|
||||
try:
|
||||
fps = int(model_fps_raw)
|
||||
if fps > 0 and fps != self.model_fps:
|
||||
self.model_fps = fps
|
||||
self.dt = 1.0 / fps
|
||||
self.v_desired_filter.dt = self.dt
|
||||
self.v_desired_filter.update_alpha(2.0) # rc=2.0, same as __init__
|
||||
except (ValueError, ZeroDivisionError):
|
||||
pass
|
||||
|
||||
self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc'
|
||||
|
||||
v_ego = sm['carState'].vEgo
|
||||
@@ -239,7 +255,10 @@ class LongitudinalPlanner:
|
||||
self.j_desired_trajectory = np.interp(ModelConstants.T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution)
|
||||
|
||||
# TODO counter is only needed because radar is glitchy, remove once radar is gone
|
||||
self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
|
||||
# CLEARPILOT: scale crash_cnt threshold by FPS — at 20fps need 3 frames (0.15s),
|
||||
# at 4fps need 1 frame (0.25s already exceeds 0.15s window)
|
||||
fcw_threshold = max(0, int(0.15 * self.model_fps) - 1) # 20fps->2, 4fps->0
|
||||
self.fcw = self.mpc.crash_cnt > fcw_threshold and not sm['carState'].standstill
|
||||
if self.fcw:
|
||||
cloudlog.info("FCW triggered")
|
||||
|
||||
|
||||
@@ -35,120 +35,19 @@ int ABGRToNV12(const uint8_t* src_abgr,
|
||||
int halfwidth = (width + 1) >> 1;
|
||||
void (*ABGRToUVRow)(const uint8_t* src_abgr0, int src_stride_abgr,
|
||||
uint8_t* dst_u, uint8_t* dst_v, int width) =
|
||||
ABGRToUVRow_C;
|
||||
ABGRToUVRow_NEON;
|
||||
void (*ABGRToYRow)(const uint8_t* src_abgr, uint8_t* dst_y, int width) =
|
||||
ABGRToYRow_C;
|
||||
void (*MergeUVRow_)(const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_uv, int width) = MergeUVRow_C;
|
||||
ABGRToYRow_NEON;
|
||||
void (*MergeUVRow_)(const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_uv, int width) = MergeUVRow_NEON;
|
||||
if (!src_abgr || !dst_y || !dst_uv || width <= 0 || height == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (height < 0) { // Negative height means invert the image.
|
||||
height = -height;
|
||||
src_abgr = src_abgr + (height - 1) * src_stride_abgr;
|
||||
src_stride_abgr = -src_stride_abgr;
|
||||
}
|
||||
#if defined(HAS_ABGRTOYROW_SSSE3) && defined(HAS_ABGRTOUVROW_SSSE3)
|
||||
if (TestCpuFlag(kCpuHasSSSE3)) {
|
||||
ABGRToUVRow = ABGRToUVRow_Any_SSSE3;
|
||||
ABGRToYRow = ABGRToYRow_Any_SSSE3;
|
||||
if (IS_ALIGNED(width, 16)) {
|
||||
ABGRToUVRow = ABGRToUVRow_SSSE3;
|
||||
ABGRToYRow = ABGRToYRow_SSSE3;
|
||||
}
|
||||
if (height < 0) {
|
||||
height = -height;
|
||||
src_abgr = src_abgr + (height - 1) * src_stride_abgr;
|
||||
src_stride_abgr = -src_stride_abgr;
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ABGRTOYROW_AVX2) && defined(HAS_ABGRTOUVROW_AVX2)
|
||||
if (TestCpuFlag(kCpuHasAVX2)) {
|
||||
ABGRToUVRow = ABGRToUVRow_Any_AVX2;
|
||||
ABGRToYRow = ABGRToYRow_Any_AVX2;
|
||||
if (IS_ALIGNED(width, 32)) {
|
||||
ABGRToUVRow = ABGRToUVRow_AVX2;
|
||||
ABGRToYRow = ABGRToYRow_AVX2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ABGRTOYROW_NEON)
|
||||
if (TestCpuFlag(kCpuHasNEON)) {
|
||||
ABGRToYRow = ABGRToYRow_Any_NEON;
|
||||
if (IS_ALIGNED(width, 8)) {
|
||||
ABGRToYRow = ABGRToYRow_NEON;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ABGRTOUVROW_NEON)
|
||||
if (TestCpuFlag(kCpuHasNEON)) {
|
||||
ABGRToUVRow = ABGRToUVRow_Any_NEON;
|
||||
if (IS_ALIGNED(width, 16)) {
|
||||
ABGRToUVRow = ABGRToUVRow_NEON;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ABGRTOYROW_MMI) && defined(HAS_ABGRTOUVROW_MMI)
|
||||
if (TestCpuFlag(kCpuHasMMI)) {
|
||||
ABGRToYRow = ABGRToYRow_Any_MMI;
|
||||
ABGRToUVRow = ABGRToUVRow_Any_MMI;
|
||||
if (IS_ALIGNED(width, 8)) {
|
||||
ABGRToYRow = ABGRToYRow_MMI;
|
||||
}
|
||||
if (IS_ALIGNED(width, 16)) {
|
||||
ABGRToUVRow = ABGRToUVRow_MMI;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ABGRTOYROW_MSA) && defined(HAS_ABGRTOUVROW_MSA)
|
||||
if (TestCpuFlag(kCpuHasMSA)) {
|
||||
ABGRToYRow = ABGRToYRow_Any_MSA;
|
||||
ABGRToUVRow = ABGRToUVRow_Any_MSA;
|
||||
if (IS_ALIGNED(width, 16)) {
|
||||
ABGRToYRow = ABGRToYRow_MSA;
|
||||
}
|
||||
if (IS_ALIGNED(width, 32)) {
|
||||
ABGRToUVRow = ABGRToUVRow_MSA;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_MERGEUVROW_SSE2)
|
||||
if (TestCpuFlag(kCpuHasSSE2)) {
|
||||
MergeUVRow_ = MergeUVRow_Any_SSE2;
|
||||
if (IS_ALIGNED(halfwidth, 16)) {
|
||||
MergeUVRow_ = MergeUVRow_SSE2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_MERGEUVROW_AVX2)
|
||||
if (TestCpuFlag(kCpuHasAVX2)) {
|
||||
MergeUVRow_ = MergeUVRow_Any_AVX2;
|
||||
if (IS_ALIGNED(halfwidth, 32)) {
|
||||
MergeUVRow_ = MergeUVRow_AVX2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_MERGEUVROW_NEON)
|
||||
if (TestCpuFlag(kCpuHasNEON)) {
|
||||
MergeUVRow_ = MergeUVRow_Any_NEON;
|
||||
if (IS_ALIGNED(halfwidth, 16)) {
|
||||
MergeUVRow_ = MergeUVRow_NEON;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_MERGEUVROW_MMI)
|
||||
if (TestCpuFlag(kCpuHasMMI)) {
|
||||
MergeUVRow_ = MergeUVRow_Any_MMI;
|
||||
if (IS_ALIGNED(halfwidth, 8)) {
|
||||
MergeUVRow_ = MergeUVRow_MMI;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_MERGEUVROW_MSA)
|
||||
if (TestCpuFlag(kCpuHasMSA)) {
|
||||
MergeUVRow_ = MergeUVRow_Any_MSA;
|
||||
if (IS_ALIGNED(halfwidth, 16)) {
|
||||
MergeUVRow_ = MergeUVRow_MSA;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{
|
||||
// Allocate a rows of uv.
|
||||
align_buffer_64(row_u, ((halfwidth + 31) & ~31) * 2);
|
||||
uint8_t* row_v = row_u + ((halfwidth + 31) & ~31);
|
||||
|
||||
@@ -182,9 +81,9 @@ extern ExitHandler do_exit;
|
||||
// ***** OMX callback functions *****
|
||||
|
||||
void OmxEncoder::wait_for_state(OMX_STATETYPE state_) {
|
||||
std::unique_lock lk(this->state_lock);
|
||||
while (this->state != state_) {
|
||||
this->state_cv.wait(lk);
|
||||
std::unique_lock lk(state_lock);
|
||||
while (state != state_) {
|
||||
state_cv.wait(lk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,270 +135,203 @@ static const char* omx_color_fomat_name(uint32_t format) __attribute__((unused))
|
||||
static const char* omx_color_fomat_name(uint32_t format) {
|
||||
switch (format) {
|
||||
case OMX_COLOR_FormatUnused: return "OMX_COLOR_FormatUnused";
|
||||
case OMX_COLOR_FormatMonochrome: return "OMX_COLOR_FormatMonochrome";
|
||||
case OMX_COLOR_Format8bitRGB332: return "OMX_COLOR_Format8bitRGB332";
|
||||
case OMX_COLOR_Format12bitRGB444: return "OMX_COLOR_Format12bitRGB444";
|
||||
case OMX_COLOR_Format16bitARGB4444: return "OMX_COLOR_Format16bitARGB4444";
|
||||
case OMX_COLOR_Format16bitARGB1555: return "OMX_COLOR_Format16bitARGB1555";
|
||||
case OMX_COLOR_Format16bitRGB565: return "OMX_COLOR_Format16bitRGB565";
|
||||
case OMX_COLOR_Format16bitBGR565: return "OMX_COLOR_Format16bitBGR565";
|
||||
case OMX_COLOR_Format18bitRGB666: return "OMX_COLOR_Format18bitRGB666";
|
||||
case OMX_COLOR_Format18bitARGB1665: return "OMX_COLOR_Format18bitARGB1665";
|
||||
case OMX_COLOR_Format19bitARGB1666: return "OMX_COLOR_Format19bitARGB1666";
|
||||
case OMX_COLOR_Format24bitRGB888: return "OMX_COLOR_Format24bitRGB888";
|
||||
case OMX_COLOR_Format24bitBGR888: return "OMX_COLOR_Format24bitBGR888";
|
||||
case OMX_COLOR_Format24bitARGB1887: return "OMX_COLOR_Format24bitARGB1887";
|
||||
case OMX_COLOR_Format25bitARGB1888: return "OMX_COLOR_Format25bitARGB1888";
|
||||
case OMX_COLOR_Format32bitBGRA8888: return "OMX_COLOR_Format32bitBGRA8888";
|
||||
case OMX_COLOR_Format32bitARGB8888: return "OMX_COLOR_Format32bitARGB8888";
|
||||
case OMX_COLOR_FormatYUV411Planar: return "OMX_COLOR_FormatYUV411Planar";
|
||||
case OMX_COLOR_FormatYUV411PackedPlanar: return "OMX_COLOR_FormatYUV411PackedPlanar";
|
||||
case OMX_COLOR_FormatYUV420Planar: return "OMX_COLOR_FormatYUV420Planar";
|
||||
case OMX_COLOR_FormatYUV420PackedPlanar: return "OMX_COLOR_FormatYUV420PackedPlanar";
|
||||
case OMX_COLOR_FormatYUV420SemiPlanar: return "OMX_COLOR_FormatYUV420SemiPlanar";
|
||||
case OMX_COLOR_FormatYUV422Planar: return "OMX_COLOR_FormatYUV422Planar";
|
||||
case OMX_COLOR_FormatYUV422PackedPlanar: return "OMX_COLOR_FormatYUV422PackedPlanar";
|
||||
case OMX_COLOR_FormatYUV422SemiPlanar: return "OMX_COLOR_FormatYUV422SemiPlanar";
|
||||
case OMX_COLOR_FormatYCbYCr: return "OMX_COLOR_FormatYCbYCr";
|
||||
case OMX_COLOR_FormatYCrYCb: return "OMX_COLOR_FormatYCrYCb";
|
||||
case OMX_COLOR_FormatCbYCrY: return "OMX_COLOR_FormatCbYCrY";
|
||||
case OMX_COLOR_FormatCrYCbY: return "OMX_COLOR_FormatCrYCbY";
|
||||
case OMX_COLOR_FormatYUV444Interleaved: return "OMX_COLOR_FormatYUV444Interleaved";
|
||||
case OMX_COLOR_FormatRawBayer8bit: return "OMX_COLOR_FormatRawBayer8bit";
|
||||
case OMX_COLOR_FormatRawBayer10bit: return "OMX_COLOR_FormatRawBayer10bit";
|
||||
case OMX_COLOR_FormatRawBayer8bitcompressed: return "OMX_COLOR_FormatRawBayer8bitcompressed";
|
||||
case OMX_COLOR_FormatL2: return "OMX_COLOR_FormatL2";
|
||||
case OMX_COLOR_FormatL4: return "OMX_COLOR_FormatL4";
|
||||
case OMX_COLOR_FormatL8: return "OMX_COLOR_FormatL8";
|
||||
case OMX_COLOR_FormatL16: return "OMX_COLOR_FormatL16";
|
||||
case OMX_COLOR_FormatL24: return "OMX_COLOR_FormatL24";
|
||||
case OMX_COLOR_FormatL32: return "OMX_COLOR_FormatL32";
|
||||
case OMX_COLOR_FormatYUV420PackedSemiPlanar: return "OMX_COLOR_FormatYUV420PackedSemiPlanar";
|
||||
case OMX_COLOR_FormatYUV422PackedSemiPlanar: return "OMX_COLOR_FormatYUV422PackedSemiPlanar";
|
||||
case OMX_COLOR_Format18BitBGR666: return "OMX_COLOR_Format18BitBGR666";
|
||||
case OMX_COLOR_Format24BitARGB6666: return "OMX_COLOR_Format24BitARGB6666";
|
||||
case OMX_COLOR_Format24BitABGR6666: return "OMX_COLOR_Format24BitABGR6666";
|
||||
|
||||
case OMX_COLOR_FormatAndroidOpaque: return "OMX_COLOR_FormatAndroidOpaque";
|
||||
case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar: return "OMX_TI_COLOR_FormatYUV420PackedSemiPlanar";
|
||||
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: return "OMX_QCOM_COLOR_FormatYVU420SemiPlanar";
|
||||
case OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka: return "OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka";
|
||||
case OMX_SEC_COLOR_FormatNV12Tiled: return "OMX_SEC_COLOR_FormatNV12Tiled";
|
||||
case OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m: return "OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m";
|
||||
|
||||
case QOMX_COLOR_FormatYVU420PackedSemiPlanar32m4ka: return "QOMX_COLOR_FormatYVU420PackedSemiPlanar32m4ka";
|
||||
case QOMX_COLOR_FormatYUV420PackedSemiPlanar16m2ka: return "QOMX_COLOR_FormatYUV420PackedSemiPlanar16m2ka";
|
||||
case QOMX_COLOR_FORMATYUV420PackedSemiPlanar32mMultiView: return "QOMX_COLOR_FORMATYUV420PackedSemiPlanar32mMultiView";
|
||||
case QOMX_COLOR_FORMATYUV420PackedSemiPlanar32mCompressed: return "QOMX_COLOR_FORMATYUV420PackedSemiPlanar32mCompressed";
|
||||
case QOMX_COLOR_Format32bitRGBA8888: return "QOMX_COLOR_Format32bitRGBA8888";
|
||||
case QOMX_COLOR_Format32bitRGBA8888Compressed: return "QOMX_COLOR_Format32bitRGBA8888Compressed";
|
||||
|
||||
default:
|
||||
return "unkn";
|
||||
default: return "unkn";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ***** encoder functions *****
|
||||
|
||||
OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bitrate, bool h265, bool downscale) {
|
||||
OmxEncoder::OmxEncoder(const char* path, int width, int height, int fps, int bitrate) {
|
||||
this->path = path;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->fps = fps;
|
||||
this->remuxing = !h265;
|
||||
|
||||
this->downscale = downscale;
|
||||
if (this->downscale) {
|
||||
this->y_ptr2 = (uint8_t *)malloc(this->width*this->height);
|
||||
this->u_ptr2 = (uint8_t *)malloc(this->width*this->height/4);
|
||||
this->v_ptr2 = (uint8_t *)malloc(this->width*this->height/4);
|
||||
OMX_ERRORTYPE err = OMX_Init();
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("OMX_Init failed: %x", err);
|
||||
return;
|
||||
}
|
||||
|
||||
auto component = (OMX_STRING)(h265 ? "OMX.qcom.video.encoder.hevc" : "OMX.qcom.video.encoder.avc");
|
||||
int err = OMX_GetHandle(&this->handle, component, this, &omx_callbacks);
|
||||
OMX_STRING component = (OMX_STRING)("OMX.qcom.video.encoder.avc");
|
||||
err = OMX_GetHandle(&handle, component, this, &omx_callbacks);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("error getting codec: %x", err);
|
||||
LOGE("Error getting codec: %x", err);
|
||||
OMX_Deinit();
|
||||
return;
|
||||
}
|
||||
|
||||
// setup input port
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE in_port = {0};
|
||||
in_port.nSize = sizeof(in_port);
|
||||
in_port.nPortIndex = (OMX_U32) PORT_INDEX_IN;
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
OMX_CHECK(OMX_GetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
|
||||
in_port.format.video.nFrameWidth = this->width;
|
||||
in_port.format.video.nFrameHeight = this->height;
|
||||
in_port.format.video.nStride = VENUS_Y_STRIDE(COLOR_FMT_NV12, this->width);
|
||||
in_port.format.video.nSliceHeight = this->height;
|
||||
in_port.nBufferSize = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, this->width, this->height);
|
||||
in_port.format.video.xFramerate = (this->fps * 65536);
|
||||
in_port.format.video.nFrameWidth = width;
|
||||
in_port.format.video.nFrameHeight = height;
|
||||
in_port.format.video.nStride = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
|
||||
in_port.format.video.nSliceHeight = height;
|
||||
in_port.nBufferSize = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, width, height);
|
||||
in_port.format.video.xFramerate = (fps * 65536);
|
||||
in_port.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused;
|
||||
in_port.format.video.eColorFormat = (OMX_COLOR_FORMATTYPE)QOMX_COLOR_FORMATYUV420PackedSemiPlanar32m;
|
||||
|
||||
OMX_CHECK(OMX_SetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
this->in_buf_headers.resize(in_port.nBufferCountActual);
|
||||
OMX_CHECK(OMX_SetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
OMX_CHECK(OMX_GetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR) &in_port));
|
||||
in_buf_headers.resize(in_port.nBufferCountActual);
|
||||
|
||||
// setup output port
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE out_port = {0};
|
||||
OMX_PARAM_PORTDEFINITIONTYPE out_port;
|
||||
memset(&out_port, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
|
||||
out_port.nSize = sizeof(out_port);
|
||||
out_port.nVersion.s.nVersionMajor = 1;
|
||||
out_port.nVersion.s.nVersionMinor = 0;
|
||||
out_port.nVersion.s.nRevision = 0;
|
||||
out_port.nVersion.s.nStep = 0;
|
||||
out_port.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR)&out_port));
|
||||
out_port.format.video.nFrameWidth = this->width;
|
||||
out_port.format.video.nFrameHeight = this->height;
|
||||
|
||||
OMX_ERRORTYPE error = OMX_GetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR)&out_port);
|
||||
if (error != OMX_ErrorNone) {
|
||||
LOGE("Error getting output port parameters: 0x%08x", error);
|
||||
return;
|
||||
}
|
||||
|
||||
out_port.format.video.nFrameWidth = width;
|
||||
out_port.format.video.nFrameHeight = height;
|
||||
out_port.format.video.xFramerate = 0;
|
||||
out_port.format.video.nBitrate = bitrate;
|
||||
if (h265) {
|
||||
out_port.format.video.eCompressionFormat = OMX_VIDEO_CodingHEVC;
|
||||
} else {
|
||||
out_port.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC;
|
||||
}
|
||||
out_port.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC;
|
||||
out_port.format.video.eColorFormat = OMX_COLOR_FormatUnused;
|
||||
|
||||
OMX_CHECK(OMX_SetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR) &out_port));
|
||||
error = OMX_SetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR) &out_port);
|
||||
if (error != OMX_ErrorNone) {
|
||||
LOGE("Error setting output port parameters: 0x%08x", error);
|
||||
return;
|
||||
}
|
||||
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamPortDefinition, (OMX_PTR) &out_port));
|
||||
this->out_buf_headers.resize(out_port.nBufferCountActual);
|
||||
error = OMX_GetParameter(handle, OMX_IndexParamPortDefinition, (OMX_PTR) &out_port);
|
||||
if (error != OMX_ErrorNone) {
|
||||
LOGE("Error getting updated output port parameters: 0x%08x", error);
|
||||
return;
|
||||
}
|
||||
|
||||
out_buf_headers.resize(out_port.nBufferCountActual);
|
||||
|
||||
OMX_VIDEO_PARAM_BITRATETYPE bitrate_type = {0};
|
||||
bitrate_type.nSize = sizeof(bitrate_type);
|
||||
bitrate_type.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamVideoBitrate, (OMX_PTR) &bitrate_type));
|
||||
OMX_CHECK(OMX_GetParameter(handle, OMX_IndexParamVideoBitrate, (OMX_PTR) &bitrate_type));
|
||||
bitrate_type.eControlRate = OMX_Video_ControlRateVariable;
|
||||
bitrate_type.nTargetBitrate = bitrate;
|
||||
|
||||
OMX_CHECK(OMX_SetParameter(this->handle, OMX_IndexParamVideoBitrate, (OMX_PTR) &bitrate_type));
|
||||
OMX_CHECK(OMX_SetParameter(handle, OMX_IndexParamVideoBitrate, (OMX_PTR) &bitrate_type));
|
||||
|
||||
if (h265) {
|
||||
// setup HEVC
|
||||
#ifndef QCOM2
|
||||
OMX_VIDEO_PARAM_HEVCTYPE hevc_type = {0};
|
||||
OMX_INDEXTYPE index_type = (OMX_INDEXTYPE) OMX_IndexParamVideoHevc;
|
||||
#else
|
||||
OMX_VIDEO_PARAM_PROFILELEVELTYPE hevc_type = {0};
|
||||
OMX_INDEXTYPE index_type = OMX_IndexParamVideoProfileLevelCurrent;
|
||||
#endif
|
||||
hevc_type.nSize = sizeof(hevc_type);
|
||||
hevc_type.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, index_type, (OMX_PTR) &hevc_type));
|
||||
// setup h264
|
||||
OMX_VIDEO_PARAM_AVCTYPE avc = {0};
|
||||
avc.nSize = sizeof(avc);
|
||||
avc.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
|
||||
OMX_CHECK(OMX_GetParameter(handle, OMX_IndexParamVideoAvc, &avc));
|
||||
|
||||
hevc_type.eProfile = OMX_VIDEO_HEVCProfileMain;
|
||||
hevc_type.eLevel = OMX_VIDEO_HEVCHighTierLevel5;
|
||||
avc.nBFrames = 0;
|
||||
avc.nPFrames = 15;
|
||||
|
||||
OMX_CHECK(OMX_SetParameter(this->handle, index_type, (OMX_PTR) &hevc_type));
|
||||
} else {
|
||||
// setup h264
|
||||
OMX_VIDEO_PARAM_AVCTYPE avc = { 0 };
|
||||
avc.nSize = sizeof(avc);
|
||||
avc.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
|
||||
OMX_CHECK(OMX_GetParameter(this->handle, OMX_IndexParamVideoAvc, &avc));
|
||||
avc.eProfile = OMX_VIDEO_AVCProfileHigh;
|
||||
avc.eLevel = OMX_VIDEO_AVCLevel31;
|
||||
|
||||
avc.nBFrames = 0;
|
||||
avc.nPFrames = 15;
|
||||
avc.nAllowedPictureTypes |= OMX_VIDEO_PictureTypeB;
|
||||
avc.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
|
||||
|
||||
avc.eProfile = OMX_VIDEO_AVCProfileHigh;
|
||||
avc.eLevel = OMX_VIDEO_AVCLevel31;
|
||||
avc.nRefFrames = 1;
|
||||
avc.bUseHadamard = OMX_TRUE;
|
||||
avc.bEntropyCodingCABAC = OMX_TRUE;
|
||||
avc.bWeightedPPrediction = OMX_TRUE;
|
||||
avc.bconstIpred = OMX_TRUE;
|
||||
|
||||
avc.nAllowedPictureTypes |= OMX_VIDEO_PictureTypeB;
|
||||
avc.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
|
||||
OMX_CHECK(OMX_SetParameter(handle, OMX_IndexParamVideoAvc, &avc));
|
||||
|
||||
avc.nRefFrames = 1;
|
||||
avc.bUseHadamard = OMX_TRUE;
|
||||
avc.bEntropyCodingCABAC = OMX_TRUE;
|
||||
avc.bWeightedPPrediction = OMX_TRUE;
|
||||
avc.bconstIpred = OMX_TRUE;
|
||||
OMX_CHECK(OMX_SendCommand(handle, OMX_CommandStateSet, OMX_StateIdle, NULL));
|
||||
|
||||
OMX_CHECK(OMX_SetParameter(this->handle, OMX_IndexParamVideoAvc, &avc));
|
||||
for (OMX_BUFFERHEADERTYPE* &buf : in_buf_headers) {
|
||||
OMX_CHECK(OMX_AllocateBuffer(handle, &buf, PORT_INDEX_IN, this, in_port.nBufferSize));
|
||||
}
|
||||
|
||||
OMX_CHECK(OMX_SendCommand(this->handle, OMX_CommandStateSet, OMX_StateIdle, NULL));
|
||||
|
||||
for (auto &buf : this->in_buf_headers) {
|
||||
OMX_CHECK(OMX_AllocateBuffer(this->handle, &buf, PORT_INDEX_IN, this,
|
||||
in_port.nBufferSize));
|
||||
}
|
||||
|
||||
for (auto &buf : this->out_buf_headers) {
|
||||
OMX_CHECK(OMX_AllocateBuffer(this->handle, &buf, PORT_INDEX_OUT, this,
|
||||
out_port.nBufferSize));
|
||||
for (OMX_BUFFERHEADERTYPE* &buf : out_buf_headers) {
|
||||
OMX_CHECK(OMX_AllocateBuffer(handle, &buf, PORT_INDEX_OUT, this, out_port.nBufferSize));
|
||||
}
|
||||
|
||||
wait_for_state(OMX_StateIdle);
|
||||
|
||||
OMX_CHECK(OMX_SendCommand(this->handle, OMX_CommandStateSet, OMX_StateExecuting, NULL));
|
||||
OMX_CHECK(OMX_SendCommand(handle, OMX_CommandStateSet, OMX_StateExecuting, NULL));
|
||||
|
||||
wait_for_state(OMX_StateExecuting);
|
||||
|
||||
// give omx all the output buffers
|
||||
for (auto &buf : this->out_buf_headers) {
|
||||
OMX_CHECK(OMX_FillThisBuffer(this->handle, buf));
|
||||
for (OMX_BUFFERHEADERTYPE* &buf : out_buf_headers) {
|
||||
OMX_CHECK(OMX_FillThisBuffer(handle, buf));
|
||||
}
|
||||
|
||||
// fill the input free queue
|
||||
for (auto &buf : this->in_buf_headers) {
|
||||
this->free_in.push(buf);
|
||||
for (OMX_BUFFERHEADERTYPE* &buf : in_buf_headers) {
|
||||
free_in.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void OmxEncoder::handle_out_buf(OmxEncoder *e, OMX_BUFFERHEADERTYPE *out_buf) {
|
||||
void OmxEncoder::handle_out_buf(OmxEncoder *encoder, OMX_BUFFERHEADERTYPE *out_buf) {
|
||||
int err;
|
||||
uint8_t *buf_data = out_buf->pBuffer + out_buf->nOffset;
|
||||
|
||||
if (out_buf->nFlags & OMX_BUFFERFLAG_CODECCONFIG) {
|
||||
if (e->codec_config_len < out_buf->nFilledLen) {
|
||||
e->codec_config = (uint8_t *)realloc(e->codec_config, out_buf->nFilledLen);
|
||||
if (encoder->codec_config_len < out_buf->nFilledLen) {
|
||||
encoder->codec_config = (uint8_t *)realloc(encoder->codec_config, out_buf->nFilledLen);
|
||||
}
|
||||
e->codec_config_len = out_buf->nFilledLen;
|
||||
memcpy(e->codec_config, buf_data, out_buf->nFilledLen);
|
||||
encoder->codec_config_len = out_buf->nFilledLen;
|
||||
memcpy(encoder->codec_config, buf_data, out_buf->nFilledLen);
|
||||
#ifdef QCOM2
|
||||
out_buf->nTimeStamp = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (e->of) {
|
||||
fwrite(buf_data, out_buf->nFilledLen, 1, e->of);
|
||||
if (encoder->of) {
|
||||
fwrite(buf_data, out_buf->nFilledLen, 1, encoder->of);
|
||||
}
|
||||
|
||||
if (e->remuxing) {
|
||||
if (!e->wrote_codec_config && e->codec_config_len > 0) {
|
||||
// extradata will be freed by av_free() in avcodec_free_context()
|
||||
e->codec_ctx->extradata = (uint8_t*)av_mallocz(e->codec_config_len + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
e->codec_ctx->extradata_size = e->codec_config_len;
|
||||
memcpy(e->codec_ctx->extradata, e->codec_config, e->codec_config_len);
|
||||
if (!encoder->wrote_codec_config && encoder->codec_config_len > 0) {
|
||||
// extradata will be freed by av_free() in avcodec_free_context()
|
||||
encoder->out_stream->codecpar->extradata = (uint8_t*)av_mallocz(encoder->codec_config_len + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
encoder->out_stream->codecpar->extradata_size = encoder->codec_config_len;
|
||||
memcpy(encoder->out_stream->codecpar->extradata, encoder->codec_config, encoder->codec_config_len);
|
||||
|
||||
err = avcodec_parameters_from_context(e->out_stream->codecpar, e->codec_ctx);
|
||||
assert(err >= 0);
|
||||
err = avformat_write_header(e->ofmt_ctx, NULL);
|
||||
assert(err >= 0);
|
||||
err = avformat_write_header(encoder->ofmt_ctx, NULL);
|
||||
assert(err >= 0);
|
||||
|
||||
e->wrote_codec_config = true;
|
||||
encoder->wrote_codec_config = true;
|
||||
}
|
||||
|
||||
if (out_buf->nTimeStamp > 0) {
|
||||
// input timestamps are in microseconds
|
||||
AVRational in_timebase = {1, 1000000};
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = buf_data;
|
||||
pkt.size = out_buf->nFilledLen;
|
||||
|
||||
enum AVRounding rnd = static_cast<enum AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||
pkt.pts = pkt.dts = av_rescale_q_rnd(out_buf->nTimeStamp, in_timebase, encoder->out_stream->time_base, rnd);
|
||||
pkt.duration = av_rescale_q(1, AVRational{1, encoder->fps}, encoder->out_stream->time_base);
|
||||
|
||||
if (out_buf->nFlags & OMX_BUFFERFLAG_SYNCFRAME) {
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
}
|
||||
|
||||
if (out_buf->nTimeStamp > 0) {
|
||||
// input timestamps are in microseconds
|
||||
AVRational in_timebase = {1, 1000000};
|
||||
err = av_write_frame(encoder->ofmt_ctx, &pkt);
|
||||
if (err < 0) { LOGW("ts encoder write issue"); }
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = buf_data;
|
||||
pkt.size = out_buf->nFilledLen;
|
||||
|
||||
enum AVRounding rnd = static_cast<enum AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||
pkt.pts = pkt.dts = av_rescale_q_rnd(out_buf->nTimeStamp, in_timebase, e->ofmt_ctx->streams[0]->time_base, rnd);
|
||||
pkt.duration = av_rescale_q(50*1000, in_timebase, e->ofmt_ctx->streams[0]->time_base);
|
||||
|
||||
if (out_buf->nFlags & OMX_BUFFERFLAG_SYNCFRAME) {
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
}
|
||||
|
||||
err = av_write_frame(e->ofmt_ctx, &pkt);
|
||||
if (err < 0) { LOGW("ts encoder write issue"); }
|
||||
|
||||
av_free_packet(&pkt);
|
||||
}
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
|
||||
// give omx back the buffer
|
||||
@@ -508,59 +340,53 @@ void OmxEncoder::handle_out_buf(OmxEncoder *e, OMX_BUFFERHEADERTYPE *out_buf) {
|
||||
out_buf->nTimeStamp = 0;
|
||||
}
|
||||
#endif
|
||||
OMX_CHECK(OMX_FillThisBuffer(e->handle, out_buf));
|
||||
OMX_CHECK(OMX_FillThisBuffer(encoder->handle, out_buf));
|
||||
}
|
||||
|
||||
int OmxEncoder::encode_frame_rgba(const uint8_t *ptr, int in_width, int in_height, uint64_t ts) {
|
||||
int err;
|
||||
if (!this->is_open) {
|
||||
if (!is_open) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// this sometimes freezes... put it outside the encoder lock so we can still trigger rotates...
|
||||
// THIS IS A REALLY BAD IDEA, but apparently the race has to happen 30 times to trigger this
|
||||
OMX_BUFFERHEADERTYPE* in_buf = nullptr;
|
||||
while (!this->free_in.try_pop(in_buf, 20)) {
|
||||
while (!free_in.try_pop(in_buf, 20)) {
|
||||
if (do_exit) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = this->counter;
|
||||
int ret = counter;
|
||||
|
||||
uint8_t *in_buf_ptr = in_buf->pBuffer;
|
||||
|
||||
uint8_t *in_y_ptr = in_buf_ptr;
|
||||
int in_y_stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, this->width);
|
||||
int in_uv_stride = VENUS_UV_STRIDE(COLOR_FMT_NV12, this->width);
|
||||
uint8_t *in_uv_ptr = in_buf_ptr + (in_y_stride * VENUS_Y_SCANLINES(COLOR_FMT_NV12, this->height));
|
||||
int in_y_stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
|
||||
int in_uv_stride = VENUS_UV_STRIDE(COLOR_FMT_NV12, width);
|
||||
uint8_t *in_uv_ptr = in_buf_ptr + (in_y_stride * VENUS_Y_SCANLINES(COLOR_FMT_NV12, height));
|
||||
|
||||
err = ABGRToNV12(ptr, this->width*4,
|
||||
in_y_ptr, in_y_stride,
|
||||
in_uv_ptr, in_uv_stride,
|
||||
this->width, this->height);
|
||||
int err = ABGRToNV12(ptr, width * 4, in_y_ptr, in_y_stride, in_uv_ptr, in_uv_stride, width, height);
|
||||
assert(err == 0);
|
||||
|
||||
in_buf->nFilledLen = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, this->width, this->height);
|
||||
in_buf->nFilledLen = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, width, height);
|
||||
in_buf->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
|
||||
in_buf->nOffset = 0;
|
||||
in_buf->nTimeStamp = ts/1000LL; // OMX_TICKS, in microseconds
|
||||
this->last_t = in_buf->nTimeStamp;
|
||||
in_buf->nTimeStamp = ts / 1000LL; // OMX_TICKS, in microseconds
|
||||
last_t = in_buf->nTimeStamp;
|
||||
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(this->handle, in_buf));
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(handle, in_buf));
|
||||
|
||||
// pump output
|
||||
while (true) {
|
||||
OMX_BUFFERHEADERTYPE *out_buf;
|
||||
if (!this->done_out.try_pop(out_buf)) {
|
||||
if (!done_out.try_pop(out_buf)) {
|
||||
break;
|
||||
}
|
||||
handle_out_buf(this, out_buf);
|
||||
}
|
||||
|
||||
this->dirty = true;
|
||||
dirty = true;
|
||||
|
||||
this->counter++;
|
||||
counter++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -568,24 +394,24 @@ int OmxEncoder::encode_frame_rgba(const uint8_t *ptr, int in_width, int in_heigh
|
||||
// CLEARPILOT: encode raw NV12 frames directly (no RGBA conversion needed)
|
||||
int OmxEncoder::encode_frame_nv12(const uint8_t *y_ptr, int y_stride, const uint8_t *uv_ptr, int uv_stride,
|
||||
int in_width, int in_height, uint64_t ts) {
|
||||
if (!this->is_open) {
|
||||
if (!is_open) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
OMX_BUFFERHEADERTYPE* in_buf = nullptr;
|
||||
while (!this->free_in.try_pop(in_buf, 20)) {
|
||||
while (!free_in.try_pop(in_buf, 20)) {
|
||||
if (do_exit) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = this->counter;
|
||||
int ret = counter;
|
||||
|
||||
uint8_t *in_buf_ptr = in_buf->pBuffer;
|
||||
int venus_y_stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, this->width);
|
||||
int venus_uv_stride = VENUS_UV_STRIDE(COLOR_FMT_NV12, this->width);
|
||||
int venus_y_stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
|
||||
int venus_uv_stride = VENUS_UV_STRIDE(COLOR_FMT_NV12, width);
|
||||
uint8_t *dst_y = in_buf_ptr;
|
||||
uint8_t *dst_uv = in_buf_ptr + (venus_y_stride * VENUS_Y_SCANLINES(COLOR_FMT_NV12, this->height));
|
||||
uint8_t *dst_uv = in_buf_ptr + (venus_y_stride * VENUS_Y_SCANLINES(COLOR_FMT_NV12, height));
|
||||
|
||||
// Copy Y plane row by row (source stride may differ from VENUS stride)
|
||||
for (int row = 0; row < in_height; row++) {
|
||||
@@ -597,99 +423,103 @@ int OmxEncoder::encode_frame_nv12(const uint8_t *y_ptr, int y_stride, const uint
|
||||
memcpy(dst_uv + row * venus_uv_stride, uv_ptr + row * uv_stride, in_width);
|
||||
}
|
||||
|
||||
in_buf->nFilledLen = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, this->width, this->height);
|
||||
in_buf->nFilledLen = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, width, height);
|
||||
in_buf->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
|
||||
in_buf->nOffset = 0;
|
||||
in_buf->nTimeStamp = ts / 1000LL;
|
||||
this->last_t = in_buf->nTimeStamp;
|
||||
last_t = in_buf->nTimeStamp;
|
||||
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(this->handle, in_buf));
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(handle, in_buf));
|
||||
|
||||
while (true) {
|
||||
OMX_BUFFERHEADERTYPE *out_buf;
|
||||
if (!this->done_out.try_pop(out_buf)) {
|
||||
if (!done_out.try_pop(out_buf)) {
|
||||
break;
|
||||
}
|
||||
handle_out_buf(this, out_buf);
|
||||
}
|
||||
|
||||
this->dirty = true;
|
||||
this->counter++;
|
||||
dirty = true;
|
||||
counter++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OmxEncoder::encoder_open(const char* filename) {
|
||||
int err;
|
||||
if (!filename || strlen(filename) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(filename) + path.size() + 2 > sizeof(vid_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat st = {0};
|
||||
if (stat(this->path.c_str(), &st) == -1) {
|
||||
mkdir(this->path.c_str(), 0755);
|
||||
}
|
||||
|
||||
snprintf(this->vid_path, sizeof(this->vid_path), "%s/%s", this->path.c_str(), filename);
|
||||
printf("encoder_open %s remuxing:%d\n", this->vid_path, this->remuxing);
|
||||
|
||||
if (this->remuxing) {
|
||||
avformat_alloc_output_context2(&this->ofmt_ctx, NULL, NULL, this->vid_path);
|
||||
assert(this->ofmt_ctx);
|
||||
|
||||
this->out_stream = avformat_new_stream(this->ofmt_ctx, NULL);
|
||||
assert(this->out_stream);
|
||||
|
||||
// set codec correctly
|
||||
av_register_all();
|
||||
|
||||
AVCodec *codec = NULL;
|
||||
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
assert(codec);
|
||||
|
||||
this->codec_ctx = avcodec_alloc_context3(codec);
|
||||
assert(this->codec_ctx);
|
||||
this->codec_ctx->width = this->width;
|
||||
this->codec_ctx->height = this->height;
|
||||
this->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
this->codec_ctx->time_base = (AVRational){ 1, this->fps };
|
||||
|
||||
err = avio_open(&this->ofmt_ctx->pb, this->vid_path, AVIO_FLAG_WRITE);
|
||||
assert(err >= 0);
|
||||
|
||||
this->wrote_codec_config = false;
|
||||
} else {
|
||||
this->of = fopen(this->vid_path, "wb");
|
||||
assert(this->of);
|
||||
#ifndef QCOM2
|
||||
if (this->codec_config_len > 0) {
|
||||
fwrite(this->codec_config, this->codec_config_len, 1, this->of);
|
||||
if (stat(path.c_str(), &st) == -1) {
|
||||
if (mkdir(path.c_str(), 0755) == -1) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// create camera lock file
|
||||
snprintf(this->lock_path, sizeof(this->lock_path), "%s/%s.lock", this->path.c_str(), filename);
|
||||
int lock_fd = HANDLE_EINTR(open(this->lock_path, O_RDWR | O_CREAT, 0664));
|
||||
assert(lock_fd >= 0);
|
||||
snprintf(vid_path, sizeof(vid_path), "%s/%s", path.c_str(), filename);
|
||||
|
||||
if (avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, vid_path) < 0 || !ofmt_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
out_stream = avformat_new_stream(ofmt_ctx, NULL);
|
||||
if (!out_stream) {
|
||||
avformat_free_context(ofmt_ctx);
|
||||
ofmt_ctx = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
out_stream->time_base = AVRational{1, fps};
|
||||
|
||||
out_stream->codecpar->codec_id = AV_CODEC_ID_H264;
|
||||
out_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
out_stream->codecpar->width = width;
|
||||
out_stream->codecpar->height = height;
|
||||
|
||||
int err = avio_open(&ofmt_ctx->pb, vid_path, AVIO_FLAG_WRITE);
|
||||
if (err < 0) {
|
||||
avformat_free_context(ofmt_ctx);
|
||||
ofmt_ctx = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
wrote_codec_config = false;
|
||||
|
||||
snprintf(lock_path, sizeof(lock_path), "%s/%s.lock", path.c_str(), filename);
|
||||
int lock_fd = HANDLE_EINTR(open(lock_path, O_RDWR | O_CREAT, 0664));
|
||||
if (lock_fd < 0) {
|
||||
avio_closep(&ofmt_ctx->pb);
|
||||
avformat_free_context(ofmt_ctx);
|
||||
ofmt_ctx = nullptr;
|
||||
return;
|
||||
}
|
||||
close(lock_fd);
|
||||
|
||||
this->is_open = true;
|
||||
this->counter = 0;
|
||||
is_open = true;
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
void OmxEncoder::encoder_close() {
|
||||
if (this->is_open) {
|
||||
if (this->dirty) {
|
||||
// drain output only if there could be frames in the encoder
|
||||
if (!is_open) return;
|
||||
|
||||
OMX_BUFFERHEADERTYPE* in_buf = this->free_in.pop();
|
||||
if (dirty) {
|
||||
OMX_BUFFERHEADERTYPE* in_buf = free_in.pop();
|
||||
if (in_buf) {
|
||||
in_buf->nFilledLen = 0;
|
||||
in_buf->nOffset = 0;
|
||||
in_buf->nFlags = OMX_BUFFERFLAG_EOS;
|
||||
in_buf->nTimeStamp = this->last_t + 1000000LL/this->fps;
|
||||
in_buf->nTimeStamp = last_t + 1000000LL / fps;
|
||||
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(this->handle, in_buf));
|
||||
OMX_CHECK(OMX_EmptyThisBuffer(handle, in_buf));
|
||||
|
||||
while (true) {
|
||||
OMX_BUFFERHEADERTYPE *out_buf = this->done_out.pop();
|
||||
OMX_BUFFERHEADERTYPE *out_buf = done_out.pop();
|
||||
if (!out_buf) break;
|
||||
|
||||
handle_out_buf(this, out_buf);
|
||||
|
||||
@@ -697,55 +527,112 @@ void OmxEncoder::encoder_close() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->dirty = false;
|
||||
}
|
||||
|
||||
if (this->remuxing) {
|
||||
av_write_trailer(this->ofmt_ctx);
|
||||
avcodec_free_context(&this->codec_ctx);
|
||||
avio_closep(&this->ofmt_ctx->pb);
|
||||
avformat_free_context(this->ofmt_ctx);
|
||||
} else {
|
||||
fclose(this->of);
|
||||
this->of = nullptr;
|
||||
}
|
||||
unlink(this->lock_path);
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
if (out_stream) {
|
||||
out_stream->nb_frames = counter;
|
||||
out_stream->duration = av_rescale_q(counter, AVRational{1, fps}, out_stream->time_base);
|
||||
}
|
||||
|
||||
if (ofmt_ctx) {
|
||||
av_write_trailer(ofmt_ctx);
|
||||
ofmt_ctx->duration = out_stream ? out_stream->duration : 0;
|
||||
avio_closep(&ofmt_ctx->pb);
|
||||
avformat_free_context(ofmt_ctx);
|
||||
ofmt_ctx = nullptr;
|
||||
out_stream = nullptr;
|
||||
}
|
||||
|
||||
if (lock_path[0] != '\0') {
|
||||
unlink(lock_path);
|
||||
}
|
||||
|
||||
is_open = false;
|
||||
|
||||
// Remux with faststart for streaming/seeking support
|
||||
if (strlen(vid_path) > 0) {
|
||||
char fixed_path[1024];
|
||||
snprintf(fixed_path, sizeof(fixed_path), "%s.fixed.mp4", vid_path);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof(cmd), "ffmpeg -y -i \"%s\" -c copy -movflags +faststart \"%s\" && mv \"%s\" \"%s\"",
|
||||
vid_path, fixed_path, fixed_path, vid_path);
|
||||
|
||||
int ret = system(cmd);
|
||||
if (ret != 0) {
|
||||
LOGW("ffmpeg faststart remux failed with exit code %d", ret);
|
||||
}
|
||||
}
|
||||
this->is_open = false;
|
||||
}
|
||||
|
||||
OmxEncoder::~OmxEncoder() {
|
||||
assert(!this->is_open);
|
||||
|
||||
OMX_CHECK(OMX_SendCommand(this->handle, OMX_CommandStateSet, OMX_StateIdle, NULL));
|
||||
|
||||
wait_for_state(OMX_StateIdle);
|
||||
|
||||
OMX_CHECK(OMX_SendCommand(this->handle, OMX_CommandStateSet, OMX_StateLoaded, NULL));
|
||||
|
||||
for (auto &buf : this->in_buf_headers) {
|
||||
OMX_CHECK(OMX_FreeBuffer(this->handle, PORT_INDEX_IN, buf));
|
||||
if (is_open) {
|
||||
LOGE("OmxEncoder closed with is_open=true, calling encoder_close()");
|
||||
encoder_close();
|
||||
}
|
||||
|
||||
for (auto &buf : this->out_buf_headers) {
|
||||
OMX_CHECK(OMX_FreeBuffer(this->handle, PORT_INDEX_OUT, buf));
|
||||
if (!handle) {
|
||||
LOGE("OMX handle is null in destructor, skipping teardown.");
|
||||
return;
|
||||
}
|
||||
|
||||
OMX_ERRORTYPE err;
|
||||
|
||||
err = OMX_SendCommand(handle, OMX_CommandStateSet, OMX_StateIdle, NULL);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("Failed to set OMX state to Idle: %x", err);
|
||||
} else {
|
||||
wait_for_state(OMX_StateIdle);
|
||||
}
|
||||
|
||||
err = OMX_SendCommand(handle, OMX_CommandStateSet, OMX_StateLoaded, NULL);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("Failed to set OMX state to Loaded: %x", err);
|
||||
}
|
||||
|
||||
for (OMX_BUFFERHEADERTYPE *buf : in_buf_headers) {
|
||||
if (buf) {
|
||||
err = OMX_FreeBuffer(handle, PORT_INDEX_IN, buf);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("Failed to free input buffer: %x", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (OMX_BUFFERHEADERTYPE *buf : out_buf_headers) {
|
||||
if (buf) {
|
||||
err = OMX_FreeBuffer(handle, PORT_INDEX_OUT, buf);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("Failed to free output buffer: %x", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_state(OMX_StateLoaded);
|
||||
|
||||
OMX_CHECK(OMX_FreeHandle(this->handle));
|
||||
err = OMX_FreeHandle(handle);
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("Failed to free OMX handle: %x", err);
|
||||
}
|
||||
|
||||
handle = nullptr;
|
||||
|
||||
err = OMX_Deinit();
|
||||
if (err != OMX_ErrorNone) {
|
||||
LOGE("OMX_Deinit failed: %x", err);
|
||||
}
|
||||
|
||||
OMX_BUFFERHEADERTYPE *out_buf;
|
||||
while (this->free_in.try_pop(out_buf));
|
||||
while (this->done_out.try_pop(out_buf));
|
||||
while (free_in.try_pop(out_buf));
|
||||
while (done_out.try_pop(out_buf));
|
||||
|
||||
if (this->codec_config) {
|
||||
free(this->codec_config);
|
||||
if (codec_config) {
|
||||
free(codec_config);
|
||||
codec_config = nullptr;
|
||||
}
|
||||
|
||||
if (this->downscale) {
|
||||
free(this->y_ptr2);
|
||||
free(this->u_ptr2);
|
||||
free(this->v_ptr2);
|
||||
}
|
||||
in_buf_headers.clear();
|
||||
out_buf_headers.clear();
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ extern "C" {
|
||||
|
||||
#include "common/queue.h"
|
||||
|
||||
// OmxEncoder, lossey codec using hardware hevc
|
||||
// OmxEncoder, lossey codec using hardware H.264
|
||||
class OmxEncoder {
|
||||
public:
|
||||
OmxEncoder(const char* path, int width, int height, int fps, int bitrate, bool h265, bool downscale);
|
||||
OmxEncoder(const char* path, int width, int height, int fps, int bitrate);
|
||||
~OmxEncoder();
|
||||
|
||||
int encode_frame_rgba(const uint8_t *ptr, int in_width, int in_height, uint64_t ts);
|
||||
@@ -44,31 +44,26 @@ private:
|
||||
int counter = 0;
|
||||
|
||||
std::string path;
|
||||
FILE *of;
|
||||
FILE *of = nullptr;
|
||||
|
||||
size_t codec_config_len;
|
||||
uint8_t *codec_config = NULL;
|
||||
bool wrote_codec_config;
|
||||
size_t codec_config_len = 0;
|
||||
uint8_t *codec_config = nullptr;
|
||||
bool wrote_codec_config = false;
|
||||
|
||||
std::mutex state_lock;
|
||||
std::condition_variable state_cv;
|
||||
OMX_STATETYPE state = OMX_StateLoaded;
|
||||
|
||||
OMX_HANDLETYPE handle;
|
||||
OMX_HANDLETYPE handle = nullptr;
|
||||
|
||||
std::vector<OMX_BUFFERHEADERTYPE *> in_buf_headers;
|
||||
std::vector<OMX_BUFFERHEADERTYPE *> out_buf_headers;
|
||||
|
||||
uint64_t last_t;
|
||||
uint64_t last_t = 0;
|
||||
|
||||
SafeQueue<OMX_BUFFERHEADERTYPE *> free_in;
|
||||
SafeQueue<OMX_BUFFERHEADERTYPE *> done_out;
|
||||
|
||||
AVFormatContext *ofmt_ctx;
|
||||
AVCodecContext *codec_ctx;
|
||||
AVStream *out_stream;
|
||||
bool remuxing;
|
||||
|
||||
bool downscale;
|
||||
uint8_t *y_ptr2, *u_ptr2, *v_ptr2;
|
||||
AVFormatContext *ofmt_ctx = nullptr;
|
||||
AVStream *out_stream = nullptr;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ ScreenRecorder::ScreenRecorder(QWidget *parent) : QPushButton(parent), image_que
|
||||
|
||||
void ScreenRecorder::initializeEncoder() {
|
||||
const std::string path = "/data/media/0/videos";
|
||||
encoder = std::make_unique<OmxEncoder>(path.c_str(), recording_width, recording_height, 60, 2 * 1024 * 1024, false, false);
|
||||
encoder = std::make_unique<OmxEncoder>(path.c_str(), recording_width, recording_height, 60, 2 * 1024 * 1024);
|
||||
}
|
||||
|
||||
ScreenRecorder::~ScreenRecorder() {
|
||||
|
||||
@@ -87,7 +87,12 @@ def manager_init(frogpilot_functions) -> None:
|
||||
params_memory = Params("/dev/shm/params")
|
||||
params_memory.put("TelemetryEnabled", "0")
|
||||
params_memory.put("VpnEnabled", "1")
|
||||
params_memory.put("DashcamFrames", "0")
|
||||
params_memory.put("DashcamState", "stopped")
|
||||
params_memory.put("LatRequested", "0")
|
||||
params_memory.put("ModelFps", "20")
|
||||
params_memory.put("ModelStandby", "0")
|
||||
params_memory.put("ShutdownTouchReset", "0")
|
||||
params_memory.put("ModelStandbyTs", "0")
|
||||
params_memory.put("CarIsMetric", "0")
|
||||
params_memory.put("ClearpilotSpeedDisplay", "")
|
||||
|
||||
@@ -242,10 +242,12 @@ def main(demo=False):
|
||||
sm.update(0)
|
||||
|
||||
# CLEARPILOT: power saving — three modes based on driving state
|
||||
# Full 20fps: lat active or lane changing
|
||||
# Reduced 4fps: not lat active, not standstill (driving without cruise)
|
||||
# Full 20fps: lat requested or lane changing
|
||||
# Reduced 4fps: not lat requested, not standstill (driving without cruise)
|
||||
# Standby 0fps: standstill
|
||||
lat_active = sm['carControl'].latActive
|
||||
# Uses LatRequested (not carControl.latActive) so modeld ramps to 20fps BEFORE
|
||||
# controlsd actually engages steering — gives downstream services time to stabilize.
|
||||
lat_active = params_memory.get_bool("LatRequested")
|
||||
lane_changing = params_memory.get_bool("no_lat_lane_change")
|
||||
standstill = sm['carState'].standstill
|
||||
calibrating = sm['liveCalibration'].calStatus != log.LiveCalibrationData.Status.calibrated
|
||||
@@ -271,18 +273,27 @@ def main(demo=False):
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
continue
|
||||
|
||||
# Reduced framerate: 4fps when not lat active and not standstill
|
||||
# Skip 4 out of every 5 frames (20fps -> 4fps)
|
||||
# Reduced framerate: daylight 10fps (skip 1/2), night 4fps (skip 4/5)
|
||||
# Daytime runs twice as fast — better model responsiveness when lighting is good
|
||||
# and the neural net has more signal. Night stays conservative for power.
|
||||
# Write standby timestamp so controlsd suppresses transient errors
|
||||
if not full_rate:
|
||||
is_daylight = params_memory.get_bool("IsDaylight")
|
||||
skip_interval = 2 if is_daylight else 5
|
||||
target_fps = b"10" if is_daylight else b"4"
|
||||
if params_memory.get("ModelFps") != target_fps:
|
||||
params_memory.put("ModelFps", target_fps.decode())
|
||||
now = _time.monotonic()
|
||||
if now - last_standby_ts_write > 1.0:
|
||||
params_memory.put("ModelStandbyTs", str(now))
|
||||
last_standby_ts_write = now
|
||||
if run_count % 5 != 0:
|
||||
if run_count % skip_interval != 0:
|
||||
last_vipc_frame_id = meta_main.frame_id
|
||||
run_count += 1
|
||||
continue
|
||||
else:
|
||||
if params_memory.get("ModelFps") != b"20":
|
||||
params_memory.put("ModelFps", "20")
|
||||
|
||||
desire = DH.desire
|
||||
is_rhd = sm["driverMonitoringState"].isRHD
|
||||
|
||||
@@ -39,6 +39,10 @@ class TiciFanController(BaseFanController):
|
||||
elif ignition:
|
||||
self.controller.neg_limit = -100
|
||||
self.controller.pos_limit = -15
|
||||
# CLEARPILOT: offroad but overheating (startup cooling) — full fan range
|
||||
elif cur_temp >= 75:
|
||||
self.controller.neg_limit = -100
|
||||
self.controller.pos_limit = 0
|
||||
else:
|
||||
self.controller.neg_limit = -30
|
||||
self.controller.pos_limit = 0
|
||||
|
||||
@@ -36,12 +36,9 @@ class PowerMonitoring:
|
||||
# Reset capacity if it's low
|
||||
self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh))
|
||||
|
||||
# FrogPilot variables
|
||||
device_management = self.params.get_bool("DeviceManagement")
|
||||
device_shutdown_setting = self.params.get_int("DeviceShutdown") if device_management else 33
|
||||
# If the toggle is set for < 1 hour, configure by 15 minute increments
|
||||
self.device_shutdown_time = (device_shutdown_setting - 3) * 3600 if device_shutdown_setting >= 4 else device_shutdown_setting * (60 * 15)
|
||||
self.low_voltage_shutdown = self.params.get_float("LowVoltageShutdown") if device_management else VBATT_PAUSE_CHARGING
|
||||
# CLEARPILOT: hardcoded 10 minute shutdown timer (not user-configurable)
|
||||
self.device_shutdown_time = 600
|
||||
self.low_voltage_shutdown = VBATT_PAUSE_CHARGING
|
||||
|
||||
# Calculation tick
|
||||
def calculate(self, voltage: int | None, ignition: bool):
|
||||
|
||||
@@ -197,7 +197,9 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
engaged_prev = False
|
||||
|
||||
params = Params()
|
||||
params_memory = Params("/dev/shm/params")
|
||||
power_monitor = PowerMonitoring()
|
||||
last_touch_reset = "0" # CLEARPILOT: track last seen touch reset value
|
||||
|
||||
HARDWARE.initialize_hardware()
|
||||
thermal_config = HARDWARE.get_thermal_config()
|
||||
@@ -409,6 +411,13 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
statlog.sample("som_power_draw", som_power_draw)
|
||||
msg.deviceState.somPowerDrawW = som_power_draw
|
||||
|
||||
# CLEARPILOT: screen tap resets shutdown timer (off_ts) while offroad
|
||||
touch_reset = params_memory.get("ShutdownTouchReset")
|
||||
if touch_reset is not None and touch_reset != last_touch_reset and off_ts is not None:
|
||||
off_ts = time.monotonic()
|
||||
cloudlog.info("shutdown timer reset by screen touch")
|
||||
last_touch_reset = touch_reset
|
||||
|
||||
# Check if we need to shut down
|
||||
if power_monitor.should_shutdown(onroad_conditions["ignition"], in_car, off_ts, started_seen):
|
||||
cloudlog.warning(f"shutting device down, offroad since {off_ts}")
|
||||
@@ -416,7 +425,7 @@ def thermald_thread(end_event, hw_queue) -> None:
|
||||
params.put_bool("DashcamShutdown", True)
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if not params.getBool("DashcamShutdown"):
|
||||
if not params.get_bool("DashcamShutdown"):
|
||||
cloudlog.info("dashcamd shutdown ack received")
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
+14
-32
@@ -85,15 +85,16 @@ void HomeWindow::updateState(const UIState &s) {
|
||||
showDriverView(s.scene.driver_camera_timer >= 10, true);
|
||||
|
||||
// CLEARPILOT: show splash screen when onroad but in park
|
||||
// In nightrider mode (states 1,4), stay on onroad view in park — only offroad transition shows splash
|
||||
bool parked = s.scene.parked;
|
||||
int screenMode = paramsMemory.getInt("ScreenDisplayMode");
|
||||
bool nightrider = (screenMode == 1 || screenMode == 4);
|
||||
|
||||
if (parked && !was_parked_onroad) {
|
||||
if (!nightrider) {
|
||||
LOGW("CLP UI: park transition -> showing splash");
|
||||
slayout->setCurrentWidget(ready);
|
||||
LOGW("CLP UI: park transition -> showing splash");
|
||||
slayout->setCurrentWidget(ready);
|
||||
// If we were in nightrider mode, switch to screen off
|
||||
if (nightrider) {
|
||||
paramsMemory.putInt("ScreenDisplayMode", 3);
|
||||
}
|
||||
} else if (!parked && was_parked_onroad) {
|
||||
LOGW("CLP UI: drive transition -> showing onroad");
|
||||
@@ -178,13 +179,6 @@ static const char *clpSidebarBtnStyle = R"(
|
||||
|
||||
// clpActionBtnStyle removed — no longer used
|
||||
|
||||
// Shutdown timer: param value -> display label
|
||||
static QString shutdownLabel(int val) {
|
||||
if (val == 0) return "5 mins";
|
||||
if (val <= 3) return QString::number(val * 15) + " mins";
|
||||
int hours = val - 3;
|
||||
return QString::number(hours) + (hours == 1 ? " hour" : " hours");
|
||||
}
|
||||
|
||||
ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
|
||||
// Sidebar
|
||||
@@ -255,27 +249,6 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
|
||||
});
|
||||
general_panel->addItem(resetCalibBtn);
|
||||
|
||||
// Shutdown Timer
|
||||
int cur_shutdown = Params().getInt("DeviceShutdown");
|
||||
auto shutdownBtn = new ButtonControl("Shutdown Timer", shutdownLabel(cur_shutdown),
|
||||
"How long the device stays on after the car is turned off.");
|
||||
connect(shutdownBtn, &ButtonControl::clicked, [=]() {
|
||||
QStringList options;
|
||||
for (int i = 0; i <= 33; i++) {
|
||||
options << shutdownLabel(i);
|
||||
}
|
||||
int current = Params().getInt("DeviceShutdown");
|
||||
QString sel = MultiOptionDialog::getSelection("Shutdown Timer", options, shutdownLabel(current), this);
|
||||
if (!sel.isEmpty()) {
|
||||
int idx = options.indexOf(sel);
|
||||
if (idx >= 0) {
|
||||
Params().putInt("DeviceShutdown", idx);
|
||||
shutdownBtn->setValue(shutdownLabel(idx));
|
||||
}
|
||||
}
|
||||
});
|
||||
general_panel->addItem(shutdownBtn);
|
||||
|
||||
// Power buttons
|
||||
QHBoxLayout *power_layout = new QHBoxLayout();
|
||||
power_layout->setSpacing(30);
|
||||
@@ -365,6 +338,15 @@ ClearPilotPanel::ClearPilotPanel(QWidget* parent) : QFrame(parent) {
|
||||
});
|
||||
debug_panel->addItem(telemetry_toggle);
|
||||
|
||||
auto *health_metrics_toggle = new ToggleControl("System Health Overlay",
|
||||
"Show controls lag, model frame drops, temperature, CPU, and memory usage "
|
||||
"in the lower-right of the onroad UI. For diagnosing slowdown issues.", "",
|
||||
Params().getBool("ClearpilotShowHealthMetrics"), this);
|
||||
QObject::connect(health_metrics_toggle, &ToggleControl::toggleFlipped, [](bool on) {
|
||||
Params().putBool("ClearpilotShowHealthMetrics", on);
|
||||
});
|
||||
debug_panel->addItem(health_metrics_toggle);
|
||||
|
||||
auto *vpn_toggle = new ToggleControl("VPN",
|
||||
"Connect to vpn.hanson.xyz for remote SSH access. "
|
||||
"Disabling kills the active tunnel and stops reconnection attempts.", "",
|
||||
|
||||
@@ -468,6 +468,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
||||
drawSpeedLimitSign(p);
|
||||
drawCruiseWarningSign(p);
|
||||
|
||||
// CLEARPILOT: system health metrics in lower-right (debug overlay)
|
||||
drawHealthMetrics(p);
|
||||
|
||||
// Draw FrogPilot widgets
|
||||
paintFrogPilotWidgets(p);
|
||||
}
|
||||
@@ -727,6 +730,82 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t
|
||||
p.drawText(real_rect.x(), real_rect.bottom(), text);
|
||||
}
|
||||
|
||||
// CLEARPILOT: System health overlay — shows metrics that indicate the system
|
||||
// is overburdened or behind. Toggled via ClearpilotShowHealthMetrics param.
|
||||
// Metrics (top→bottom): LAG, DROP, TEMP, CPU, MEM
|
||||
// LAG (ms): controlsd cumLagMs — most direct "is the loop keeping up" indicator
|
||||
// DROP (%): modelV2 frameDropPerc — modeld losing frames; controlsd errors >20%
|
||||
// TEMP (°C): deviceState.maxTempC — thermal throttling starts ~75, serious >88
|
||||
// CPU (%): max core from deviceState.cpuUsagePercent
|
||||
// MEM (%): deviceState.memoryUsagePercent
|
||||
// Each value color-codes green/yellow/red by severity.
|
||||
void AnnotatedCameraWidget::drawHealthMetrics(QPainter &p) {
|
||||
static bool enabled = Params().getBool("ClearpilotShowHealthMetrics");
|
||||
static int check_counter = 0;
|
||||
// re-check the param every ~2s without a toggle signal path
|
||||
if (++check_counter >= 40) {
|
||||
check_counter = 0;
|
||||
enabled = Params().getBool("ClearpilotShowHealthMetrics");
|
||||
}
|
||||
if (!enabled) return;
|
||||
|
||||
SubMaster &sm = *(uiState()->sm);
|
||||
auto cs = sm["controlsState"].getControlsState();
|
||||
auto ds = sm["deviceState"].getDeviceState();
|
||||
auto mv = sm["modelV2"].getModelV2();
|
||||
|
||||
float lag_ms = cs.getCumLagMs();
|
||||
float drop_pct = mv.getFrameDropPerc();
|
||||
float temp_c = ds.getMaxTempC();
|
||||
int mem_pct = ds.getMemoryUsagePercent();
|
||||
int cpu_pct = 0;
|
||||
for (auto v : ds.getCpuUsagePercent()) cpu_pct = std::max(cpu_pct, (int)v);
|
||||
|
||||
auto color_for = [](float v, float warn, float crit) {
|
||||
if (v >= crit) return QColor(0xff, 0x50, 0x50); // red
|
||||
if (v >= warn) return QColor(0xff, 0xd0, 0x40); // yellow
|
||||
return QColor(0xff, 0xff, 0xff); // white (ok)
|
||||
};
|
||||
|
||||
struct Row { QString label; QString value; QColor color; };
|
||||
Row rows[] = {
|
||||
{"LAG", QString::number((int)lag_ms), color_for(lag_ms, 50.f, 200.f)},
|
||||
{"DROP", QString::number((int)drop_pct),color_for(drop_pct, 5.f, 15.f)},
|
||||
{"TEMP", QString::number((int)temp_c), color_for(temp_c, 75.f, 88.f)},
|
||||
{"CPU", QString::number(cpu_pct), color_for((float)cpu_pct, 75.f, 90.f)},
|
||||
{"MEM", QString::number(mem_pct), color_for((float)mem_pct, 70.f, 85.f)},
|
||||
};
|
||||
|
||||
p.save();
|
||||
p.setFont(InterFont(90, QFont::Bold));
|
||||
QFontMetrics fm = p.fontMetrics();
|
||||
int row_h = fm.height(); // natural line height at 90pt bold
|
||||
int gap = 40; // requested 40px between values
|
||||
int margin = 30; // requested 30px margin
|
||||
int panel_w = 360; // fixed width — fits "TEMP 99"
|
||||
int n = sizeof(rows) / sizeof(rows[0]);
|
||||
int panel_h = n * row_h + (n - 1) * gap + 2 * margin;
|
||||
int x = width() - panel_w - margin;
|
||||
int y = height() - panel_h - margin;
|
||||
|
||||
// black background
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QColor(0, 0, 0, 200));
|
||||
p.drawRoundedRect(QRect(x, y, panel_w, panel_h), 20, 20);
|
||||
|
||||
// rows
|
||||
int text_y = y + margin + fm.ascent();
|
||||
for (int i = 0; i < n; i++) {
|
||||
p.setPen(rows[i].color);
|
||||
// label left, value right
|
||||
p.drawText(x + margin, text_y, rows[i].label);
|
||||
QRect vrect = fm.boundingRect(rows[i].value);
|
||||
p.drawText(x + panel_w - margin - vrect.width(), text_y, rows[i].value);
|
||||
text_y += row_h + gap;
|
||||
}
|
||||
p.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::initializeGL() {
|
||||
CameraWidget::initializeGL();
|
||||
qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION));
|
||||
@@ -867,8 +946,9 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
}
|
||||
|
||||
if (outlineOnly) {
|
||||
// CLEARPILOT: center path (tire track) is 2x wider than other lines in nightrider
|
||||
painter.setPen(QPen(QColor(center_lane_color.red(), center_lane_color.green(),
|
||||
center_lane_color.blue(), 180), outlineWidth));
|
||||
center_lane_color.blue(), 180), outlineWidth * 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
} else {
|
||||
painter.setPen(Qt::NoPen);
|
||||
|
||||
@@ -53,6 +53,7 @@ private:
|
||||
void drawSpeedWidget(QPainter &p, int x, int y, const QString &title, const QString &speedLimitStr, QColor colorSpeed, int width = 176);
|
||||
void drawSpeedLimitSign(QPainter &p);
|
||||
void drawCruiseWarningSign(QPainter &p);
|
||||
void drawHealthMetrics(QPainter &p);
|
||||
|
||||
QVBoxLayout *main_layout;
|
||||
QPixmap dm_img;
|
||||
|
||||
Binary file not shown.
@@ -171,6 +171,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseMove: {
|
||||
// CLEARPILOT: tap while screen-off (mode 3) -> wake to auto-normal (mode 0)
|
||||
Params pmem{"/dev/shm/params"};
|
||||
if (!device()->isAwake()) {
|
||||
if (pmem.getInt("ScreenDisplayMode") == 3) {
|
||||
pmem.putInt("ScreenDisplayMode", 0);
|
||||
}
|
||||
}
|
||||
// CLEARPILOT: reset shutdown timer on any screen touch
|
||||
static int touch_counter = 0;
|
||||
pmem.put("ShutdownTouchReset", std::to_string(++touch_counter));
|
||||
// ignore events when device is awakened by resetInteractiveTimeout
|
||||
ignore = !device()->isAwake();
|
||||
device()->resetInteractiveTimeout(uiState()->scene.screen_timeout, uiState()->scene.screen_timeout_onroad);
|
||||
@@ -256,6 +266,11 @@ static StatusWindow::StatusData collectStatus() {
|
||||
// Telemetry
|
||||
d.telemetry = readFile("/data/params/d/TelemetryEnabled");
|
||||
|
||||
// Dashcam
|
||||
d.dashcam_state = readFile("/dev/shm/params/d/DashcamState");
|
||||
if (d.dashcam_state.isEmpty()) d.dashcam_state = "stopped";
|
||||
d.dashcam_frames = readFile("/dev/shm/params/d/DashcamFrames");
|
||||
|
||||
// Panda: checked on UI thread in applyResults() via scene.pandaType
|
||||
|
||||
return d;
|
||||
@@ -298,6 +313,7 @@ StatusWindow::StatusWindow(QWidget *parent) : QFrame(parent) {
|
||||
vpn_label = makeRow("VPN");
|
||||
gps_label = makeRow("GPS");
|
||||
telemetry_label = makeRow("Telemetry");
|
||||
dashcam_label = makeRow("Dashcam");
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
@@ -369,6 +385,19 @@ void StatusWindow::applyResults() {
|
||||
telemetry_label->setText("Disabled");
|
||||
telemetry_label->setStyleSheet("color: grey; font-size: 38px;");
|
||||
}
|
||||
|
||||
if (d.dashcam_state == "recording") {
|
||||
QString text = "Recording";
|
||||
if (!d.dashcam_frames.isEmpty() && d.dashcam_frames != "0") text += " (" + d.dashcam_frames + " frames)";
|
||||
dashcam_label->setText(text);
|
||||
dashcam_label->setStyleSheet("color: #17c44d; font-size: 38px;");
|
||||
} else if (d.dashcam_state == "waiting") {
|
||||
dashcam_label->setText("Waiting");
|
||||
dashcam_label->setStyleSheet("color: #ffaa00; font-size: 38px;");
|
||||
} else {
|
||||
dashcam_label->setText("Stopped");
|
||||
dashcam_label->setStyleSheet("color: #ff4444; font-size: 38px;");
|
||||
}
|
||||
}
|
||||
|
||||
void StatusWindow::mousePressEvent(QMouseEvent *e) {
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
struct StatusData {
|
||||
QString time, storage, ram, load, temp, fan, ip, wifi;
|
||||
QString vpn_status, vpn_ip, gps, telemetry;
|
||||
QString dashcam_state, dashcam_frames;
|
||||
float temp_c = 0;
|
||||
};
|
||||
|
||||
@@ -49,6 +50,7 @@ private:
|
||||
QLabel *gps_label;
|
||||
QLabel *time_label;
|
||||
QLabel *telemetry_label;
|
||||
QLabel *dashcam_label;
|
||||
QLabel *panda_label;
|
||||
};
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ def main():
|
||||
params_memory = Params("/dev/shm/params")
|
||||
last_daylight_check = 0.0
|
||||
daylight_computed = False
|
||||
prev_daylight = None # CLEARPILOT: gate IsDaylight write on change
|
||||
print("gpsd: entering main loop", file=sys.stderr, flush=True)
|
||||
|
||||
while True:
|
||||
@@ -205,7 +206,10 @@ def main():
|
||||
last_daylight_check = now_mono
|
||||
utc_now = datetime.datetime.utcfromtimestamp(fix["timestamp_ms"] / 1000)
|
||||
daylight = is_daylight(fix["latitude"], fix["longitude"], utc_now)
|
||||
params_memory.put_bool("IsDaylight", daylight)
|
||||
# CLEARPILOT: gate on change — daylight flips twice a day, don't rewrite every 30s
|
||||
if daylight != prev_daylight:
|
||||
params_memory.put_bool("IsDaylight", daylight)
|
||||
prev_daylight = daylight
|
||||
|
||||
if not daylight_computed:
|
||||
daylight_computed = True
|
||||
@@ -221,7 +225,7 @@ def main():
|
||||
params_memory.put_int("ScreenDisplayMode", 0)
|
||||
cloudlog.warning("gpsd: auto-switch to normal (sunrise)")
|
||||
|
||||
time.sleep(1.0) # 1 Hz polling
|
||||
time.sleep(0.5) # 2 Hz polling
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user