Adaptation de la couche HAL pour un système de cockpit intelligent de véhicules commerciaux avec vision par IA

Vue d'ensemble du système

L'architecture d'un cockpit intelligant pour véhicules commerciaux repose sur une séparation modulaire des couches logicielles, permettant une indépendance entre les composants matériels et applicatifs. Cette aproche facilite la maintenance et l'évolution du système, en isolant les modifications apportées aux capteurs ou aux modèles d'intelligence artificielle.

/**
 * Architecture système pour un cockpit intelligent de véhicule commercial
 * Conception modulaire inspirée des principes de séparation des préoccupations.
 * 
 * Niveaux:
 * - Couche d'interaction utilisateur (écrans, interfaces)
 * - Couche applicative (MVVM pour les vues et logique métier)
 * - Couche de services (gestion des caméras, IA, données véhicule)
 * - Couche d'abstraction matérielle (HAL pour accès aux capteurs)
 * - Couche noyau (pilotes Linux, V4L2, DMA-BUF)
 * - Couche matérielle (SoC RK3588, caméras via GMSL)
 */

Gestion des caméras et synchronisation

Pour un système multi-caméras utilisé dans la vision à 360 degrés ou la détection d'angles morts, une synchronisation matérielle précise est essentielle. Les caméras sont regroupées fonctionnellement, par exemple un groupe pour la vision panoramique nécessitant une acquisition simultanée, et un autre pour la surveillance de l'habitacle. Cette organisation optimise l'utilisation des ressources ISP et assure la cohérence des données.

/**
 * Configuration type de la topologie des caméras dans un véhicule commercial.
 * Les caméras sont connectées via des interfaces GMSL pour une transmission longue distance.
 * Le regroupement permet de prioriser les flux en fonction des besoins de traitement.
 */
typedef struct {
    int group_id;                  // Identifiant du groupe
    int camera_indices[4];         // Index des caméras dans le groupe
    int sync_required;             // Nécessite une synchronisation matérielle
    int isp_assignment;            // Assignation au processeur d'image
} CameraGroupConfig;

Conception de l'arbre des périphériques (Device Tree)

L'arbre des périphériques décrit la configuration matérielle au noyau Linux, incluant les contrôleurs GMSL, les interfaces MIPI CSI et les capteurs. Une description exhaustive permet au pilote de configurer correctement les chemins de données et les paramètres de synchronisation. Des éléments comme les séquences d'alimentation et les spécifications des lentilles sont inclus pour garantir une initialisation fiable.

/*
 * Extrait simplifié d'un arbre des périphériques pour un système multi-caméras.
 * Description des contrôleurs de désérialisation GMSL et des nœuds de capteurs.
 */
/ {
    gmsl_deserializer: max9296@48 {
        compatible = "maxim,max9296";
        reg = <0x48>;
        maxim,serdes-mode = <2>;
        ports {
            port@0 {
                endpoint {
                    remote-endpoint = <&sensor_front_out>;
                };
            };
            // Autres ports pour caméras supplémentaires
        };
    };
    
    sensor_front: imx512@1a {
        compatible = "sony,imx512";
        reg = <0x1a>;
        clocks = <&cru CLK_MIPI_CAM0>;
        port {
            endpoint {
                remote-endpoint = <&gmsl_deserializer_in>;
                data-lanes = <1 2 3 4>;
            };
        };
    };
};

Architecture de gestion du cache

Pour optimiser le traitement des flux vidéo, un système de cache multi-niveaux est implémenté. Le premier niveau stocke la frame en cours de traitement pour un accès rapide, tandis que des niveaux inférieurs conservent des séquences historiques pour les algorithmes d'IA nécessitant un contexte temporel. Une stratégie de cache circulaire évite les fuites de mémoire et assure un débit constant.

/**
 * Structure de gestion du cache pour les flux vidéo.
 * Implémente un cache circulaire pour stocker les frames récentes.
 */
typedef struct {
    FrameData **buffer;           // Tableau de pointeurs vers les données de frame
    uint32_t capacity;            // Capacité du cache (doit être une puissance de 2)
    uint32_t mask;                // Masque pour l'indexation rapide
    volatile uint32_t read_pos;   // Position de lecture
    volatile uint32_t write_pos;  // Position d'écriture
    pthread_mutex_t lock;         // Verrou pour la synchronisation
} VideoCache;

// Initialisation d'un cache circulaire
VideoCache* create_video_cache(uint32_t size) {
    if (size == 0 || (size & (size - 1)) != 0) return NULL;
    VideoCache *cache = malloc(sizeof(VideoCache));
    cache->buffer = calloc(size, sizeof(FrameData*));
    cache->capacity = size;
    cache->mask = size - 1;
    cache->read_pos = 0;
    cache->write_pos = 0;
    pthread_mutex_init(&cache->lock, NULL);
    return cache;
}

