Android contient une couche d'abstraction matérielle (HAL) HIDL pour l'automobile qui permet de capturer et d'afficher des images très tôt dans le processus de démarrage d'Android et qui continue de fonctionner pendant toute la durée de vie du système. Le HAL inclut la pile EVS (Exterior View System) et est généralement utilisé pour la prise en charge des caméras de rétroaction et des affichages en vue surround dans les véhicules équipés de systèmes d'infoloisirs en véhicule (IVI) basés sur Android. L'EVS permet également d'implémenter des fonctionnalités avancées dans les applications utilisateur.
Android inclut également une interface de pilote de capture et d'affichage spécifique à l'EVS (dans /hardware/interfaces/automotive/evs/1.0
). Bien qu'il soit possible de créer une application de caméra de recul en plus des services d'appareil photo et d'affichage Android existants, une telle application s'exécuterait probablement trop tard dans le processus de démarrage d'Android. L'utilisation d'un HAL dédié permet d'obtenir une interface simplifiée et indique clairement ce qu'un OEM doit implémenter pour prendre en charge la pile EVS.
Composants système
L'EVS comprend les composants système suivants:
Figure 1 : Présentation des composants du système EVS
Application EVS
Un exemple d'application EVS C++ (/packages/services/Car/evs/app
) sert d'implémentation de référence. Cette application se charge de demander des images vidéo à EVS Manager et de renvoyer les images terminées pour les afficher à nouveau.
Il doit être démarré par init dès que l'EVS et le service de voiture sont disponibles, ciblé dans les deux (2) secondes suivant la mise sous tension. Les OEM peuvent modifier ou remplacer l'application EVS selon leurs besoins.
EVS Manager
Le Gestionnaire EVS (/packages/services/Car/evs/manager
) fournit les éléments de base dont une application EVS a besoin pour implémenter tout ce qui va d'un simple affichage de caméra de recul à un rendu multicaméra à 6 degrés de liberté. Son interface est présentée via HIDL et est conçue pour accepter plusieurs clients simultanés.
D'autres applications et services (en particulier le service de voiture) peuvent interroger l'état du Gestionnaire EVS pour savoir quand le système EVS est actif.
Interface HIDL de l'EVS
Le système EVS, à la fois la caméra et les éléments d'affichage, est défini dans le package android.hardware.automotive.evs
. Un exemple d'implémentation qui exécute l'interface (génère des images de test synthétiques et valide que les images effectuent un aller-retour) est fourni dans /hardware/interfaces/automotive/evs/1.0/default
.
L'OEM est responsable de l'implémentation de l'API exprimée par les fichiers .hal dans /hardware/interfaces/automotive/evs
. Ces implémentations sont chargées de configurer et de collecter les données des caméras physiques, puis de les transmettre via des tampons de mémoire partagée reconnaissables par Gralloc. Le côté affichage de l'implémentation est chargé de fournir un tampon de mémoire partagée pouvant être rempli par l'application (généralement via le rendu EGL) et de présenter les frames terminés de préférence à tout autre élément pouvant apparaître sur l'écran physique. Les implémentations de l'interface EVS par les fournisseurs peuvent être stockées sous /vendor/… /device/…
ou hardware/…
(par exemple,
/hardware/[vendor]/[platform]/evs
).
Pilotes de kernel
Un périphérique compatible avec la pile EVS nécessite des pilotes de noyau. Au lieu de créer de nouveaux pilotes, les OEM peuvent prendre en charge les fonctionnalités requises par l'EVS via les pilotes matériels existants de l'appareil photo et/ou de l'écran. La réutilisation des pilotes peut être avantageuse, en particulier pour les pilotes d'affichage, où la présentation des images peut nécessiter une coordination avec d'autres threads actifs. Android 8.0 inclut un exemple de pilote basé sur v4l2 (dans packages/services/Car/evs/sampleDriver
) qui dépend du kernel pour la prise en charge de v4l2 et de SurfaceFlinger pour présenter l'image de sortie.
Description de l'interface matérielle EVS
Cette section décrit le HAL. Les fournisseurs doivent fournir des implémentations de cette API adaptées à leur matériel.
IEvsEnumerator
Cet objet est chargé d'énumérer le matériel EVS disponible dans le système (une ou plusieurs caméras et un seul appareil d'affichage).
getCameraList() generates (vec<CameraDesc> cameras);
Renvoie un vecteur contenant les descriptions de toutes les caméras du système. On suppose que l'ensemble des caméras est fixe et connu au démarrage. Pour en savoir plus sur les descriptions des caméras, consultez CameraDesc
.
openCamera(string camera_id) generates (IEvsCamera camera);
Obtient un objet d'interface utilisé pour interagir avec une caméra spécifique identifiée par la chaîne unique camera_id. Renvoie une valeur NULL en cas d'échec.
Les tentatives de rouvrir une caméra déjà ouverte ne peuvent pas échouer. Pour éviter les conditions de course associées au démarrage et à l'arrêt de l'application, la réouverture d'une caméra doit arrêter l'instance précédente afin que la nouvelle requête puisse être traitée. Une instance de caméra qui a été préemptée de cette manière doit être mise dans un état inactif, en attente de destruction finale et répondre à toute requête visant à affecter l'état de la caméra avec un code de retour OWNERSHIP_LOST
.
closeCamera(IEvsCamera camera);
Libère l'interface IEvsCamera (et est l'opposé de l'appel openCamera()
). Vous devez arrêter le flux vidéo de la caméra en appelant stopVideoStream()
avant d'appeler closeCamera
.
openDisplay() generates (IEvsDisplay display);
Obtient un objet d'interface utilisé pour interagir exclusivement avec l'écran EVS du système. Un seul client peut détenir une instance fonctionnelle d'IEvsDisplay à la fois. Comme pour le comportement d'ouverture agressif décrit dans openCamera
, un nouvel objet IEvsDisplay peut être créé à tout moment et désactiver toutes les instances précédentes. Les instances non validées continuent d'exister et de répondre aux appels de fonction de leurs propriétaires, mais ne doivent effectuer aucune opération de mutation lorsqu'elles sont inactives. À terme, l'application cliente doit remarquer les codes de retour d'erreur OWNERSHIP_LOST
, fermer et libérer l'interface inactive.
closeDisplay(IEvsDisplay display);
Libère l'interface IEvsDisplay (et est l'opposé de l'appel openDisplay()
). Les tampons en attente reçus via des appels getTargetBuffer()
doivent être renvoyés à l'écran avant de le fermer.
getDisplayState() generates (DisplayState state);
Récupère l'état actuel de l'écran. L'implémentation HAL doit indiquer l'état actuel réel, qui peut différer de l'état le plus récemment demandé.
La logique responsable de la modification des états d'affichage doit exister au-dessus de la couche de l'appareil. Il est donc déconseillé que l'implémentation du HAL modifie spontanément les états d'affichage. Si l'écran n'est actuellement détenu par aucun client (par un appel à openDisplay), cette fonction renvoie NOT_OPEN
. Sinon, elle indique l'état actuel de l'écran EVS (voir API IEvsDisplay).
struct CameraDesc { string camera_id; int32 vendor_flags; // Opaque value }
camera_id
: chaîne identifiant de manière unique une caméra donnée. Il peut s'agir du nom de l'appareil du kernel ou d'un nom donné à l'appareil, tel que rearview (vue arrière). La valeur de cette chaîne est choisie par l'implémentation HAL et utilisée de manière opaque par la pile ci-dessus.vendor_flags
: méthode permettant de transmettre des informations de caméra spécialisées de manière opaque du pilote à une application EVS personnalisée. Elle est transmise sans interprétation du pilote à l'application EVS, qui est libre de l'ignorer.
IEvsCamera
Cet objet représente une seule caméra et constitue l'interface principale de capture d'images.
getCameraInfo() generates (CameraDesc info);
Renvoie l'CameraDesc
de cette caméra.
setMaxFramesInFlight(int32 bufferCount) generates (EvsResult result);
Indique la profondeur de la chaîne de tampons que la caméra doit prendre en charge. Le client d'IEvsCamera peut détenir jusqu'à ce nombre de frames simultanément. Si autant de trames ont été transmises au récepteur sans être renvoyées par doneWithFrame
, le flux saute des trames jusqu'à ce qu'une mémoire tampon soit renvoyée pour être réutilisée. Il est possible de recevoir cet appel à tout moment, même lorsque des flux sont déjà en cours d'exécution. Dans ce cas, des tampons doivent être ajoutés ou supprimés de la chaîne selon les besoins. Si aucun appel n'est effectué à ce point d'entrée, l'IEvsCamera prend en charge au moins un frame par défaut, avec plus d'acceptabilité.
Si le bufferCount demandé ne peut pas être accepté, la fonction renvoie BUFFER_NOT_AVAILABLE
ou un autre code d'erreur approprié. Dans ce cas, le système continue de fonctionner avec la valeur précédemment définie.
startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);
Demande la diffusion d'images EVS de cette caméra. IEvsCameraStream commence à recevoir des appels périodiques avec de nouveaux frames d'image jusqu'à ce que stopVideoStream()
soit appelé. Les frames doivent commencer à être diffusées dans les 500 ms suivant l'appel startVideoStream
et, une fois démarrées, doivent être générées à au moins 10 FPS. Le temps nécessaire pour démarrer le flux vidéo est pris en compte dans le délai de démarrage de la caméra arrière. Si le flux n'est pas démarré, un code d'erreur doit être renvoyé. Sinon, la valeur "OK" est renvoyée.
oneway doneWithFrame(BufferDesc buffer);
Renvoie une image envoyée par IEvsCameraStream. Lorsque vous avez terminé d'utiliser un frame envoyé à l'interface IEvsCameraStream, vous devez le renvoyer à IEvsCamera pour le réutiliser. Un petit nombre fini de tampons est disponible (peut-être aussi petit qu'un seul), et si l'approvisionnement est épuisé, aucun autre frame n'est envoyé tant qu'un tampon n'est pas renvoyé, ce qui peut entraîner des frames ignorés (un tampon avec un gestionnaire nul indique la fin d'un flux et n'a pas besoin d'être renvoyé via cette fonction). Renvoie "OK" en cas de réussite, ou le code d'erreur approprié pouvant inclure INVALID_ARG
ou BUFFER_NOT_AVAILABLE
.
stopVideoStream();
Arrête la diffusion des images de l'appareil photo EVS. Étant donné que la diffusion est asynchrone, les trames peuvent continuer à arriver pendant un certain temps après le retour de cet appel. Chaque frame doit être renvoyé jusqu'à ce que la fermeture du flux soit signalée à IEvsCameraStream. Il est légal d'appeler stopVideoStream
sur un flux qui a déjà été arrêté ou qui n'a jamais été démarré, auquel cas il est ignoré.
getExtendedInfo(int32 opaqueIdentifier) generates (int32 value);
Demande des informations spécifiques au pilote à l'implémentation HAL. Les valeurs autorisées pour opaqueIdentifier
sont spécifiques au pilote, mais aucune valeur transmise ne peut provoquer le plantage du pilote. Le pilote doit renvoyer 0 pour toute valeur opaqueIdentifier
non reconnue.
setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);
Envoie une valeur spécifique au pilote à l'implémentation HAL. Cette extension n'est fournie que pour faciliter les extensions spécifiques au véhicule. Aucune implémentation HAL ne doit nécessiter cet appel pour fonctionner dans un état par défaut. Si le pilote reconnaît et accepte les valeurs, la valeur "OK" doit être renvoyée. Sinon, INVALID_ARG
ou un autre code d'erreur représentatif doit être renvoyé.
struct BufferDesc { uint32 width; // Units of pixels uint32 height; // Units of pixels uint32 stride; // Units of pixels uint32 pixelSize; // Size of single pixel in bytes uint32 format; // May contain values from android_pixel_format_t uint32 usage; // May contain values from Gralloc.h uint32 bufferId; // Opaque value handle memHandle; // gralloc memory buffer handle }
Décrit une image transmise via l'API. Le pilote HAL est chargé de remplir cette structure pour décrire le tampon d'image, et le client HAL doit traiter cette structure en lecture seule. Les champs contiennent suffisamment d'informations pour permettre au client de reconstruire un objet ANativeWindowBuffer
, ce qui peut être nécessaire pour utiliser l'image avec EGL via l'extension eglCreateImageKHR()
.
width
: largeur en pixels de l'image présentée.height
: hauteur en pixels de l'image présentée.stride
: nombre de pixels que chaque ligne occupe réellement en mémoire, en tenant compte de tout espace réservé pour l'alignement des lignes. Exprimé en pixels pour correspondre à la convention adoptée par gralloc pour ses descriptions de tampons.pixelSize
: nombre d'octets occupés par chaque pixel, ce qui permet de calculer la taille en octets nécessaire pour passer d'une ligne à l'autre dans l'image (stride
en octets =stride
en pixels *pixelSize
).format
: format de pixel utilisé par l'image. Le format fourni doit être compatible avec l'implémentation OpenGL de la plate-forme. Pour réussir les tests de compatibilité, il est préférable d'utiliserHAL_PIXEL_FORMAT_YCRCB_420_SP
pour l'appareil photo, etRGBA
ouBGRA
pour l'affichage.usage
: options d'utilisation définies par l'implémentation HAL. Les clients HAL doivent transmettre ces éléments sans modification (pour en savoir plus, consultez les indicateurs associés àGralloc.h
).bufferId
: valeur unique spécifiée par l'implémentation HAL pour permettre la reconnaissance d'un tampon après un aller-retour via les API HAL. La valeur stockée dans ce champ peut être choisie arbitrairement par l'implémentation HAL.memHandle
. Poignée du tampon de mémoire sous-jacent contenant les données d'image. L'implémentation du HAL peut choisir de stocker ici un gestionnaire de tampon Gralloc.
IEvsCameraStream
Le client implémente cette interface pour recevoir des envois de frames vidéo asynchrones.
deliverFrame(BufferDesc buffer);
Réçoit des appels de la part du HAL chaque fois qu'un frame vidéo est prêt à être inspecté.
Les poignées de tampon reçues par cette méthode doivent être renvoyées via des appels à IEvsCamera::doneWithFrame()
. Lorsque le flux vidéo est arrêté via un appel à IEvsCamera::stopVideoStream()
, ce rappel peut se poursuivre à mesure que le pipeline se vide. Chaque frame doit toujours être renvoyé. Lorsque le dernier frame du flux a été diffusé, un bufferHandle NULL est diffusé, ce qui signifie que le flux est terminé et qu'aucune autre diffusion de frame ne se produit. Le bufferHandle NULL lui-même n'a pas besoin d'être renvoyé via doneWithFrame()
, mais tous les autres handles doivent être renvoyés.
Bien que les formats de tampon propriétaires soient techniquement possibles, les tests de compatibilité nécessitent que le tampon soit dans l'un des quatre formats compatibles : NV21 (YCrCb 4:2:0 semi-plan), YV12 (YCrCb 4:2:0 Planar), YUYV (YCrCb 4:2:2 entrelacé), RVBA (32 bits R:G:B:G:x). Le format sélectionné doit être une source de texture GL valide dans l'implémentation GLES de la plate-forme.
L'application ne doit pas s'appuyer sur une correspondance entre le champ bufferId
et le memHandle
dans la structure BufferDesc
. Les valeurs bufferId
sont essentiellement privées pour l'implémentation du pilote HAL et peuvent les utiliser (et les réutiliser) comme bon lui semble.
IEvsDisplay
Cet objet représente l'écran Evs, contrôle son état et gère la présentation réelle des images.
getDisplayInfo() generates (DisplayDesc info);
Renvoie des informations de base sur l'affichage EVS fourni par le système (voir DisplayDesc).
setDisplayState(DisplayState state) generates (EvsResult result);
Définit l'état d'affichage. Les clients peuvent définir l'état d'affichage pour exprimer l'état souhaité. L'implémentation HAL doit accepter de manière appropriée une requête pour n'importe quel état, même si la réponse consiste à ignorer la requête.
Lors de l'initialisation, l'écran est défini pour démarrer à l'état NOT_VISIBLE
, après quoi le client doit demander l'état VISIBLE_ON_NEXT_FRAME
et commencer à fournir des vidéos. Lorsque l'écran n'est plus nécessaire, le client doit demander l'état NOT_VISIBLE
après avoir transmis la dernière image vidéo.
Vous pouvez demander n'importe quel état à tout moment. Si l'écran est déjà visible, il doit le rester s'il est défini sur VISIBLE_ON_NEXT_FRAME
. Renvoie toujours OK, sauf si l'état demandé est une valeur d'énumération non reconnue, auquel cas INVALID_ARG
est renvoyé.
getDisplayState() generates (DisplayState state);
Récupère l'état de l'écran. L'implémentation de HAL doit indiquer l'état actuel réel, qui peut différer de l'état le plus récemment demandé. La logique chargée de modifier les états d'affichage doit se trouver au-dessus de la couche de l'appareil. Il est donc déconseillé que l'implémentation du HAL modifie spontanément les états d'affichage.
getTargetBuffer() generates (handle bufferHandle);
Renvoie un handle vers un tampon de frame associé à l'écran. Ce tampon peut être verrouillé et écrit par le logiciel et/ou GL. Ce tampon doit être renvoyé via un appel à returnTargetBufferForDisplay()
, même si l'écran n'est plus visible.
Bien que les formats de tampon propriétaires soient techniquement possibles, les tests de compatibilité exigent que le tampon soit dans l'un des quatre formats compatibles: NV21 (YCrCb 4:2:0 semi-planaire), YV12 (YCrCb 4:2:0 planaire), YUYV (YCrCb 4:2:2 entrelacé), RGBA (32 bits R:G:B:x), BGRA (32 bits B:G:R:x). Le format sélectionné doit être une cible de rendu GL valide sur l'implémentation GLES de la plate-forme.
En cas d'erreur, un tampon avec un gestionnaire nul est renvoyé, mais un tel tampon n'a pas besoin d'être renvoyé à returnTargetBufferForDisplay
.
returnTargetBufferForDisplay(handle bufferHandle) generates (EvsResult result);
Indique à l'écran que le tampon est prêt à être affiché. Seuls les tampons récupérés via un appel à getTargetBuffer()
sont valides pour cet appel, et le contenu de BufferDesc
ne peut pas être modifié par l'application cliente. Après cet appel, le tampon n'est plus valide pour l'utilisation par le client. En cas de réussite, renvoie "OK", ou un code d'erreur approprié peut inclure INVALID_ARG
ou BUFFER_NOT_AVAILABLE
.
struct DisplayDesc { string display_id; int32 vendor_flags; // Opaque value }
Décrit les propriétés de base d'un écran EVS et requises par une implémentation EVS. Le HAL est chargé de remplir cette structure pour décrire l'affichage EVS. Il peut s'agir d'un écran physique ou virtuel qui est superposé ou mélangé à un autre appareil de présentation.
display_id
: chaîne qui identifie de manière unique l'écran. Il peut s'agir du nom de l'appareil du kernel ou d'un nom donné à l'appareil, comme rearview (vue arrière). La valeur de cette chaîne est choisie par l'implémentation HAL et utilisée de manière opaque par la pile ci-dessus.vendor_flags
: méthode permettant de transmettre des informations de caméra spécialisées de manière opaque du pilote à une application EVS personnalisée. Elle est transmise sans interprétation du pilote à l'application EVS, qui est libre de l'ignorer.
enum DisplayState : uint32 { NOT_OPEN, // Display has not been “opened” yet NOT_VISIBLE, // Display is inhibited VISIBLE_ON_NEXT_FRAME, // Will become visible with next frame VISIBLE, // Display is currently active DEAD, // Display is not available. Interface should be closed }
Décrit l'état de l'écran EVS, qui peut être désactivé (non visible par le conducteur) ou activé (affichage d'une image au conducteur).
Inclut un état temporaire dans lequel l'affichage n'est pas encore visible, mais est prêt à le devenir lors de la diffusion de l'image suivante via l'appel returnTargetBufferForDisplay()
.
EVS Manager
Le gestionnaire EVS fournit l'interface publique au système EVS pour collecter et présenter les vues des caméras externes. Lorsque les pilotes matériels n'autorisent qu'une seule interface active par ressource (caméra ou écran), le Gestionnaire EVS facilite l'accès partagé aux caméras. Une seule application EVS principale est le premier client d'EVS Manager. Il s'agit du seul client autorisé à écrire des données d'affichage (les clients supplémentaires peuvent disposer d'un accès en lecture seule aux images de l'appareil photo).
EVS Manager implémente la même API que les pilotes HAL sous-jacents et fournit un service étendu en prenant en charge plusieurs clients simultanés (plusieurs clients peuvent ouvrir une caméra via EVS Manager et recevoir un flux vidéo).
Les applications ne voient aucune différence lorsqu'elles fonctionnent via l'implémentation du HAL matériel EVS ou l'API EVS Manager, sauf que l'API EVS Manager permet un accès simultané au flux de la caméra. Le gestionnaire EVS est, lui-même, le seul client autorisé de la couche HAL matérielle EVS et agit en tant que proxy pour la HAL matérielle EVS.
Les sections suivantes décrivent uniquement les appels dont le comportement est différent (étendu) dans l'implémentation d'EVS Manager. Les appels restants sont identiques aux descriptions d'EVS HAL.
IEvsEnumerator
openCamera(string camera_id) generates (IEvsCamera camera);
Obtient un objet d'interface utilisé pour interagir avec une caméra spécifique identifiée par la chaîne unique camera_id. Renvoie une valeur NULL en cas d'échec.
Au niveau de la couche du gestionnaire EVS, tant que des ressources système suffisantes sont disponibles, une caméra déjà ouverte peut être rouverte par un autre processus, ce qui permet de dériver le flux vidéo vers plusieurs applications grand public. Les chaînes camera_id
au niveau de la couche EVS Manager sont identiques à celles signalées à la couche matérielle EVS.
IEvsCamera
L'implémentation IEvsCamera fournie par le Gestionnaire EVS est virtualisée en interne afin que les opérations effectuées par un client sur une caméra n'affectent pas les autres clients, qui conservent un accès indépendant à leurs caméras.
startVideoStream(IEvsCameraStream receiver) generates (EvsResult result);
Lance les flux vidéo. Les clients peuvent démarrer et arrêter indépendamment les flux vidéo sur la même caméra sous-jacente. La caméra sous-jacente démarre lorsque le premier client démarre.
doneWithFrame(uint32 frameId, handle bufferHandle) generates (EvsResult result);
Renvoie un frame. Chaque client doit renvoyer ses frames lorsqu'il a terminé, mais il est autorisé à les conserver aussi longtemps qu'il le souhaite. Lorsque le nombre de frames détenu par un client atteint la limite configurée, il ne reçoit plus de frames tant qu'il n'en a pas renvoyé un. Ce saut d'image n'a aucune incidence sur les autres clients, qui continuent à recevoir tous les frames comme prévu.
stopVideoStream();
Arrête un flux vidéo. Chaque client peut arrêter son flux vidéo à tout moment sans affecter les autres clients. Le flux de caméra sous-jacent au niveau de la couche matérielle est arrêté lorsque le dernier client d'une caméra donnée arrête son flux.
setExtendedInfo(int32 opaqueIdentifier, int32 opaqueValue) generates (EvsResult result);
Envoie une valeur spécifique au pilote, ce qui peut permettre à un client d'affecter un autre client. Étant donné que EVS Manager ne peut pas comprendre les implications des mots de contrôle définis par le fournisseur, ils ne sont pas virtualisés et les effets secondaires s'appliquent à tous les clients d'une caméra donnée. Par exemple, si un fournisseur utilisait cet appel pour modifier les fréquences d'images, tous les clients de la caméra de la couche matérielle concernée recevraient des images au nouveau débit.
IEvsDisplay
Un seul propriétaire de l'écran est autorisé, même au niveau du responsable EVS. Le gestionnaire n'ajoute aucune fonctionnalité et transmet simplement l'interface IEvsDisplay directement à l'implémentation HAL sous-jacente.
Application EVS
Android inclut une implémentation de référence native C++ d'une application EVS qui communique avec le Gestionnaire EVS et le HAL du véhicule pour fournir des fonctions de base de la caméra de recul. L'application doit démarrer très tôt dans le processus de démarrage du système, avec une vidéo appropriée affichée en fonction des caméras disponibles et de l'état de la voiture (état du rapport et des clignotants). Les OEM peuvent modifier ou remplacer l'application EVS par leur propre logique et présentation spécifiques au véhicule.
Figure 3. Exemple de logique d'application EVS, obtenir la liste des caméras.
Figure 4. Exemple de logique d'application EVS, rappel de frame.
Étant donné que les données d'image sont présentées à l'application dans un tampon graphique standard, l'application est chargée de déplacer l'image du tampon source vers le tampon de sortie. Bien que cela entraîne le coût d'une copie de données, l'application peut également afficher l'image dans le tampon d'affichage de la manière qu'elle souhaite.
Par exemple, l'application peut choisir de déplacer les données de pixel elle-même, éventuellement avec une opération d'échelle ou de rotation intégrée. L'application peut également choisir d'utiliser l'image source comme texture OpenGL et d'afficher une scène complexe dans le tampon de sortie, y compris des éléments virtuels tels que des icônes, des consignes et des animations. Une application plus sophistiquée peut également sélectionner plusieurs caméras d'entrée simultanées et les fusionner dans un seul frame de sortie (par exemple, pour une vue virtuelle du dessus de l'environnement du véhicule).
Utiliser EGL/SurfaceFlinger dans le HAL d'affichage EVS
Cette section explique comment utiliser EGL pour afficher une implémentation HAL d'affichage EVS dans Android 10.
Une implémentation de référence HAL EVS utilise EGL pour afficher l'aperçu de l'appareil photo à l'écran et libgui
pour créer la surface de rendu EGL cible. Sous Android 8 (et versions ultérieures), libgui
est classé comme privé VNDK, ce qui fait référence à un groupe de bibliothèques disponibles pour les bibliothèques VNDK que les processus du fournisseur ne peuvent pas utiliser.
Étant donné que les implémentations HAL doivent résider dans la partition du fournisseur, les fournisseurs ne peuvent pas utiliser Surface dans les implémentations HAL.
Créer libgui pour les processus des fournisseurs
L'utilisation de libgui
est la seule option permettant d'utiliser EGL/SurfaceFlinger dans les implémentations d'écran HAL EVS pour les écrans. Le moyen le plus simple d'implémenter libgui
consiste à utiliser directement frameworks/native/libs/gui en utilisant une cible de compilation supplémentaire dans le script de compilation. Cette cible est identique à la cible libgui
, à l'exception de deux champs ajoutés:
name
vendor_available
cc_library_shared { name: "libgui_vendor", vendor_available: true, vndk: { enabled: false, }, double_loadable: true,
defaults: ["libgui_bufferqueue-defaults"],
srcs: [ … // bufferhub is not used when building libgui for vendors target: { vendor: { cflags: [ "-DNO_BUFFERHUB", "-DNO_INPUT", ], …
Remarque: Les cibles de fournisseurs sont créées avec la macro NO_INPUT
, qui supprime un mot de 32 bits des données de lot. Comme SurfaceFlinger s'attend à ce que ce champ ait été supprimé, SurfaceFlinger ne parvient pas à analyser le colis. Cela se traduit par une défaillance fcntl
:
W Parcel : Attempt to read object from Parcel 0x78d9cffad8 at offset 428 that is not in the object list E Parcel : fcntl(F_DUPFD_CLOEXEC) failed in Parcel::read, i is 0, fds[i] is 0, fd_count is 20, error: Unknown error 2147483647 W Parcel : Attempt to read object from Parcel 0x78d9cffad8 at offset 544 that is not in the object list
Pour résoudre ce problème:
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 6066421fa..25cf5f0ce 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -54,6 +54,9 @@ status_t layer_state_t::write(Parcel& output) const output.writeFloat(color.b); #ifndef NO_INPUT inputInfo.write(output); +#else + // Write a dummy 32-bit word. + output.writeInt32(0); #endif output.write(transparentRegion); output.writeUint32(transform);
Vous trouverez ci-dessous des exemples d'instructions de compilation. Attendez-vous à recevoir une $(ANDROID_PRODUCT_OUT)/system/lib64/libgui_vendor.so
.
$ cd <your_android_source_tree_top> $ . ./build/envsetup. $ lunch <product_name>-<build_variant> ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=10 TARGET_PRODUCT=<product_name> TARGET_BUILD_VARIANT=<build_variant> TARGET_BUILD_TYPE=release TARGET_ARCH=arm64 TARGET_ARCH_VARIANT=armv8-a TARGET_CPU_VARIANT=generic TARGET_2ND_ARCH=arm TARGET_2ND_ARCH_VARIANT=armv7-a-neon TARGET_2ND_CPU_VARIANT=cortex-a9 HOST_ARCH=x86_64 HOST_2ND_ARCH=x86 HOST_OS=linux HOST_OS_EXTRA=<host_linux_version> HOST_CROSS_OS=windows HOST_CROSS_ARCH=x86 HOST_CROSS_2ND_ARCH=x86_64 HOST_BUILD_TYPE=release BUILD_ID=QT OUT_DIR=out ============================================
$ m -j libgui_vendor … $ find $ANDROID_PRODUCT_OUT/system -name "libgui_vendor*" .../out/target/product/hawk/system/lib64/libgui_vendor.so .../out/target/product/hawk/system/lib/libgui_vendor.so
Utiliser un liaisonneur dans une implémentation de HAL EVS
Sous Android 8 (et versions ultérieures), le nœud d'appareil /dev/binder
est devenu exclusif aux processus de framework et, par conséquent, inaccessible aux processus du fournisseur. À la place, les processus du fournisseur doivent utiliser /dev/hwbinder
et convertir toutes les interfaces AIDL en HIDL. Si vous souhaitez continuer à utiliser les interfaces AIDL entre les processus des fournisseurs, utilisez le domaine de liaison /dev/vndbinder
.
Domaine IPC | Description |
---|---|
/dev/binder |
Communication inter-processus entre les processus de framework/application avec des interfaces AIDL |
/dev/hwbinder |
Communication inter-processus entre les processus de framework/fournisseur avec des interfaces HIDL Communication inter-processus entre les processus de fournisseur avec des interfaces HIDL |
/dev/vndbinder |
Communication interprocessus entre les fournisseurs et les processus avec des interfaces AIDL |
Bien que SurfaceFlinger définisse des interfaces AIDL, les processus du fournisseur ne peuvent utiliser que des interfaces HIDL pour communiquer avec les processus du framework. Une quantité non négligeable de travail est requise pour convertir les interfaces AIDL existantes en HIDL. Heureusement, Android fournit une méthode permettant de sélectionner le pilote de liaison pour libbinder
, auquel les processus de bibliothèque d'espace utilisateur sont associés.
diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp index d8fb3166..5fd02935 100644 --- a/evs/sampleDriver/service.cpp +++ b/evs/sampleDriver/service.cpp @@ -21,6 +21,7 @@ #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/Log.h> +#include <binder/ProcessState.h> #include "ServiceNames.h" #include "EvsEnumerator.h" @@ -43,6 +44,9 @@ using namespace android; int main() { ALOGI("EVS Hardware Enumerator service is starting"); + // Use /dev/binder for SurfaceFlinger + ProcessState::initWithDriver("/dev/binder"); + // Start a thread to listen to video device addition events. std::atomic<bool> running { true }; std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running));
Remarque: Les processus du fournisseur doivent appeler cette méthode avant d'appeler Process
ou IPCThreadState
, ou avant d'effectuer des appels de liaison.
Règles SELinux
Si l'implémentation de l'appareil est aiguë complète, SELinux empêche les processus fournisseurs d'utiliser /dev/binder
. Par exemple, une implémentation d'exemple HAL EVS est attribuée au domaine hal_evs_driver
et nécessite des autorisations en lecture/écriture pour le domaine binder_device
.
W ProcessState: Opening '/dev/binder' failed: Permission denied F ProcessState: Binder driver could not be opened. Terminating. F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 9145 (android.hardwar), pid 9145 (android.hardwar) W android.hardwar: type=1400 audit(0.0:974): avc: denied { read write } for name="binder" dev="tmpfs" ino=2208 scontext=u:r:hal_evs_driver:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=0
Cependant, l'ajout de ces autorisations entraîne un échec de compilation, car il enfreint les règles neverallow suivantes définies dans system/sepolicy/domain.te
pour un appareil full-treble.
libsepol.report_failure: neverallow on line 631 of system/sepolicy/public/domain.te (or line 12436 of policy.conf) violated by allow hal_evs_driver binder_device:chr_file { read write }; libsepol.check_assertions: 1 neverallow failures occurred
full_treble_only(` neverallow { domain -coredomain -appdomain -binder_in_vendor_violators } binder_device:chr_file rw_file_perms; ')
binder_in_vendor_violators
est un attribut fourni pour détecter un bug et guider le développement. Il peut également être utilisé pour résoudre le non-respect d'Android 10 décrit ci-dessus.
diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te index f1f31e9fc..6ee67d88e 100644 --- a/evs/sepolicy/evs_driver.te +++ b/evs/sepolicy/evs_driver.te @@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain; hal_server_domain(hal_evs_driver, hal_evs) hal_client_domain(hal_evs_driver, hal_evs) +# Allow to use /dev/binder +typeattribute hal_evs_driver binder_in_vendor_violators; + # allow init to launch processes in this context type hal_evs_driver_exec, exec_type, file_type, system_file_type; init_daemon_domain(hal_evs_driver)
Créer une implémentation de référence HAL EVS en tant que processus de fournisseur
À titre de référence, vous pouvez appliquer les modifications suivantes à packages/services/Car/evs/Android.mk
. Assurez-vous que toutes les modifications décrites fonctionnent pour votre implémentation.
diff --git a/evs/sampleDriver/Android.mk b/evs/sampleDriver/Android.mk index 734feea7d..0d257214d 100644 --- a/evs/sampleDriver/Android.mk +++ b/evs/sampleDriver/Android.mk @@ -16,7 +16,7 @@ LOCAL_SRC_FILES := \ LOCAL_SHARED_LIBRARIES := \ android.hardware.automotive.evs@1.0 \ libui \ - libgui \ + libgui_vendor \ libEGL \ libGLESv2 \ libbase \ @@ -33,6 +33,7 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_INIT_RC := android.hardware.automotive.evs@1.0-sample.rc LOCAL_MODULE := android.hardware.automotive.evs@1.0-sample +LOCAL_PROPRIETARY_MODULE := true LOCAL_MODULE_TAGS := optional LOCAL_STRIP_MODULE := keep_symbols @@ -40,6 +41,7 @@ LOCAL_STRIP_MODULE := keep_symbols LOCAL_CFLAGS += -DLOG_TAG=\"EvsSampleDriver\" LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code +LOCAL_CFLAGS += -Iframeworks/native/include # NOTE: It can be helpful, while debugging, to disable optimizations #LOCAL_CFLAGS += -O0 -g diff --git a/evs/sampleDriver/service.cpp b/evs/sampleDriver/service.cpp index d8fb31669..5fd029358 100644 --- a/evs/sampleDriver/service.cpp +++ b/evs/sampleDriver/service.cpp @@ -21,6 +21,7 @@ #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/Log.h> +#include <binder/ProcessState.h> #include "ServiceNames.h" #include "EvsEnumerator.h" @@ -43,6 +44,9 @@ using namespace android; int main() { ALOGI("EVS Hardware Enumerator service is starting"); + // Use /dev/binder for SurfaceFlinger + ProcessState::initWithDriver("/dev/binder"); + // Start a thread to listen video device addition events. std::atomic<bool> running { true }; std::thread ueventHandler(EvsEnumerator::EvsUeventThread, std::ref(running)); diff --git a/evs/sepolicy/evs_driver.te b/evs/sepolicy/evs_driver.te index f1f31e9fc..632fc7337 100644 --- a/evs/sepolicy/evs_driver.te +++ b/evs/sepolicy/evs_driver.te @@ -3,6 +3,9 @@ type hal_evs_driver, domain, coredomain; hal_server_domain(hal_evs_driver, hal_evs) hal_client_domain(hal_evs_driver, hal_evs) +# allow to use /dev/binder +typeattribute hal_evs_driver binder_in_vendor_violators; + # allow init to launch processes in this context type hal_evs_driver_exec, exec_type, file_type, system_file_type; init_daemon_domain(hal_evs_driver) @@ -22,3 +25,7 @@ allow hal_evs_driver ion_device:chr_file r_file_perms; # Allow the driver to access kobject uevents allow hal_evs_driver self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl; + +# Allow the driver to use the binder device +allow hal_evs_driver binder_device:chr_file rw_file_perms;