Architecture et Débogage de la Couche Native pour Systèmes de Cabine Intelligente de Véhicules Commerciaux


/**
 * @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.

Étiquettes: AIDL Binder IPC V4L2 RKNN RKISP

Publié le 27 juin à 22h24