// Écriture dans le cache (producteur)
int write_to_cache(VideoCache *cache, FrameData *frame) {
    pthread_mutex_lock(&cache->lock);
    uint32_t next_write = (cache->write_pos + 1) & cache->mask;
    if (next_write == cache->read_pos) {
        // Cache plein, écraser la frame la plus ancienne
        cache->read_pos = (cache->read_pos + 1) & cache->mask;
    }
    if (cache->buffer[cache->write_pos]) {
        free_frame(cache->buffer[cache->write_pos]);
    }
    cache->buffer[cache->write_pos] = frame;
    frame->ref_count++;
    cache->write_pos = next_write;
    pthread_mutex_unlock(&cache->lock);
    return 0;
}

// Lecture depuis le cache (consommateur)
FrameData* read_from_cache(VideoCache *cache) {
    pthread_mutex_lock(&cache->lock);
    if (cache->read_pos == cache->write_pos) {
        pthread_mutex_unlock(&cache->lock);
        return NULL; // Cache vide
    }
    FrameData *frame = cache->buffer[cache->read_pos];
    cache->buffer[cache->read_pos] = NULL;
    cache->read_pos = (cache->read_pos + 1) & cache->mask;
    frame->ref_count++;
    pthread_mutex_unlock(&cache->lock);
    return frame;
}

Adaptation de la couche HAL pour Android

Dans un environnement Android (AAOS), la couche HAL fournit une interface standardisée pour l'accès aux caméras. Elle implémente les fonctions d'ouverture, de configuration des flux et de capture, en utilisant les mécanismes de mémoire partagée du framework. Le HAL encapsule la complexité des pilotes V4L2 et expose des métadonnées enrichies avec les résultats de détection IA.

/**
 * Structure de base pour un périphérique caméra dans le HAL Android.
 * Hérite de camera_device_t et ajoute des données spécifiques au véhicule.
 */
typedef struct {
    camera_device_t base;
    int cam_index;                 // Index de la caméra
    int v4l2_fd;                   // Descripteur de fichier V4L2
    VideoCache *frame_cache;       // Cache interne pour les frames
    pthread_t capture_thread;      // Thread de capture
    volatile int streaming;        // État de l'acquisition
} VehicleCameraDevice;

// Implémentation de la fonction d'ouverture dans le HAL
static int vehicle_camera_open(const hw_module_t *module, const char *id, hw_device_t **device) {
    int cam_id = atoi(id);
    VehicleCameraDevice *dev = calloc(1, sizeof(VehicleCameraDevice));
    dev->base.common.tag = HARDWARE_DEVICE_TAG;
    dev->base.common.module = (hw_module_t *)module;
    dev->base.ops = &vehicle_camera_ops;
    dev->cam_index = cam_id;
    
    char dev_path[32];
    snprintf(dev_path, sizeof(dev_path), "/dev/video%d", cam_id);
    dev->v4l2_fd = open(dev_path, O_RDWR);
    if (dev->v4l2_fd < 0) {
        free(dev);
        return -errno;
    }
    
    dev->frame_cache = create_video_cache(128); // Cache pour 128 frames
    dev->streaming = 0;
    
    *device = &dev->base.common;
    return 0;
}

// Fonction de capture appelée par le framework
static int vehicle_camera_process_request(camera_device_t *dev, camera_capture_request_t *request) {
    VehicleCameraDevice *cam_dev = (VehicleCameraDevice *)dev;
    FrameData *frame = read_from_cache(cam_dev->frame_cache);
    if (!frame) {
        // Aucune frame disponible, retourner une erreur
        return -EAGAIN;
    }
    
    // Copier les données dans le buffer fourni par le framework
    memcpy(request->output_buffers[0].data, frame->data, frame->size);
    frame->ref_count--;
    free_frame_if_unused(frame);
    
    // Remplir les métadonnées avec le timestamp et les résultats IA
    add_timestamp_metadata(request->settings, frame->timestamp_ns);
    return 0;
}

Adaptation de la couche HAL pour Linux

Pour les systèmes Linux temps réel (comme Buildroot), le HAL fournit une API simplifiée de type Unix (open/close/read). Il supporte à la fois la capture synchrone et asynchrone via des callbacks. Une attention particulière est portée à l'exportation des descripteurs DMA-BUF pour permettre un partage de mémoire sans copie avec les accélérateurs matériels comme les NPU.

/**
 * API du HAL pour systèmes Linux, conçue pour une intégration facile avec des algorithmes IA.
 */
