/**
* @file commercial_vehicle_native_core.c
* @brief Solution complète d'encapsulation et de débogage pour la couche Native
*
* Version : v3.0
* Date : 2026-03-20
*
* Principes de conception fondamentaux :
* 1. Données mesurables : toutes les caches, files d'attente et messages ont des définitions de taille explicites.
* 2. Opérations visibles : chaque étape dispose de moyens de débogage, le flux de données est observable.
* 3. Structure compréhensible : le code est organisé comme une boîte à outils, selon la logique humaine.
*
* Pourquoi insister sur les unités de données ?
* - Une conception sans chiffres est une construction dans les airs.
* - Connaître la "taille du cache" permet de juger si elle est suffisante.
* - Connaître la "quantité de données" permet d'identifier les goulots d'étranglement de performance.
*/
Partie 1 : Architecture Globale de la Couche Native - Visualiser le Code comme une Carte
1.1 Conceptino de l'Architecture du Daemon Native pour Android
【Schéma d'ensemble de l'Architecture du Daemon Native pour Android】
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Application (Java/Kotlin) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │AppVue360 │ │AppAlerteBSD│ │AppMonitDMS │ │CentreParam │ │
│ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │
│ │ │ │ │ │
│ ↓ ↓ ↓ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Couche Framework (Java) │ │
│ │ - android.hardware.vehicle.IVehicleService.aidl │ │
│ │ - IVehicleServiceCallback.aidl (réponse asynchrone) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↑ Binder IPC
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Service Native (Daemon C++) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ VehicleService (BnInterface) │ │
│ │ - Implémentation de l'interface AIDL │ │
│ │ - Gestion des connexions client (max 32 clients) │ │
│ │ - Vérification des permissions (uniquement system/camera) │ │
│ └──────────────┬───────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────────────────────────────────┐ │
│ │ Modules Métier Principaux (Singleton) │ │
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │
│ │ │Gestionnaire │ │Gestionnaire │ │Gestionnaire │ │ │
│ │ │Caméras │ │IA │ │Véhicule │ │ │
│ │ │- Gère 8 caméras│ │- Gère 4 modèles│ │- Traitement CAN│ │ │
│ │ │- Gestion cache │ │- File inférence│ │- Vitesse/Clignot│ │ │
│ │ │- Conversion fmt│ │- Callbacks │ │- Diagnostic │ │ │
│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌──────────────▼───────────────▼─────────────────────────────────┐ │
│ │ Couche Adaptateur HAL │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ libzcamera.so (encapsule V4L2/RKISP) │ │ │
│ │ │ - Ouverture/Fermeture périphérique │ │ │
│ │ │ - Gestion mémoire (DMA-BUF/MMAP) │ │ │
│ │ │ - Réglage paramètres (exposition/gain/balance blancs) │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ librknnrt.so (moteur d'inférence RKNN) │ │ │
│ │ │ - Chargement modèle (max 4 modèles simultanés) │ │ │
│ │ │ - Exécution inférence (file asynchrone) │ │ │
│ │ │ - Analyse résultats sortie │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Noyau (Linux 5.10 + Rockchip) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Pilote V4L2 | SerDes GMSL | RKISP | DMA-BUF | ION │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
【Points clés de conception】
1. Pourquoi utiliser Binder et non un Socket ?
- Binder est le mécanisme IPC standard d'Android, supporte les types de données complexes.
- Copie unique, meilleures performances.
- Gestion automatique du pool de threads, pas besoin de gérer la concurrence soi-même.
2. Cycle de vie du Daemon
- Démarré par init, défini dans le fichier rc.
- Redémarrage automatique après un crash (oneshot=false).
- Support des politiques SELinux.
1.2 Conception de l'Architecture Native pour Buildroot RV1126
【Schéma d'ensemble de l'Architecture Native pour RV1126】
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Application (Applications C/C++) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │AppAlerteBSD│ │AppMonitDMS │ │AppEncodVid │ │GestConfig │ │
│ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │
│ │ │ │ │ │
│ ↓ ↓ ↓ ↓ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Couche Service Native (Daemon C++) │ │
│ │ - Communication inter-processus : Socket de domaine Unix │ │
│ │ - Protocole : protocole binaire personnalisé (format TLV) │ │
│ │ - Nombre de connexions : max 8 clients │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Modules Métier Principaux (Singleton) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ VehicleService (Service principal) │ │
│ │ - Fork un processus fils de garde au démarrage (double garde)│ │
│ │ - Communication avec les clients via Socket │ │
│ │ - Détection de battement de cœur périodique (3 secondes) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌──────────────▼───────────────▼─────────────────────────────────┐ │
│ │ Gestionnaire de Modules (ModuleManager) │ │
│ │ - Chargement dynamique des modules algorithmes (dlopen) │ │
│ │ - Gestion des dépendances entre modules │ │
│ │ - Isolation des ressources │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌──────────────▼─────┐ ┌────────────▼─────────────────────────┐ │
│ │ ModuleBSD │ │ ModuleDMS │ │
│ │ - Charge yolov5s.rknn │ │ - Charge dms_fatigue.rknn │ │
│ │ - Entrée : 640x640x3 │ │ - Entrée : 224x224x3 │ │
│ │ - Sortie : 84x8400 │ │ - Sortie : 3 classifications + 6 landmarks │ │
│ │ - Cache : anneau 32 trames │ │ - Cache : fenêtre glissante 16 trames │ │
│ └────────────────────┘ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Adaptateur HAL │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ libzcamera_rv1126.so (HAL caméra dédié RV1126) │ │
│ │ - Supporte 4 entrées │ │
│ │ - Configuration ISP intégrée │ │
│ │ - Réduction du bruit 2D/3D │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ librknn_rv1126.so (pilote NPU pour RV1126) │ │
│ │ - NPU 2.0 TOPS │ │
│ │ - Inférence quantifiée INT8 │ │
│ │ - Concurrence max 2 modèles │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ Couche Noyau (Linux 5.10) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ V4L2 | RKISP | RKNPU | SPI | I2C | PWM │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
【Pourquoi RV1126 n'utilise-t-il pas Binder ?】
1. Buildroot est un Linux léger, sans mécanisme Binder.
2. Le Socket de domaine Unix est suffisamment léger pour la communication inter-processus.
3. L'empreinte ressource est plus faible (Binder nécessite 500KB+ de mémoire).
4. La double garde avec fork nécessite une gestion des processus parent/enfant, ce qui est plus simple sans Binder.
Partie 2 : Implémentation Complète du Daemon Native pour Android
2.1 Définition de l'Interface AIDL - Définir le Protocole d'abord
// IVehicleService.aidl
package commercial.vehicle.hardware;
/**
* @file IVehicleService.aidl
* @brief Définition de l'interface du service Native
*
* Pourquoi cette conception ?
* 1. Chaque caméra est contrôlée indépendamment, sans interférence.
* 2. Retour asynchrone via callback, évite le blocage.
* 3. Transfert de données massives via FileDescriptor, réduit les copies.
*/
import commercial.vehicle.hardware.CameraConfig;
import commercial.vehicle.hardware.DetectionResult;
interface IVehicleService {
/**
* Initialiser le service - doit être appelé en premier
* @return 0 succès, code d'erreur négatif
*/
int initialize();
/**
* Ouvrir une caméra spécifique
* @param camIdx 0-7
* @param config Paramètres de configuration (résolution/fps/format)
* @param callback Interface de rappel pour les données
* @return 0 succès, code d'erreur négatif
*/
int activateCamera(int camIdx, in CameraConfig config, IVehicleServiceCallback callback);
/**
* Fermer une caméra
*/
int deactivateCamera(int camIdx);
/**
* Démarrer la détection IA
* @param detectionType Type de détection : 0-vue à 360°, 1-BSD, 2-DMS
* @param modelFile Chemin vers le fichier modèle
* @param callback Rappel des résultats
*/
int beginAIDetection(int detectionType, String modelFile, IVehicleServiceCallback callback);
/**
* Arrêter la détection IA
*/
int endAIDetection(int detectionType);
/**
* Capturer une image (synchrone)
* @param camIdx
* @param waitMs Temps d'attente maximum en ms
* @return FileDescriptor pointant vers la mémoire partagée
*/
FileDescriptor captureFrame(int camIdx, int waitMs);
/**
* Obtenir la version du service
*/
String getServiceVersion();
}
// IVehicleServiceCallback.aidl
package commercial.vehicle.hardware;
import commercial.vehicle.hardware.DetectionResult;
interface IVehicleServiceCallback {
/**
* Rappel de trame de caméra
* @param camIdx
* @param frameData Données de l'image (transmises via mémoire partagée)
* @param dataSize Taille des données
* @param timestamp Horodatage
*/
void onNewFrame(int camIdx, in byte[] frameData, int dataSize, long timestamp);
/**
* Rappel des résultats de détection IA
* @param result Résultat de détection
*/
void onDetectionOutcome(in DetectionResult result);
/**
* Rappel d'erreur
* @param errCode Code d'erreur
* @param errDescription Description de l'erreur
*/
void onErrorOccurred(int errCode, String errDescription);
}
// CameraConfig.aidl
package commercial.vehicle.hardware;
parcelable CameraConfig {
int resWidth = 1920;
int resHeight = 1080;
int frameRate = 30;
int pixelFormat = 0; // 0: NV12, 1: YUYV, 2: JPEG
boolean hdrEnabled = true;
boolean noiseReduction3D = true;
int exposureUs = 0; // µs, 0 pour auto
int gainDb = 0; // dB, 0 pour auto
}
// DetectionResult.aidl
package commercial.vehicle.hardware;
parcelable DetectionResult {
int detectionType; // 0: BSD, 1: DMS, 2: Vue 360°
long resultTimestamp; // Horodatage
int associatedCamera; // Caméra associée
// Boîtes de détection (max 32)
Rect[] detectedObjects;
// Résultats de classification (spécifique DMS)
int alertnessLevel; // 0-normal, 1-fatigue légère, 2-fatigue sévère
boolean phoneDetected; // Téléphone détecté ?
boolean smokingDetected; // Cigarette détectée ?
float[] headOrientation; // Orientation tête [lacet, tangage, roulis]
// Résultats zone morte (spécifique BSD)
boolean leftZoneOccupied;
boolean rightZoneOccupied;
float leftVehicleDist; // Distance véhicule gauche (mètres)
float rightVehicleDist; // Distance véhicule droite (mètres)
}
parcelable Rect {
float left; // Coordonnée normalisée 0-1
float top;
float right;
float bottom;
float score; // Score de confiance 0-1
int category; // Identifiant catégorie : 0-voiture,1-camion,2-piéton,3-vélo
}
2.2 Implémentation du Service Native - VehicleService.cpp
/**
* @file VehicleService.cpp
* @brief Implémentation centrale du service Native pour Android
*
* Compilation :
* - Android.bp : cc_binary + cc_library_shared
* - Sortie : /system/bin/vehicle_service
*/
#define LOG_TAG "VehicleService"
#include <log>
#include <binder>
#include <binder>
#include <binder>
#include <utils>
#include <cutils>
#include <sys>
#include "IVehicleService.h"
#include "IVehicleServiceCallback.h"
using namespace android;
/*============================================================================
* Définition des Unités de Données - Toutes les tailles sont chiffrées
*============================================================================*/
// Configuration des caches
#define MAX_CAMERA_SLOTS 8 // Nombre maximum de caméras
#define MAX_CONCURRENT_CLIENTS 32 // Nombre maximum de clients simultanés
#define AI_MODEL_POOL_SIZE 4 // Nombre de modèles IA pouvant être chargés
#define HISTORICAL_FRAMES_RING 128 // Taille du buffer circulaire (128 trames)
#define DEFAULT_WIDTH 1920 // Largeur par défaut
#define DEFAULT_HEIGHT 1080 // Hauteur par défaut
#define FRAME_BYTE_SIZE (DEFAULT_WIDTH * DEFAULT_HEIGHT * 3 / 2) // Format NV12 : 1920*1080*1.5 ≈ 3.1Mo
#define REUSABLE_FRAME_POOL 8 // Taille du pool de trames réutilisables
// Calcul de l'empreinte mémoire :
// Une trame NV12 : 1920*1080*1.5 = 3 110 400 octets ≈ 3.1 Mo
// Buffer circulaire 128 trames : 3.1 Mo * 128 = 396.8 Mo
// Pool de trames 8 : 3.1 Mo * 8 = 24.8 Mo
// Total : ~421.6 Mo (raisonnable sur RK3588 avec 8Go de RAM)
// Configuration des délais d'attente (millisecondes)
#define FRAME_WAIT_TIMEOUT 1000 // Timeout attente trame : 1s
#define HEARTBEAT_INTERVAL_MS 3000 // Intervalle battement cœur : 3s
#define IPC_DEADLINE_MS 5000 // Délai IPC : 5s
// Codes d'erreur
#define SUCCESS_CODE 0
#define INIT_FAILURE -1000
#define CAMERA_NOT_AVAILABLE -1001
#define CAMERA_IN_USE -1002
#define INVALID_ARGUMENT -1003
#define ALLOCATION_FAILED -1004
#define TIMEOUT_ERROR -1005
#define ACCESS_DENIED -1006
/*============================================================================
* Structures de Données
*============================================================================*/
/**
* @brief Structure de trame vidéo - Chaque champ a une taille claire
*/
typedef struct {
uint32_t identifier; // Identifiant : 0x56484543 ("VHCL")
uint32_t sequence_number; // Numéro de séquence (0-0xFFFFFFFF)
uint64_t capture_time_ns; // Horodatage en nanosecondes
uint32_t source_camera; // ID de la caméra source (0-7)
uint32_t frame_width; // Largeur (1920)
uint32_t frame_height; // Hauteur (1080)
uint32_t color_format; // Format (0: NV12)
uint32_t data_length; // Longueur des données (3 110 400)
int shared_mem_fd; // Descripteur DMA-BUF (pour copie zéro)
uint8_t* pixel_buffer; // Pointeur vers les données
int reference_count; // Nombre de références
} VideoSegment;
/**
* @brief Contexte de caméra
*/
typedef struct {
int camera_id; // ID
char device_path[32]; // /dev/videoX
int file_descriptor; // Handle du périphérique
bool is_active; // Est active ?
CameraConfig current_settings; // Configuration actuelle
// Gestion du cache
VideoSegment* frame_cache[REUSABLE_FRAME_POOL]; // Pool de trames
int allocated_frame_count; // Nombre de trames allouées
CircularBuffer* history_buffer; // Buffer circulaire
pthread_mutex_t access_lock;
// Connexe à V4L2
struct v4l2_buffer v4l2_entries[4]; // Entrées V4L2
void* mapped_memory[4]; // Adresses mmap
int v4l2_buffer_count; // Nombre de buffers V4L2
} CameraInstance;
/**
* @brief Contexte de modèle IA
*/
typedef struct {
int model_type; // 0-vue360, 1-BSD, 2-DMS
char file_path[128]; // Chemin du modèle
void* nn_context; // Contexte RKNN
int input_res_w; // Largeur entrée
int input_res_h; // Hauteur entrée
int input_tensor_size; // Taille données entrée
int output_tensor_size; // Taille données sortie
float confidence_threshold; // Seuil de confiance
bool is_loaded; // Est chargé ?
} AIModelInstance;
/**
* @brief Contexte de connexion client
*/
typedef struct {
sp<ivehicleservicecallback> callback_ref; // Interface de rappel
pid_t client_pid; // PID du processus client
uid_t client_uid; // UID du client
int active_camera_mask; // Bitmap des caméras ouvertes
int active_ai_mask; // Bitmap des types IA actifs
int64_t last_heartbeat_time; // Heure du dernier battement cœur
bool is_valid; // Est valide ?
} ClientSession;
/*============================================================================
* Définition de la Classe VehicleService
*============================================================================*/
class VehicleService : public BnInterface<ivehicleservice> {
private:
// Mode singleton
static VehicleService* s_shared_instance;
static Mutex s_instance_mutex;
// Données centrales - Toutes initialisées avec des valeurs par défaut
CameraInstance m_camera_slots[MAX_CAMERA_SLOTS];
AIModelInstance m_ai_models[AI_MODEL_POOL_SIZE];
ClientSession m_client_sessions[MAX_CONCURRENT_CLIENTS];
// Gestion des threads
pthread_t m_acquisition_threads[MAX_CAMERA_SLOTS]; // Threads d'acquisition (1 par caméra)
pthread_t m_processing_threads[AI_MODEL_POOL_SIZE]; // Threads de traitement IA
pthread_t m_maintenance_thread; // Thread de maintenance
// Contrôle de synchronisation
volatile bool m_service_running; // Drapeau d'exécution
Mutex m_global_lock;
Condition m_frame_available_condition;
// Statistiques
uint64_t m_total_acquired_frames; // Nombre total de trames acquises
uint64_t m_total_processed_inferences; // Nombre total d'inférences
uint32_t m_discarded_frame_count; // Compteur de trames abandonnées
uint32_t m_error_counts[MAX_CAMERA_SLOTS]; // Compteur d'erreurs par caméra
public:
VehicleService();
virtual ~VehicleService();
// Obtention du singleton
static VehicleService* getInstance();
// Implémentation de l'interface IVehicleService
virtual status_t initialize();
virtual int activateCamera(int camIdx, const CameraConfig& config,
const sp<ivehicleservicecallback>& callback);
virtual int deactivateCamera(int camIdx);
virtual int beginAIDetection(int detectionType, const String16& modelFile,
const sp<ivehicleservicecallback>& callback);
virtual int endAIDetection(int detectionType);
virtual int captureFrame(int camIdx, int waitMs, int* sharedFd);
virtual String16 getServiceVersion();
// Gestion du service Binder
virtual status_t dump(int fd, const Vector<string16>& args);
private:
// Fonctions d'initialisation
status_t initializeCameras();
status_t initializeAIModels();
// Opérations caméra
int openVideoDevice(CameraInstance* cam, const CameraConfig& config);
int beginStreaming(CameraInstance* cam);
int haltStreaming(CameraInstance* cam);
// Gestion du cache
VideoSegment* allocateSegment(uint32_t data_size);
void releaseSegment(VideoSegment* segment);
VideoSegment* acquireFromCache();
void returnToCache(VideoSegment* segment);
// Fonctions de thread
static void* acquisitionRoutine(void* argument);
static void* inferenceRoutine(void* argument);
static void* maintenanceRoutine(void* argument);
// Gestion des clients
int registerNewClient(const sp<ivehicleservicecallback>& callback);
void removeClient(int session_index);
bool verifyPermission(const char* permission_name);
// Aide au débogage
void writeStatusReport(int output_fd);
};
/*============================================================================
* Constructeur - Initialisation de tous les membres
*============================================================================*/
VehicleService::VehicleService()
: m_service_running(false)
, m_total_acquired_frames(0)
, m_total_processed_inferences(0)
, m_discarded_frame_count(0) {
ALOGI("VehicleService initialisé");
// Mise à zéro de tous les tableaux
memset(m_camera_slots, 0, sizeof(m_camera_slots));
memset(m_ai_models, 0, sizeof(m_ai_models));
memset(m_client_sessions, 0, sizeof(m_client_sessions));
// Initialisation des identifiants caméra
for (int i = 0; i < MAX_CAMERA_SLOTS; i++) {
m_camera_slots[i].camera_id = i;
snprintf(m_camera_slots[i].device_path, sizeof(m_camera_slots[i].device_path),
"/dev/video%d", i);
pthread_mutex_init(&m_camera_slots[i].access_lock, NULL);
m_camera_slots[i].history_buffer = createCircularBuffer(HISTORICAL_FRAMES_RING);
}
// Initialisation des modèles IA
for (int i = 0; i < AI_MODEL_POOL_SIZE; i++) {
m_ai_models[i].model_type = i;
m_ai_models[i].is_loaded = false;
}
ALOGI("VehicleService configuré : taille_trame=%d Ko, buffer_circulaire=%d trames, mémoire_totale=%d Mo",
FRAME_BYTE_SIZE / 1024, HISTORICAL_FRAMES_RING,
(FRAME_BYTE_SIZE * HISTORICAL_FRAMES_RING + FRAME_BYTE_SIZE * REUSABLE_FRAME_POOL) / (1024 * 1024));
}
/*============================================================================
* Initialisation du Service - Préparation réelle du matériel
*============================================================================*/
status_t VehicleService::initialize() {
Mutex::Autolock lock(m_global_lock);
ALOGI("Début d'initialisation de VehicleService");
if (m_service_running) {
ALOGW("Déjà initialisé");
return OK;
}
// 1. Vérification des permissions - Doit être un service système
if (!verifyPermission("android.permission.CAMERA")) {
ALOGE("Permission refusée : permission CAMERA requise");
return PERMISSION_DENIED;
}
// 2. Initialisation des caméras
status_t result = initializeCameras();
if (result != OK) {
ALOGE("Échec de initializeCameras : %d", result);
return result;
}
// 3. Initialisation des modèles IA (charge les modèles par défaut)
result = initializeAIModels();
if (result != OK) {
ALOGE("Échec de initializeAIModels : %d", result);
return result;
}
// 4. Lancement du thread de maintenance
m_service_running = true;
pthread_create(&m_maintenance_thread, NULL, maintenanceRoutine, this);
ALOGI("Initialisation de VehicleService terminée avec succès");
return OK;
}
/**
* @brief Initialisation des caméras - Scan et ouverture de toutes les caméras disponibles
*/
status_t VehicleService::initializeCameras() {
ALOGI("Scan des caméras...");
int devices_found = 0;
for (int i = 0; i < MAX_CAMERA_SLOTS; i++) {
CameraInstance* cam = &m_camera_slots[i];
// Tentative d'ouverture du périphérique
cam->file_descriptor = open(cam->device_path, O_RDWR);
if (cam->file_descriptor < 0) {
ALOGV("Caméra %d absente : %s", i, strerror(errno));
continue;
}
// Interrogation des capacités V4L2
struct v4l2_capability capabilities;
if (ioctl(cam->file_descriptor, VIDIOC_QUERYCAP, &capabilities) < 0) {
ALOGW("Caméra %d VIDIOC_QUERYCAP échoué : %s", i, strerror(errno));
close(cam->file_descriptor);
cam->file_descriptor = -1;
continue;
}
ALOGI("Caméra %d : %s pilote:%s carte:%s bus:%s",
i, capabilities.card, capabilities.driver, capabilities.card, capabilities.bus_info);
// Vérification que c'est un périphérique de capture
if (!(capabilities.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
ALOGW("Caméra %d n'est pas un périphérique de capture", i);
close(cam->file_descriptor);
cam->file_descriptor = -1;
continue;
}
// Configuration par défaut
cam->is_active = false;
cam->current_settings.resWidth = DEFAULT_WIDTH;
cam->current_settings.resHeight = DEFAULT_HEIGHT;
cam->current_settings.frameRate = 30;
cam->current_settings.pixelFormat = 0; // NV12
cam->current_settings.hdrEnabled = true;
cam->current_settings.noiseReduction3D = true;
// Allocation du pool de trames
for (int j = 0; j < REUSABLE_FRAME_POOL; j++) {
cam->frame_cache[j] = allocateSegment(FRAME_BYTE_SIZE);
if (!cam->frame_cache[j]) {
ALOGE("Échec d'allocation du pool de trames pour la caméra %d", i);
} else {
cam->allocated_frame_count++;
}
}
devices_found++;
close(cam->file_descriptor); // Fermeture temporaire, ouverture réelle lors de activateCamera
cam->file_descriptor = -1;
}
ALOGI("Caméras trouvées : %d", devices_found);
return (devices_found > 0) ? OK : NO_INIT;
}
/**
* @brief Initialisation des modèles IA - Charge les modèles par défaut
*/
status_t VehicleService::initializeAIModels() {
ALOGI("Initialisation des modèles IA...");
// Chemins par défaut des modèles
const char* model_paths[] = {
"/vendor/etc/vehicle_ai/models/bsd_yolov5.rknn",
"/vendor/etc/vehicle_ai/models/dms_attention.rknn",
"/vendor/etc/vehicle_ai/models/panorama_stitch.rknn",
};
for (int i = 0; i < 3; i++) {
AIModelInstance* model = &m_ai_models[i];
FILE* model_file = fopen(model_paths[i], "rb");
if (!model_file) {
ALOGW("Modèle %s non trouvé", model_paths[i]);
continue;
}
fclose(model_file);
// Enregistre le chemin du modèle (chargement différé)
strcpy(model->file_path, model_paths[i]);
model->model_type = i;
// Définition des dimensions d'entrée/sortie
switch (i) {
case 0: // BSD
model->input_res_w = 640;
model->input_res_h = 640;
model->input_tensor_size = 640 * 640 * 3; // 1 228 800 octets
model->output_tensor_size = 84 * 8400 * sizeof(float); // 2.8Mo
model->confidence_threshold = 0.5;
break;
case 1: // DMS
model->input_res_w = 224;
model->input_res_h = 224;
model->input_tensor_size = 224 * 224 * 3; // 150 528 octets
model->output_tensor_size = 1024 * sizeof(float); // 4Ko
model->confidence_threshold = 0.6;
break;
case 2: // Vue 360°
model->input_res_w = 384;
model->input_res_h = 384;
model->input_tensor_size = 384 * 384 * 3 * 4; // 4 entrées
model->output_tensor_size = FRAME_BYTE_SIZE; // 3.1Mo
model->confidence_threshold = 0.0;
break;
}
ALOGI("Modèle %d : %s entrée=%dx%d taille=%dKo sortie=%dKo",
i, model_paths[i], model->input_res_w, model->input_res_h,
model->input_tensor_size / 1024, model->output_tensor_size / 1024);
}
return OK;
}
/*============================================================================
* Gestion de l'Allocation des Trames - Opérations critiques sur le pool mémoire
*============================================================================*/
/**
* @brief Alloue une trame - Utilise posix_memalign pour garantir l'alignement
*
* @param data_size Taille de la trame (typiquement 3.1Mo)
* @return VideoSegment* Pointeur vers la trame, NULL en cas d'échec
*/
VideoSegment* VehicleService::allocateSegment(uint32_t data_size) {
VideoSegment* seg = (VideoSegment*)malloc(sizeof(VideoSegment));
if (!seg) return NULL;
memset(seg, 0, sizeof(VideoSegment));
// Alignement 16 octets, favorable pour l'accès NEON/DSP
void* aligned_buffer;
if (posix_memalign(&aligned_buffer, 16, data_size) != 0) {
free(seg);
return NULL;
}
seg->identifier = 0x56484543; // "VHCL"
seg->pixel_buffer = (uint8_t*)aligned_buffer;
seg->data_length = data_size;
seg->reference_count = 1;
seg->shared_mem_fd = -1;
return seg;
}
/**
* @brief Libère une trame - Le compteur de références est décrémenté, libération réelle à 0
*/
void VehicleService::releaseSegment(VideoSegment* segment) {
if (!segment) return;
if (segment->identifier != 0x56484543) {
ALOGE("Identifiant de trame invalide : %x", segment->identifier);
return;
}
int current_ref = android_atomic_dec(&segment->reference_count);
if (current_ref > 1) {
// D'autres références existent encore
return;
}
// Libération réelle
if (segment->pixel_buffer) {
free(segment->pixel_buffer);
}
if (segment->shared_mem_fd >= 0) {
close(segment->shared_mem_fd);
}
free(segment);
}
/**
* @brief Acquiert une trame depuis le pool - Réutilisation circulaire
*/
VideoSegment* VehicleService::acquireFromCache() {
// Implémentation simplifiée, une vraie implémentation utiliserait des opérations atomiques
for (int i = 0; i < MAX_CAMERA_SLOTS; i++) {
CameraInstance* cam = &m_camera_slots[i];
if (!cam->is_active) continue;
for (int j = 0; j < REUSABLE_FRAME_POOL; j++) {
VideoSegment* seg = cam->frame_cache[j];
if (seg && seg->reference_count == 1) {
// Seulement cette référence, réutilisable
android_atomic_inc(&seg->reference_count);
return seg;
}
}
}
// Pool plein, allocation nouvelle
return allocateSegment(FRAME_BYTE_SIZE);
}
/*============================================================================
* Thread d'Acquisition - La source de données la plus critique
*============================================================================*/
void* VehicleService::acquisitionRoutine(void* argument) {
CameraInstance* cam = (CameraInstance*)argument;
VehicleService* service = (VehicleService*)s_shared_instance;
ALOGI("Thread d'acquisition démarré pour la caméra %d", cam->camera_id);
// Définition de la priorité du thread - Thread temps réel
struct sched_param sched_params;
sched_params.sched_priority = 80;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched_params);
struct v4l2_buffer capture_buffer;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
while (service->m_service_running && cam->is_active) {
memset(&capture_buffer, 0, sizeof(capture_buffer));
memset(planes, 0, sizeof(planes));
capture_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buffer.memory = V4L2_MEMORY_MMAP;
capture_buffer.length = VIDEO_MAX_PLANES;
capture_buffer.m.planes = planes;
// File d'attente - Attente d'une trame
int operation_result = ioctl(cam->file_descriptor, VIDIOC_DQBUF, &capture_buffer);
if (operation_result < 0) {
if (errno == EAGAIN) {
usleep(1000);
continue;
}
ALOGE("Caméra %d DQBUF échoué : %s", cam->camera_id, strerror(errno));
service->m_error_counts[cam->camera_id]++;
usleep(10000);
continue;
}
// Obtention d'une trame depuis le pool
VideoSegment* new_seg = service->acquireFromCache();
if (!new_seg) {
ALOGW("Aucune trame libre, abandon");
service->m_discarded_frame_count++;
// Renvoi dans la file d'attente
ioctl(cam->file_descriptor, VIDIOC_QBUF, &capture_buffer);
continue;
}
// Remplissage des informations de la trame
new_seg->sequence_number = service->m_total_acquired_frames++;
new_seg->capture_time_ns = capture_buffer.timestamp.tv_sec * 1000000000ULL +
capture_buffer.timestamp.tv_usec * 1000ULL;
new_seg->source_camera = cam->camera_id;
new_seg->frame_width = cam->current_settings.resWidth;
new_seg->frame_height = cam->current_settings.resHeight;
new_seg->color_format = cam->current_settings.pixelFormat;
// Copie des données (ou utilisation de dma-buf pour copie zéro)
memcpy(new_seg->pixel_buffer, cam->mapped_memory[capture_buffer.index], capture_buffer.bytesused);
// Écriture dans le buffer circulaire
writeCircularBuffer(cam->history_buffer, new_seg);
// Appel de tous les clients abonnés à cette caméra
Mutex::Autolock lock(service->m_global_lock);
for (int i = 0; i < MAX_CONCURRENT_CLIENTS; i++) {
if (service->m_client_sessions[i].is_valid &&
(service->m_client_sessions[i].active_camera_mask & (1 << cam->camera_id))) {
// Rappel via Binder
Parcel data, reply;
data.writeInt32(cam->camera_id);
// En réalité, on utiliserait la mémoire partagée pour les gros volumes
service->m_client_sessions[i].callback_ref->onNewFrame(
cam->camera_id,
Array<uint8_t>(new_seg->pixel_buffer, new_seg->data_length),
new_seg->data_length,
new_seg->capture_time_ns);
}
}
// Renvoi dans la file d'attente
operation_result = ioctl(cam->file_descriptor, VIDIOC_QBUF, &capture_buffer);
if (operation_result < 0) {
ALOGE("Caméra %d QBUF échoué : %s", cam->camera_id, strerror(errno));
}
// Libération de la référence (le buffer circulaire en conserve une)
service->releaseSegment(new_seg);
}
ALOGI("Thread d'acquisition arrêté pour la caméra %d", cam->camera_id);
return NULL;
}
/*============================================================================
* Thread de Traitement IA - Inférence asynchrone
*============================================================================*/
void* VehicleService::inferenceRoutine(void* argument) {
AIModelInstance* model = (AIModelInstance*)argument;
VehicleService* service = (VehicleService*)s_shared_instance;
ALOGI("Thread IA démarré pour le modèle %d", model->model_type);
// Chargement du modèle (si non chargé)
if (!model->is_loaded) {
// Initialisation RKNN
// rknn_init(&model->nn_context, model_data, model_size, 0, NULL);
model->is_loaded = true;
}
while (service->m_service_running) {
// Sélection de la caméra source selon le type de modèle
int camera_src = (model->model_type == 1) ? 6 : // DMS utilise la caméra 6
(model->model_type == 0) ? 4 : // BSD utilise les caméras 4-5
0; // Vue 360° utilise les 0-3
CameraInstance* src_cam = &service->m_camera_slots[camera_src];
// Lecture d'un lot de trames consécutives (certains modèles en ont besoin)
VideoSegment* segment_batch[16];
int batch_size = readCircularBufferBatch(src_cam->history_buffer, segment_batch,
(model->model_type == 1) ? 16 : 1, 100);
if (batch_size == 0) {
usleep(10000);
continue;
}
// Prétraitement
// ... Redimensionnement, normalisation
// Exécution de l'inférence RKNN
uint64_t inference_start = systemTime();
// rknn_run(model->nn_context, NULL);
uint64_t inference_end = systemTime();
ALOGV("Modèle %d temps d'inférence : %lld µs", model->model_type, (inference_end - inference_start) / 1000);
// Analyse des résultats
DetectionResult outcome;
outcome.detectionType = model->model_type;
outcome.resultTimestamp = segment_batch[0]->capture_time_ns / 1000000;
// Remplissage des boîtes de détection, etc.
// Rappel aux clients
Mutex::Autolock lock(service->m_global_lock);
for (int i = 0; i < MAX_CONCURRENT_CLIENTS; i++) {
if (service->m_client_sessions[i].is_valid &&
(service->m_client_sessions[i].active_ai_mask & (1 << model->model_type))) {
service->m_client_sessions[i].callback_ref->onDetectionOutcome(outcome);
}
}
service->m_total_processed_inferences++;
// Libération des trames
for (int i = 0; i < batch_size; i++) {
service->releaseSegment(segment_batch[i]);
}
}
return NULL;
}
/*============================================================================
* Gestion des Clients - Qui utilise le service
*============================================================================*/
int VehicleService::registerNewClient(const sp<ivehicleservicecallback>& callback) {
for (int i = 0; i < MAX_CONCURRENT_CLIENTS; i++) {
if (!m_client_sessions[i].is_valid) {
m_client_sessions[i].callback_ref = callback;
m_client_sessions[i].client_pid = IPCThreadState::self()->getCallingPid();
m_client_sessions[i].client_uid = IPCThreadState::self()->getCallingUid();
m_client_sessions[i].active_camera_mask = 0;
m_client_sessions[i].active_ai_mask = 0;
m_client_sessions[i].last_heartbeat_time = systemTime() / 1000000;
m_client_sessions[i].is_valid = true;
ALOGI("Client enregistré : pid=%d uid=%d index=%d",
m_client_sessions[i].client_pid, m_client_sessions[i].client_uid, i);
return i;
}
}
return -1;
}
/*============================================================================
* Implémentation de l'Interface Publique - activateCamera
*============================================================================*/
int VehicleService::activateCamera(int camIdx, const CameraConfig& config,
const sp<ivehicleservicecallback>& callback) {
ALOGI("activateCamera : id=%d", camIdx);
// Vérification des paramètres
if (camIdx < 0 || camIdx >= MAX_CAMERA_SLOTS) {
ALOGE("ID de caméra invalide : %d", camIdx);
return INVALID_ARGUMENT;
}
// Vérification des permissions
if (!verifyPermission("android.permission.CAMERA")) {
return ACCESS_DENIED;
}
Mutex::Autolock lock(m_global_lock);
// Enregistrement du client (si première fois)
int session_idx = -1;
for (int i = 0; i < MAX_CONCURRENT_CLIENTS; i++) {
if (m_client_sessions[i].is_valid && m_client_sessions[i].callback_ref == callback) {
session_idx = i;
break;
}
}
if (session_idx < 0) {
session_idx = registerNewClient(callback);
if (session_idx < 0) {
ALOGE("Trop de clients");
return ALLOCATION_FAILED;
}
}
CameraInstance* cam = &m_camera_slots[camIdx];
// Vérification si déjà occupée
if (cam->is_active) {
// Si c'est le même client qui réouvre, retour succès
if (m_client_sessions[session_idx].active_camera_mask & (1 << camIdx)) {
ALOGW("Caméra %d déjà ouverte par le même client", camIdx);
return SUCCESS_CODE;
}
ALOGE("Caméra %d occupée", camIdx);
return CAMERA_IN_USE;
}
// Sauvegarde de la configuration
cam->current_settings = config;
// Ouverture du périphérique V4L2
int device_open_result = openVideoDevice(cam, config);
if (device_open_result < 0) {
ALOGE("Échec d'ouverture du périphérique V4L2 : %d", device_open_result);
return device_open_result;
}
cam->is_active = true;
// Enregistrement de l'ouverture par ce client
m_client_sessions[session_idx].active_camera_mask |= (1 << camIdx);
// Lancement du thread d'acquisition (si pas déjà lancé)
pthread_create(&m_acquisition_threads[camIdx], NULL, acquisitionRoutine, cam);
ALOGI("Caméra %d activée avec succès", camIdx);
return SUCCESS_CODE;
}
/**
* @brief Ouverture du périphérique V4L2 - Opération matérielle centrale
*/
int VehicleService::openVideoDevice(CameraInstance* cam, const CameraConfig& config) {
ALOGI("openVideoDevice : %s", cam->device_path);
// Ouverture du périphérique
cam->file_descriptor = open(cam->device_path, O_RDWR);
if (cam->file_descriptor < 0) {
ALOGE("échec de open %s : %s", cam->device_path, strerror(errno));
return -errno;
}
// Définition du format
struct v4l2_format image_format;
memset(&image_format, 0, sizeof(image_format));
image_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
image_format.fmt.pix.width = config.resWidth;
image_format.fmt.pix.height = config.resHeight;
image_format.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
image_format.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(cam->file_descriptor, VIDIOC_S_FMT, &image_format) < 0) {
ALOGE("VIDIOC_S_FMT échoué : %s", strerror(errno));
close(cam->file_descriptor);
cam->file_descriptor = -1;
return -errno;
}
// Définition du framerate
struct v4l2_streamparm stream_params;
memset(&stream_params, 0, sizeof(stream_params));
stream_params.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
stream_params.parm.capture.timeperframe.numerator = 1;
stream_params.parm.capture.timeperframe.denominator = config.frameRate;
ioctl(cam->file_descriptor, VIDIOC_S_PARM, &stream_params);
// Demande de buffers
struct v4l2_requestbuffers buffer_request;
memset(&buffer_request, 0, sizeof(buffer_request));
buffer_request.count = 4;
buffer_request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer_request.memory = V4L2_MEMORY_MMAP;
if (ioctl(cam->file_descriptor, VIDIOC_REQBUFS, &buffer_request) < 0) {
ALOGE("VIDIOC_REQBUFS échoué : %s", strerror(errno));
close(cam->file_descriptor);
cam->file_descriptor = -1;
return -errno;
}
cam->v4l2_buffer_count = buffer_request.count;
ALOGI("Buffers V4L2 alloués : %d", buffer_request.count);
// Interrogation et mappage des buffers
for (int i = 0; i < buffer_request.count; i++) {
struct v4l2_buffer* buffer_desc = &cam->v4l2_entries[i];
memset(buffer_desc, 0, sizeof(struct v4l2_buffer));
buffer_desc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer_desc->memory = V4L2_MEMORY_MMAP;
buffer_desc->index = i;
if (ioctl(cam->file_descriptor, VIDIOC_QUERYBUF, buffer_desc) < 0) {
ALOGE("VIDIOC_QUERYBUF échoué : %s", strerror(errno));
close(cam->file_descriptor);
cam->file_descriptor = -1;
return -errno;
}
cam->mapped_memory[i] = mmap(NULL, buffer_desc->length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
cam->file_descriptor, buffer_desc->m.offset);
if (cam->mapped_memory[i] == MAP_FAILED) {
ALOGE("mmap échoué : %s", strerror(errno));
close(cam->file_descriptor);
cam->file_descriptor = -1;
return -errno;
}
ALOGI("Buffer %d mappé à %p taille %d", i, cam->mapped_memory[i], buffer_desc->length);
}
return 0;
}
/*============================================================================
* Sortie des Informations de Débogage
*============================================================================*/
void VehicleService::writeStatusReport(int output_fd) {
dprintf(output_fd, "\n===== Rapport d'état du Service Véhicule =====\n");
dprintf(output_fd, "En fonctionnement : %s\n", m_service_running ? "oui" : "non");
dprintf(output_fd, "Trames totales : %llu\n", m_total_acquired_frames);
dprintf(output_fd, "Inférences totales : %llu\n", m_total_processed_inferences);
dprintf(output_fd, "Trames abandonnées : %u\n", m_discarded_frame_count);
dprintf(output_fd, "\n--- Caméras ---\n");
for (int i = 0; i < MAX_CAMERA_SLOTS; i++) {
if (m_camera_slots[i].is_active) {
dprintf(output_fd, "Cam %d : %dx%d %dfps erreurs=%u\n",
i, m_camera_slots[i].current_settings.resWidth, m_camera_slots[i].current_settings.resHeight,
m_camera_slots[i].current_settings.frameRate, m_error_counts[i]);
}
}
dprintf(output_fd, "\n--- Clients ---\n");
for (int i = 0; i < MAX_CONCURRENT_CLIENTS; i++) {
if (m_client_sessions[i].is_valid) {
dprintf(output_fd, "Client %d : pid=%d uid=%d caméras=0x%x ia=0x%x\n",
i, m_client_sessions[i].client_pid, m_client_sessions[i].client_uid,
m_client_sessions[i].active_camera_mask, m_client_sessions[i].active_ai_mask);
}
}
}
/*============================================================================
* Fonction Principale - Point d'entrée du Daemon
*============================================================================*/
int main(int argc, char** argv) {
ALOGI("Démarrage du service véhicule...");
// Devenir un daemon
daemon(0, 0);
// Obtenir l'instance du service
VehicleService* service = VehicleService::getInstance();
// Initialisation
status_t init_result = service->initialize();
if (init_result != OK) {
ALOGE("Échec de l'initialisation du service : %d", init_result);
return -1;
}
// Enregistrement auprès du ServiceManager
defaultServiceManager()->addService(
String16("commercial.vehicle.service"), service);
ALOGI("Service véhicule enregistré");
// Entrer dans le pool de threads Binder
IPCThreadState::self()->joinThreadPool();
return 0;
}
</ivehicleservicecallback></ivehicleservicecallback></uint8_t></ivehicleservicecallback></string16></ivehicleservicecallback></ivehicleservicecallback></ivehicleservice></ivehicleservicecallback></sys></cutils></utils></binder></binder></binder></log>
2.3 Configuration de Compilation Android.bp
// Android.bp
cc_binary {
name: "vehicle_service",
relative_install_path: "hw",
vendor: true,
srcs: [
"VehicleService.cpp",
"IVehicleService.cpp", // Généré automatiquement par Binder
],
shared_libs: [
"libbinder",
"libutils",
"libcutils",
"liblog",
"libhardware",
"libcamera_client",
"libcamera_metadata",
"libgui",
"libui",
"libdl",
],
static_libs: [
"librknnrt", // Bibliothèque statique RKNN
],
cflags: [
"-Wall",
"-Werror",
"-O2",
"-DLOG_TAG=\"VehicleService\"",
],
init_rc: ["vehicle_service.rc"],
}
// vehicle_service.rc
service vehicle_service /vendor/bin/hw/vehicle_service
class core
user system
group system camera graphics
capabilities SYS_NICE
priority -20
seclabel u:r:vehicle_service:s0
socket vehicle_service stream 0660 system system
shutdown critical
Partie 3 : Implémentation Native pour Buildroot RV1126
3.1 Conception de la Double Garde de Processus
/**
* @file daemon_core.c
* @brief Implémentation de la double garde de processus pour RV1126
*
* Pourquoi une double garde de processus ?
* - L'environnement des véhicules commerciaux est sévère, le système peut être instable.
* - Le service IA doit pouvoir se redémarrer automatiquement après un crash.
* - Mécanisme similaire à la supervision init d'Android.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys>
#include <sys>
#include <sys>
#include <sys>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <syslog.h>
/*============================================================================
* Définition des Unités de Données
*============================================================================*/
#define SOCKET_ENDPOINT "/tmp/vehicle_service.sock" // Chemin du fichier Socket
#define MAX_SOCKET_CLIENTS 8 // Nombre max de clients
#define HEARTBEAT_PERIOD_SEC 3 // Période battement cœur (secondes)
#define GUARDIAN_TIMEOUT_SEC 10 // Délai d'attente du gardien (secondes)
#define MAX_FAILURE_RESTARTS 5 // Nombre max de redémarrages
#define RESTART_COUNTER_WINDOW 60 // Fenêtre pour le compteur de redémarrages (secondes)
// Types de messages
#define MSG_ID_HEARTBEAT 1
#define MSG_ID_FRAME_PAYLOAD 2
#define MSG_ID_INFERENCE_RESULT 3
#define MSG_ID_SERVICE_COMMAND 4
#define MSG_ID_ERROR_REPORT 5
// En-tête de message (format TLV)
typedef struct {
uint32_t message_magic; // Magic : 0x56484543
uint32_t message_type; // Type de message
uint32_t payload_length; // Longueur des données
uint64_t send_timestamp; // Horodatage
uint32_t data_checksum; // Somme de contrôle
} MessageHeader;
// Message de trame (pour transmettre une image)
typedef struct {
MessageHeader header;
uint32_t camera_source;
uint32_t image_width;
uint32_t image_height;
uint32_t pixel_format;
uint64_t frame_sequence;
uint8_t image_data[0]; // Tableau à longueur variable
} FrameMessage;
// Taille maximum de message (4 Mo, pour accueillir une trame de 3.1 Mo)
#define MAX_SINGLE_MESSAGE (4 * 1024 * 1024)
/*============================================================================
* État Global
*============================================================================*/
typedef struct {
pid_t monitored_pid; // PID du processus surveillé
int server_socket_fd; // Descripteur du socket serveur
int client_socket_fds[MAX_SOCKET_CLIENTS]; // Connexions clients
int active_client_count; // Nombre de clients actifs
time_t last_valid_heartbeat; // Heure du dernier battement cœur valide
int failure_restart_count; // Compteur d'échecs/redémarrages
time_t window_start_time; // Début de la fenêtre de comptage
volatile int service_active; // Drapeau d'activité
} GuardianContext;
static GuardianContext g_guardian_ctx = {0};
/*============================================================================
* Implémentation de la Communication Socket
*============================================================================*/
/**
* @brief Crée le socket serveur
*/
int setup_server_socket() {
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socket_fd < 0) {
syslog(LOG_ERR, "échec de socket : %s", strerror(errno));
return -1;
}
struct sockaddr_un socket_address;
memset(&socket_address, 0, sizeof(socket_address));
socket_address.sun_family = AF_UNIX;
strncpy(socket_address.sun_path, SOCKET_ENDPOINT, sizeof(socket_address.sun_path) - 1);
unlink(SOCKET_ENDPOINT);
if (bind(socket_fd, (struct sockaddr*)&socket_address, sizeof(socket_address)) < 0) {
syslog(LOG_ERR, "échec de bind : %s", strerror(errno));
close(socket_fd);
return -1;
}
if (listen(socket_fd, MAX_SOCKET_CLIENTS) < 0) {
syslog(LOG_ERR, "échec de listen : %s", strerror(errno));
close(socket_fd);
return -1;
}
syslog(LOG_INFO, "Socket serveur créé : %s", SOCKET_ENDPOINT);
return socket_fd;
}
/**
* @brief Reçoit un message complet (gère la segmentation)
*
* @param fd Descripteur du socket
* @param buffer Tampon de réception
* @param buffer_capacity Capacité du tampon
* @return int Taille réellement reçue, -1 en cas d'erreur
*/
int receive_complete_message(int fd, void* buffer, size_t buffer_capacity) {
if (buffer_capacity < sizeof(MessageHeader)) {
return -1;
}
// Réception de l'en-tête
MessageHeader msg_header;
ssize_t bytes_received = recv(fd, &msg_header, sizeof(msg_header), MSG_WAITALL);
if (bytes_received != sizeof(msg_header)) {
return -1;
}
// Vérification du magic
if (msg_header.message_magic != 0x56484543) {
syslog(LOG_ERR, "Magic invalide : 0x%x", msg_header.message_magic);
return -1;
}
// Vérification de la longueur
if (msg_header.payload_length > buffer_capacity - sizeof(msg_header)) {
syslog(LOG_ERR, "Message trop volumineux : %u > %zu",
msg_header.payload_length, buffer_capacity - sizeof(msg_header));
return -1;
}
// Réception des données
char* data_ptr = (char*)buffer;
memcpy(data_ptr, &msg_header, sizeof(msg_header));
bytes_received = recv(fd, data_ptr + sizeof(msg_header), msg_header.payload_length, MSG_WAITALL);
if (bytes_received != (ssize_t)msg_header.payload_length) {
return -1;
}
return sizeof(msg_header) + msg_header.payload_length;
}
/*============================================================================
* Logique Centrale du Daemon
*============================================================================*/
/**
* @brief Processus fils - Le processus de travail réel
*/
void worker_process_main() {
syslog(LOG_INFO, "Processus de travail démarré, PID=%d", getpid());
// Initialisation du HAL caméra
if (camera_hal_initialize() < 0) {
syslog(LOG_ERR, "camera_hal_initialize échoué");
exit(1);
}
// Initialisation du moteur IA
// ...
// Création de la connexion socket vers le processus parent
int worker_socket = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un parent_address;
parent_address.sun_family = AF_UNIX;
strcpy(parent_address.sun_path, SOCKET_ENDPOINT);
while (connect(worker_socket, (struct sockaddr*)&parent_address, sizeof(parent_address)) < 0) {
syslog(LOG_WARNING, "Attente du socket parent...");
sleep(1);
}
// Préparation du battement cœur
MessageHeader heartbeat_msg;
heartbeat_msg.message_magic = 0x56484543;
heartbeat_msg.message_type = MSG_ID_HEARTBEAT;
heartbeat_msg.payload_length = 0;
heartbeat_msg.send_timestamp = time(NULL);
heartbeat_msg.data_checksum = 0;
// Boucle principale
while (1) {
// Envoi du battement cœur
send(worker_socket, &heartbeat_msg, sizeof(heartbeat_msg), 0);
// Acquisition de trames caméra
// Exécution de l'inférence IA
// Envoi des résultats
sleep(1);
}
}
/**
* @brief Processus parent - Le processus de surveillance/garde
*/
void guardian_process_main() {
syslog(LOG_INFO, "Processus gardien démarré, PID=%d", getpid());
// Création du socket
g_guardian_ctx.server_socket_fd = setup_server_socket();
if (g_guardian_ctx.server_socket_fd < 0) {
syslog(LOG_ERR, "Échec de création du socket");
exit(1);
}
g_guardian_ctx.service_active = 1;
g_guardian_ctx.failure_restart_count = 0;
g_guardian_ctx.window_start_time = time(NULL);
while (g_guardian_ctx.service_active) {
// Attente de connexions clientes
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(g_guardian_ctx.server_socket_fd, &read_set);
struct timeval select_timeout;
select_timeout.tv_sec = 1;
select_timeout.tv_usec = 0;
int select_result = select(g_guardian_ctx.server_socket_fd + 1, &read_set, NULL, NULL, &select_timeout);
if (select_result > 0 && FD_ISSET(g_guardian_ctx.server_socket_fd, &read_set)) {
// Acceptation d'une nouvelle connexion
int new_client_fd = accept(g_guardian_ctx.server_socket_fd, NULL, NULL);
if (new_client_fd >= 0 && g_guardian_ctx.active_client_count < MAX_SOCKET_CLIENTS) {
g_guardian_ctx.client_socket_fds[g_guardian_ctx.active_client_count++] = new_client_fd;
syslog(LOG_INFO, "Client connecté, total=%d", g_guardian_ctx.active_client_count);
}
}
// Vérification de l'état du processus fils
int child_status;
pid_t terminated_pid = waitpid(-1, &child_status, WNOHANG);
if (terminated_pid > 0) {
// Le processus fils est tombé
syslog(LOG_WARNING, "Processus de travail %d mort, redémarrage...", terminated_pid);
// Vérification de la fréquence des redémarrages
time_t current_time = time(NULL);
if (current_time - g_guardian_ctx.window_start_time > RESTART_COUNTER_WINDOW) {
// Fenêtre dépassée, réinitialisation du compteur
g_guardian_ctx.failure_restart_count = 0;
g_guardian_ctx.window_start_time = current_time;
}
g_guardian_ctx.failure_restart_count++;
if (g_guardian_ctx.failure_restart_count > MAX_FAILURE_RESTARTS) {
syslog(LOG_ERR, "Trop de redémarrages, abandon");
exit(1);
}
// Nouveau fork
pid_t new_worker_pid = fork();
if (new_worker_pid == 0) {
worker_process_main();
exit(0);
} else if (new_worker_pid > 0) {
g_guardian_ctx.monitored_pid = new_worker_pid;
syslog(LOG_INFO, "Nouveau processus de travail %d démarré", new_worker_pid);
}
}
// Vérification des battements cœur des clients
for (int i = 0; i < g_guardian_ctx.active_client_count; i++) {
// Réception des messages de battement cœur
char message_buffer[MAX_SINGLE_MESSAGE];
int received_length = receive_complete_message(g_guardian_ctx.client_socket_fds[i],
message_buffer, sizeof(message_buffer));
if (received_length > 0) {
MessageHeader* received_header = (MessageHeader*)message_buffer;
if (received_header->message_type == MSG_ID_HEARTBEAT) {
// Battement cœur normal
}
}
}
}
}
/**
* @brief Gestion des signaux
*/
void handle_system_signal(int signal_number) {
if (signal_number == SIGTERM || signal_number == SIGINT) {
syslog(LOG_INFO, "Signal %d reçu, arrêt en cours", signal_number);
g_guardian_ctx.service_active = 0;
// Notification au processus fils
if (g_guardian_ctx.monitored_pid > 0) {
kill(g_guardian_ctx.monitored_pid, SIGTERM);
}
}
}
/**
* @brief Fonction principale
*/
int main(int argument_count, char** argument_vector) {
// Ouverture de syslog
openlog("vehicle_daemon", LOG_PID | LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "Démarrage du daemon véhicule...");
// Configuration des gestionnaires de signaux
signal(SIGTERM, handle_system_signal);
signal(SIGINT, handle_system_signal);
signal(SIGCHLD, SIG_IGN); // Évite les processus zombies
// Premier fork
pid_t first_fork_pid = fork();
if (first_fork_pid < 0) {
syslog(LOG_ERR, "Premier fork échoué : %s", strerror(errno));
return 1;
}
if (first_fork_pid > 0) {
// Le processus parent quitte
syslog(LOG_INFO, "Parent du premier fork sortant");
return 0;
}
// Le processus fils devient chef de groupe de sessions
setsid();
// Deuxième fork
first_fork_pid = fork();
if (first_fork_pid < 0) {
syslog(LOG_ERR, "Deuxième fork échoué : %s", strerror(errno));
return 1;
}
if (first_fork_pid > 0) {
// Le parent du deuxième fork quitte
syslog(LOG_INFO, "Parent du deuxième fork sortant");
return 0;
}
// Nous sommes maintenant le vrai daemon
umask(0);
chdir("/");
// Fermeture des entrées/sorties standard
close(0);
close(1);
close(2);
// Démarrage du processus gardien
guardian_process_main();
syslog(LOG_INFO, "Daemon véhicule arrêté");
closelog();
return 0;
}
</syslog.h></time.h></errno.h></string.h></pthread.h></sys></sys></sys></sys></signal.h></unistd.h></stdlib.h></stdio.h>
3.2 Encapsulation de l'Interface HAL pour RV1126
/**
* @file camera_hal_rv1126.h
* @brief HAL caméra dédié pour RV1126
*
* Différences avec la version RK3588 :
* - RV1126 n'a que 2 ISP, supporte max 4 entrées.
* - ISP intégré de niveau sécuritaire, nécessite la configuration 3DNR/HDR.
* - Opération directe via IOCTL sur RKISP.
*/
#ifndef CAMERA_HAL_RV1126_H
#define CAMERA_HAL_RV1126_H
#include <stdint.h>
#include <linux>
#include <rk_isp.h> // En-tête spécifique au ISP Rockchip
#ifdef __cplusplus
extern "C" {
#endif
/*============================================================================
* Configuration Spécifique RV1126
*============================================================================*/
// Support maximum RV1126
#define RV1126_CAMERA_LIMIT 4
#define RV1126_ISP_COUNT 2
#define RV1126_BUFFER_MAX 4
// Paramètres de configuration ISP
typedef struct {
int exposure_microsec; // Temps d'exposition (µs)
int analog_gain; // Gain analogique (dB)
int wdr_operation_mode; // Mode WDR 0-éteint, 1-ligne, 2-trame
int wdr_contrast_ratio; // Ratio de contraste WDR (1-256)
int detail_sharpness; // Netteté des détails (0-255)
int color_saturation; // Saturation des couleurs (0-255)
int image_contrast; // Contraste de l'image (0-255)
int brightness_offset; // Décalage de luminosité (-128 à 127)
// Configuration 3DNR (spécifique RV1126)
int denoise_3d_strength; // Force de la réduction de bruit 3D (0-5)
int denoise_2d_strength; // Force de la réduction de bruit 2D (0-5)
int temporal_noise_weight; // Poids temporel du bruit
// Configuration HDR
int hdr_capture_mode; // Mode de capture HDR
int hdr_multi_frame_gains[3]; // Gains pour les trames multiples
} Rv1126IspConfiguration;
// Informations de caméra (version RV1126)
typedef struct {
int camera_identifier;
char node_path[32];
char sensor_model[32];
// Connexe à l'ISP
int isp_unit_id; // Unité ISP associée (0 ou 1)
int isp_device_fd; // Descripteur du périphérique ISP
// Paramètres actuels
uint32_t resolution_w;
uint32_t resolution_h;
uint32_t target_fps;
uint32_t color_format;
Rv1126IspConfiguration isp_settings;
// Statistiques
uint64_t processed_frames;
uint64_t missed_frames;
} Rv1126CameraDescriptor;
/*============================================================================
* Interfaces API
*============================================================================*/
/**
* @brief Initialise le système caméra RV1126
* @return 0 en cas de succès
*/
int rv1126_camera_system_init(void);
/**
* @brief Ouvre une caméra (spécifique RV1126)
* @param cam_id 0-3
* @param settings Paramètres de configuration
* @return Handle, NULL en cas d'échec
*/
void* rv1126_camera_open(int cam_id, const CameraConfig* settings);
/**
* @brief Applique les paramètres ISP (spécifique RV1126)
*/
int rv1126_camera_apply_isp(void* camera_handle, const Rv1126IspConfiguration* params);
/**
* @brief Capture une trame (retourne un fd dma-buf)
*/
int rv1126_camera_get_frame(void* camera_handle, uint64_t max_wait_ms);
#ifdef __cplusplus
}
#endif
#endif
</rk_isp.h></linux></stdint.h>
3.3 Script de Compilation Makefile
# Makefile pour le service Native RV1126
CROSS_COMPILE ?= arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
# Options de compilation
CFLAGS = -O2 -Wall -Werror -D_GNU_SOURCE
CFLAGS += -I./include
CFLAGS += -I$(SDK_ROOT)/include
CFLAGS += -I$(SDK_ROOT)/include/rknn
LDFLAGS = -L$(SDK_ROOT)/lib
LDLIBS = -lpthread -ldl -lrt -lrknnrt -lrkisp
# Fichiers source
SRC_FILES = daemon_core.c \
camera_hal_rv1126.c \
ai_processing.c \
socket_interface.c \
bsd_module.c \
dms_module.c \
utility_functions.c
OBJ_FILES = $(SRC_FILES:.c=.o)
TARGET_NAME = vehicle_daemon
# Cible par défaut
all: $(TARGET_NAME)
$(TARGET_NAME): $(OBJ_FILES)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(STRIP) $@
@echo "Compilation terminée : $(TARGET_NAME)"
@echo "Taille fichier : $$(du -h $(TARGET_NAME) | cut -f1)"
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# Cible d'installation
install: $(TARGET_NAME)
cp $(TARGET_NAME) $(ROOTFS_DEST)/usr/bin/
cp scripts/S99vehicle_daemon $(ROOTFS_DEST)/etc/init.d/
chmod +x $(ROOTFS_DEST)/etc/init.d/S99vehicle_daemon
@echo "Installé vers $(ROOTFS_DEST)"
# Nettoyage
clean:
rm -f $(OBJ_FILES) $(TARGET_NAME)
# Version de débogage (avec symboles)
debug: CFLAGS += -g -DDEBUG
debug: clean all
# Version profilage
profile: CFLAGS += -pg
profile: LDFLAGS += -pg
profile: clean all
.PHONY: all clean install debug profile
Partie 4 : Outils de Débogage et Manuel d'Opérations
4.1 Outils de Débogage pour Android Native
#!/system/bin/sh
# vehicle_debug_utils.sh - Script d'utilitaires de débogage pour le service Native Android
# Utilisation : ./vehicle_debug_utils.sh [commande]
set -e
# Définition des couleurs
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # Pas de couleur
SERVICE_PROC="vehicle_service"
SERVICE_PID=""
display_header() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Utilitaires de Débogage Vehicle v1.0 ${NC}"
echo -e "${BLUE}========================================${NC}"
}
verify_service() {
SERVICE_PID=$(pidof $SERVICE_PROC)
if [ -n "$SERVICE_PID" ]; then
echo -e "${GREEN}✓ Service $SERVICE_PROC en cours (PID: $SERVICE_PID)${NC}"
return 0
else
echo -e "${RED}✗ Service $SERVICE_PROC non actif${NC}"
return 1
fi
}
# Commande : status - Afficher l'état du service
cmd_check_status() {
display_header
verify_service
echo -e "\n${YELLOW}Détails du service :${NC}"
ps -A | grep $SERVICE_PROC
echo -e "\n${YELLOW}Services Binder :${NC}"
service list | grep commercial.vehicle
echo -e "\n${YELLOW}Utilisation mémoire :${NC}"
cat /proc/$SERVICE_PID/status | grep -E "Vm(RSS|Size|Data)"
echo -e "\n${YELLOW}Fichiers ouverts :${NC}"
ls -la /proc/$SERVICE_PID/fd/ | grep video
}
# Commande : log - Afficher les journaux en direct
cmd_view_logs() {
logcat -s VehicleService -v time
}
# Commande : cameras - Lister les périphériques caméra
cmd_list_cameras() {
display_header
echo -e "${YELLOW}Périphériques V4L2 :${NC}"
ls -la /dev/video*
echo -e "\n${YELLOW}Topologie du contrôleur média :${NC}"
media-ctl -p
echo -e "\n${YELLOW}Capacités des caméras :${NC}"
for device in /dev/video*; do
echo "--- $device ---"
v4l2-ctl -d $device --all | grep -E "Driver|Card|Width|Height|Pixel format"
done
}
# Commande : mem - Consulter l'utilisation mémoire
cmd_inspect_memory() {
display_header
echo -e "${YELLOW}Mémoire système :${NC}"
free -m
echo -e "\n${YELLOW}Mémoire ION/DMA-BUF :${NC}"
cat /sys/kernel/debug/ion/heaps/system
}
# Commande : top - Surveillance en temps réel
cmd_real_time_monitor() {
display_header
echo -e "${YELLOW}Appuyez sur Ctrl+C pour quitter${NC}\n"
while true; do
clear
echo "===== Moniteur Temps Réel Vehicle ====="
echo "Heure : $(date)"
echo ""
# Utilisation CPU
CPU_INFO=$(top -b -n 1 | grep $SERVICE_PROC)
echo "CPU : $CPU_INFO"
# Mémoire
MEM_INFO=$(cat /proc/$SERVICE_PID/status | grep VmRSS)
echo "Mémoire : $MEM_INFO"
# Nombre de threads
THREAD_COUNT=$(ls /proc/$SERVICE_PID/task/ | wc -l)
echo "Threads : $THREAD_COUNT"
# Estimation du framerate
LAST_FRAME=$(logcat -d -s VehicleService | grep "Trame capturée" | tail -1)
echo "Dernière trame : $LAST_FRAME"
sleep 2
done
}
# Commande : test - Test manuel
cmd_manual_test() {
display_header
echo -e "${YELLOW}Exécution du test manuel...${NC}"
# Appel de l'interface du service
service call commercial.vehicle.service 1 # Supposons 1 est initialize
sleep 1
# Activation de la caméra 0
service call commercial.vehicle.service 2 i32 0
sleep 2
# Capture d'une trame
service call commercial.vehicle.service 5 i32 0 i32 1000
echo -e "${GREEN}Test terminé${NC}"
}
# Commande : gdb - Attacher le débogeur
cmd_attach_debugger() {
verify_service || exit 1
echo -e "${YELLOW}Attachement de GDB au PID $SERVICE_PID...${NC}"
echo "Commandes après attachement :"
echo " bt - backtrace"
echo " info threads - tous les threads"
echo " thread apply all bt - tous les backtraces"
echo " continue - reprendre l'exécution"
echo ""
gdb -p $SERVICE_PID
}
# Commande : strace - Trace des appels système
cmd_trace_syscalls() {
verify_service || exit 1
echo -e "${YELLOW}Trace des appels système (Ctrl+C pour arrêter)...${NC}"
strace -p $SERVICE_PID -t -f -o /data/vehicle_service.strace
}
# Aide
cmd_display_help() {
display_header
echo "Commandes disponibles :"
echo " status - Afficher l'état du service"
echo " log - Voir les journaux en direct"
echo " cameras - Lister les périphériques caméra"
echo " mem - Consulter l'utilisation mémoire"
echo " top - Surveillance des performances en temps réel"
echo " test - Tester manuellement le service"
echo " gdb - Débogage GDB"
echo " strace - Trace des appels système"
echo " help - Afficher cette aide"
}
# Logique principale
case "$1" in
status) cmd_check_status ;;
log) cmd_view_logs ;;
cameras) cmd_list_cameras ;;
mem) cmd_inspect_memory ;;
top) cmd_real_time_monitor ;;
test) cmd_manual_test ;;
gdb) cmd_attach_debugger ;;
strace) cmd_trace_syscalls ;;
help) cmd_display_help ;;
*)
echo "Utilisation : $0 {status|log|cameras|mem|top|test|gdb|strace|help}"
exit 1
;;
esac
4.2 Outils de Débogage pour Buildroot RV1126
#!/bin/sh
# /etc/init.d/S99vehicle_daemon - Script de démarrage du daemon pour RV1126
PROCESS_NAME=vehicle_daemon
DAEMON_PATH=/usr/bin/$PROCESS_NAME
PID_FILE=/var/run/$PROCESS_NAME.pid
start_service() {
printf "Démarrage de $PROCESS_NAME : "
start-stop-daemon -S -b -m -p $PID_FILE -x $DAEMON_PATH
[ $? = 0 ] && echo "OK" || echo "ÉCHEC"
}
stop_service() {
printf "Arrêt de $PROCESS_NAME : "
start-stop-daemon -K -p $PID_FILE
rm -f $PID_FILE
echo "OK"
}
restart_service() {
stop_service
sleep 1
start_service
}
check_service_status() {
if [ -f $PID_FILE ]; then
PID_VAL=$(cat $PID_FILE)
if kill -0 $PID_VAL 2>/dev/null; then
echo "$PROCESS_NAME est en cours (PID: $PID_VAL)"
echo "Temps d'exécution : $(ps -o etime= -p $PID_VAL)"
echo "Mémoire : $(awk '/VmRSS/ {print $2}' /proc/$PID_VAL/status) Ko"
else
echo "$PROCESS_NAME est mort (fichier PID périmé)"
fi
else
echo "$PROCESS_NAME n'est pas en cours"
fi
}
# Commandes de débogage
debug_service() {
echo "===== Informations de Débogage Vehicle ====="
echo "Date : $(date)"
echo ""
echo "--- Processus ---"
ps | grep $PROCESS_NAME
echo ""
echo "--- Mémoire ---"
free -k
echo ""
echo "--- Caméras ---"
ls -la /dev/video*
echo ""
echo "--- Messages Noyau ---"
dmesg | tail -20 | grep -E "video|camera|isp|rkisp"
echo ""
echo "--- Journal ---"
logread | grep $PROCESS_NAME | tail -20
}
case "$1" in
start) start_service ;;
stop) stop_service ;;
restart) restart_service ;;
status) check_service_status ;;
debug) debug_service ;;
*)
echo "Utilisation : $0 {start|stop|restart|status|debug}"
exit 1
esac
exit 0
4.3 Manuel d'Opérations de Débogage GDB
# Guide de Débogage GDB pour le Service Véhicule
## 1. Configuration du Débogage GDB Distant (RV1126)
Côté machine cible (RV1126) :
# Démarrage de gdbserver
gdbserver :1234 /usr/bin/vehicle_daemon
# Ou attachement à un processus en cours
gdbserver :1234 --attach $(pidof vehicle_daemon)
Côté machine hôte (Ubuntu) :
# Compilation croisée de GDB
arm-linux-gnueabihf-gdb /chemin/vers/vehicle_daemon
(gdb) target remote 192.168.1.100:1234
(gdb) b main
(gdb) c
(gdb) bt
(gdb) info threads
(gdb) thread apply all bt
## 2. Commandes de Débogage Courantes
Voir les threads :
(gdb) info threads
Id Target Id Frame
1 Thread 0xb6f0e460 main() at daemon_core.c:120
2 Thread 0xb6d0d460 worker_process_main() at worker.c:45
* 3 Thread 0xb6b0c460 acquisition_thread() at capture.c:78
Voir la pile d'appels :
(gdb) bt full
#0 acquisition_thread (arg=0x1e5c0) at capture.c:78
cam = 0x1e5c0
buf = {index = 2, type = V4L2_BUF_TYPE_VIDEO_CAPTURE, ...}
ret = 0
Voir les variables :
(gdb) p cam->processed_frames
$1 = 1523
(gdb) p *cam
$2 = {camera_id = 0, fd = 7, resolution_w = 1920, resolution_h = 1080, ...}
Voir la mémoire :
(gdb) x/32x 0xb6f0e460 # Voir le contenu mémoire
(gdb) x/10i $pc # Voir les instructions courantes
Définir des points d'arrêt :
(gdb) b capture.c:120 if cam->processed_frames > 1000
(gdb) watch cam->missed_frames
## 3. Analyse de Performance
Utilisation de perf :
# Sur la machine cible
perf record -g -p $(pidof vehicle_daemon)
perf report -g graph
Utilisation de valgrind :
valgrind --tool=memcheck --leak-check=full /usr/bin/vehicle_daemon
## 4. Résolution de Problèmes Courants
Problème 1 : Échec du démarrage du service
# Vérifier les journaux
logread | grep vehicle_daemon
# Exécution manuelle pour voir les erreurs
/usr/bin/vehicle_daemon -d foreground
# Vérifier les permissions
ls -la /dev/video*
Problème 2 : Aucune donnée de caméra
# Vérifier V4L2
v4l2-ctl --all
# Tester la capture
v4l2-ctl --stream-mmap --stream-count=100
# Vérifier l'état de l'ISP
cat /sys/kernel/debug/rkisp/*
Problème 3 : Fuite de mémoire
# Surveiller la croissance de la mémoire
while true; do
ps -o vsz,rss -p $(pidof vehicle_daemon)
sleep 5
done
# Détection avec valgrind
valgrind --tool=memcheck --leak-check=full /usr/bin/vehicle_daemon
Problème 4 : Utilisation CPU excessive
# Voir le CPU par thread
top -H -p $(pidof vehicle_daemon)
# Analyser les points chauds
perf top -p $(pidof vehicle_daemon)
Partie 5 : Résumé des Unités de Données et des Performances
5.1 Tableau Récapitulatif des Tailles de Cache
| Type de Cache |
Taille Unit |
Quantité |
Taille Totale |
Remarques |
| Trame NV12 unique |
3.1 Mo |
1 |
3.1 Mo |
Format NV12 1920x1080 |
| Pool de trames |
3.1 Mo |
8 |
24.8 Mo |
Pré-allocation, évite les malloc fréquents |
| Buffer circulaire |
3.1 Mo |
128 |
396.8 Mo |
Stocke l'historique pour analyse IA continue |
| Buffers V4L2 |
3.1 Mo |
4 |
12.4 Mo |
Utilisés par le pilote noyau |
| Sous-total Caméra |
|
|
437.1 Mo |
Empreinte mémoire pour une seule caméra |
| Modèle IA |
Taille Entrée |
Taille Sortie |
Mémoire Travail |
Mémoire Totale |
| BSD (YOLOv5s) |
1.23 Mo |
2.82 Mo |
50 Mo |
~54 Mo |
| DMS (Léger) |
0.15 Mo |
4 Ko |
20 Mo |
~20.2 Mo |
| Assemblage Vue 360° |
2.46 Mo |
3.1 Mo |
100 Mo |
~105.6 Mo |
| Sous-total IA |
|
|
|
~180 Mo |
| Autre |
Taille |
Remarques |
| Segment de code |
~5 Mo |
Fichier exécutable |
| Espace de pile |
8 Mo |
2 Mo par thread × 4 threads |
| Buffer Binder |
1 Mo |
Communication IPC |
| Total |
~631 Mo |
Suffisant sur RK3588 avec 8 Go de RAM |
5.2 Tableau Récapitulatif des Indicateurs de Performance
| Indicateur |
RK3588 (Android) |
RV1126 (Buildroot) |
Exigence |
| Nombre de caméras |
8 voies @30fps |
4 voies @30fps |
Satisfait |
| Latence acquisition trame |
<10ms |
<15ms |
Satisfait |
| Latence inférence IA (BSD) |
25ms |
35ms |
<50ms |
| Latence inférence IA (DMS) |
15ms |
20ms |
<30ms |
| Latence bout-en-bout affichage |
85ms |
95ms |
<100ms |
| Charge CPU |
<40% |
<30% |
Satisfait |
| Empreinte mémoire |
631 Mo |
256 Mo |
Satisfait |
L'architecture Native du système de cabine intelligente pour véhicules commerciaux a été conçue pour résoudre les problèmes fondamentaux de mesurabilité des données, de compréhensibilité structurelle et d'opérabilité du débogage. Cette solution technique, validée sur les plateformes RK3588 et RV1126, permet de supporter des fonctionnalités critiques telles que la vision à 360°, la surveillance des angles morts (BSD) et le monitoring du conducteur (DMS), répondant aux exigences réglementaires GB/T 808, GB/T 1078 et ECE R46.