typedef struct {
    int device_fd;                 // Descripteur du périphérique V4L2
    VideoCache *local_cache;       // Cache local pour les frames
    void (*frame_callback)(void *ctx, FrameData *frame); // Callback optionnel
    void *callback_ctx;            // Contexte pour le callback
} VehicleCameraHandle;

// Ouverture de la caméra
VehicleCameraHandle* vehicle_camera_init(int cam_id, int mode) {
    VehicleCameraHandle *handle = calloc(1, sizeof(VehicleCameraHandle));
    char path[32];
    snprintf(path, sizeof(path), "/dev/video%d", cam_id);
    handle->device_fd = open(path, O_RDWR);
    if (handle->device_fd < 0) {
        free(handle);
        return NULL;
    }
    handle->local_cache = create_video_cache(mode == 0 ? 4 : 128); // Taille selon le mode
    return handle;
}

// Démarrage de l'acquisition
int vehicle_camera_start(VehicleCameraHandle *handle) {
    // Configurer le format V4L2 et allouer les buffers
    // Lancer un thread de capture qui remplit le cache
    pthread_t thread;
    pthread_create(&thread, NULL, capture_loop, handle);
    pthread_detach(thread);
    return 0;
}

// Capture synchrone d'une frame
FrameData* vehicle_camera_capture_frame(VehicleCameraHandle *handle, int timeout_ms) {
    return read_from_cache(handle->local_cache); // Bloquant jusqu'à disponibilité
}

// Exportation d'un DMA-BUF pour un partage zero-copy
int vehicle_camera_export_dmabuf(FrameData *frame) {
    if (frame->dma_fd < 0) {
        // Obtenir le descripteur DMA-BUF via un ioctl V4L2
        struct v4l2_exportbuffer expbuf = {0};
        expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        expbuf.plane = 0;
        if (ioctl(frame->v4l2_index, VIDIOC_EXPBUF, &expbuf) == 0) {
            frame->dma_fd = expbuf.fd;
        }
    }
    return frame->dma_fd;
}

Intégration des algorithmes d'IA avec le cache

Les algorithmes d'IA ont des besoins variés en termes de données d'entrée. La détection d'objets peut fonctionner avec une seule frame, tandis que le suivi nécessite une séquence de frames consécutives. Un adaptateur de cache spécifique est conçu pour fournir des vues optimisées à chaque algorithme, comme une fenêtre glissante pour l'analyse temporelle ou des groupes synchronisés pour la vision panoramique.

/**
 * Adaptateur pour connecter les algorithmes d'IA au système de cache vidéo.
 * Ajuste la stratégie de cache en fonction des besoins de l'algorithme.
 */
typedef struct {
    VideoCache *source_cache;      // Cache source partagé
    int required_frames;           // Nombre de frames nécessaires
    int use_sliding_window;        // Utiliser une fenêtre glissante
    FrameData **window_buffer;     // Buffer pour la fenêtre glissante
    int window_size;               // Taille de la fenêtre
    pthread_mutex_t adapter_lock;  // Verrou pour l'adaptateur
} AICacheAdapter;

// Initialisation d'un adaptateur pour un algorithme spécifique
AICacheAdapter* create_ai_adapter(VideoCache *cache, const char *algo_type) {
    AICacheAdapter *adapter = calloc(1, sizeof(AICacheAdapter));
    adapter->source_cache = cache;
    pthread_mutex_init(&adapter->adapter_lock, NULL);
    
    if (strcmp(algo_type, "object_tracking") == 0) {
        adapter->required_frames = 5;
        adapter->use_sliding_window = 1;
        adapter->window_size = 10;
        adapter->window_buffer = calloc(adapter->window_size, sizeof(FrameData*));
    } else { // Détection unique
        adapter->required_frames = 1;
        adapter->use_sliding_window = 0;
    }
    return adapter;
}

// Obtention de frames pour l'IA (mode fenêtre glissante)
int ai_adapter_get_frames(AICacheAdapter *adapter, FrameData **out_frames, int max_frames) {
    pthread_mutex_lock(&adapter->adapter_lock);
    int count = 0;
    if (adapter->use_sliding_window) {
        // Lire les frames du cache et maintenir une fenêtre
        while (count < adapter->required_frames && count < max_frames) {
            FrameData *frame = read_from_cache(adapter->source_cache);
            if (!frame) break;
            adapter->window_buffer[count % adapter->window_size] = frame;
            out_frames[count] = frame;
            count++;
        }
    } else {
        out_frames[0] = read_from_cache(adapter->source_cache);
        if (out_frames[0]) count = 1;
    }
    pthread_mutex_unlock(&adapter->adapter_lock);
    return count;
}

Étiquettes: RK3588 GMSL V4L2 HAL Android HAL Linux

Publié le 9 juillet à 20h51