Compiler un exécutable pour Android
Je vais présenter dans ce billet comment compiler un exécutable ARM pour Android, l’intégrer à un APK et l’utiliser dans une application.
À titre d’exemple, nous allons intégrer un programme natif, udpxy, dans une application minimale de lecture vidéo.
Contexte
Le framework multimédia d’Android ne supporte pas nativement la lecture de flux UDP multicast (1, 2).
Il est possible, pour y parvenir, d’utiliser des lecteurs alternatifs, par exemple basés sur ffmpeg/libav (l’un est un fork de l’autre) ou libvlc.
Il existe par ailleurs un outil natif, sous licence GPLv3, relayant du trafic UDP multicast vers du HTTP : udpxy. N’importe quel client supportant HTTP (comme le lecteur natif d’Android) peut alors s’y connecter. C’est cet outil que nous allons utiliser ici.
udpxy
Compilation classique
Avant de l’intégrer, comprenons son utilisation en le faisant tourner sur un ordinateur classique (Debian Wheezy 64 bits pour moi).
Il faut d’abord le télécharger les sources, les extraire et compiler :
Si tout se passe bien, nous obtenons (entre autres) un binaire udpxy
.
Test de diffusion
Pour tester, nous avons besoin d’une source UDP multicast. Ça tombe bien, VLC
peut la fournir. Pour obtenir le résultat attendu par udpxy, nous devons
diffuser vers une adresse multicast (ici 239.0.0.1
). Par exemple, à partir
d’un fichier MKV :
En parallèle, démarrons une instance d’udpxy
, que nous venons de compiler :
Cette commande va démarrer un proxy relayant de l’UDP multicast vers de l’HTTP, écoutant sur le port 8379.
Dans un autre terminal, nous pouvons faire pointer VLC sur le flux ainsi proxifié :
Normalement, le flux doit être lu correctement.
Remarquez qu’udpxy pourrait très bien être démarré sur une autre machine (il
suffirait alors de remplacer localhost
par son IP). Mais pour la suite, nous
souhaiterons justement exécuter udpxy localement sur Android.
Bien sûr, avec VLC, nous n’aurions pas besoin d’udpxy. Le flux est lisible directement avec la commande :
Android
Notez que certains devices Android ne supportent pas le multicast, la réception de flux multicast ne fonctionnera donc pas.
Maintenant que nous avons vu comment fonctionne udpxy, portons-le sur Android.
Notre but est de le contrôler à partir d’une application et le faire utiliser par le lecteur vidéo natif.
Pour cela, plusieurs étapes sont nécessaires :
- obtenir un binaire ARM exécutable pour Android ;
- le packager avec une application ;
- l’extraire ;
- l’exécuter.
Exécutable ARM
Pré-compilé
Pour obtenir un binaire ARM exécutable, le plus simple, c’est évidemment de le récupérer déjà compilé, s’il est disponible (c’est le cas pour udpxy). Dans ce cas, il n’y a rien à faire.
Pour le tester, transférons-le sur le téléphone et exécutons-le :
Si tout se passe bien, cette commande ne produit en apparence rien : elle attend qu’un client se connecte. Pour valider le fonctionnement, si le téléphone est sur le même réseau que votre ordinateur, vous pouvez utiliser cette instance (ARM) d’udpxy comme proxy entre la source multicast et un lecteur VLC local :
Replacer xx.xx.xx.xx
par l’ip du device, qu’il est possible d’obtenir ainsi :
Compilation ponctuelle
S’il n’est pas disponible, il va falloir le compiler soi-même à partir des sources, ce qui nécessite le NDK Android, fournissant des chaînes de compilation pré-compilées.
Il suffit alors d’initialiser la variable
d’environnement CC
pour pointer sur la bonne chaîne de compilation (adaptez
les chemins et l’architecture selon votre configuration) :
Bravo, vous venez de générer un binaire udpxy
pour l’architecture ARM.
Compilation intégrée
La compilation telle que réalisée ci-dessus est bien adaptée à la génération d’un exécutable une fois de temps en temps, mais s’intègre mal dans un système de build automatisé. En particulier, un utilisateur avec une architecture différente devra adapter les commandes à exécuter.
Heureusement, le NDK permet une compilation plus générique.
Pour cela, il faut créer un répertoire jni
dans un projet
Android (ou n’importe où d’ailleurs, mais en pratique c’est là qu’il est censé
être), y mettre les sources et écrire des Makefiles.
Créons donc un répertoire jni
contenant les sources. Vu que nous les avons
déjà extraites, copions-les à la racine de jni/
:
Créons un Makefile nommé Android.mk
:
Puis compilons :
ndk-build
se trouve à la racine du NDK.
Le binaire sera généré dans libs/armeabi/udpxy
.
Afin d’organiser les projets plus proprement, il vaut mieux mettre les sources
d’udpxy et son Android.mk
dans un sous-répertoire spécifique au projet (dans
jni/udpxy/
). Dans ce cas, il faut rajouter un fichier jni/Android.mk
contenant :
Packager avec l’application
Je suppose ici que vous savez déjà créer une application Android.
Nous devons maintenant intégrer le binaire dans l’APK. Pour cela, il y a principalement deux solutions :
- l’intégrer aux ressources (dans
res/raw/
) ; - l’intégrer aux assets (dans
assets/
).
Vu que les projets library ne gèrent pas les assets, nous allons utiliser une ressource raw.
Il faut donc copier le binaire dans res/raw/
, à chaque fois qu’il est généré
(à automatiser donc).
Extraire l’exécutable
L’exécutable est bien packagé avec l’application, et comme toutes les
ressources, nous pouvons facilement obtenir un InputStream
(le
fonctionnement est similaire pour les assets).
Mais pour l’exécuter en natif, le binaire doit être présent sur le système de fichiers. Il faut donc le copier et lui donner les droits d’exécution. Sans la gestion des exceptions, cela donne :
Et les parties intéressantes de FileUtils
:
Exécuter le programme natif
Maintenant que le binaire est disponible sur le système de fichiers, il suffit de l’exécuter :
Le lecteur vidéo pourra alors utiliser l’URI proxifié comme source de données :
Projets
andudpxy
Je mets à disposition sous licence GPLv3 le projet library andudpxy
, qui
met en œuvre ce que j’ai expliqué ici :
git clone http://git.rom1v.com/andudpxy.git
(ou sur github)
Pour l’utiliser dans votre application, n’oubliez pas de référencer la library
et de déclarer le service UdpxyService
dans votre AndroidManifest.xml
:
Pour démarrer le démon :
et pour l’arrêter :
andudpxy-sample
J’ai également écrit une application minimale de lecture vidéo qui utilise cette library :
git clone http://git.rom1v.com/andudpxy-sample.git
(ou sur github)
C’est toujours utile d’avoir une application d’exemple censée fonctionner ;-)
L’adresse du flux UDP multicast à lire est écrite en dur dans MainActivity
(et
le flux doit fonctionner lors du démarrage de l’activité) :
Compilation
Après avoir cloné les 2 projets dans un même répertoire parent, renommez les
local.properties.sample
en local.properties
, éditez-les pour indiquer le
chemin du SDK et du NDK.
Ensuite, allez dans le répertoire andudpxy-sample
, puis exécutez :
Vous devriez obtenir bin/andudpxy-sample-debug.apk
.
Bien sûr, vous pouvez aussi les importer dans Eclipse (ou un autre IDE) et les compiler selon vos habitudes.
Conclusion
Nous avons réussi à compiler et exécuter un binaire ARM sur Android, packagé dans une application.
Ceci peut être utile pour exécuter du code déjà implémenté nativement pour
d’autres plates-formes, pour faire tourner un démon natif… Par exemple, le
projet Serval (sur lequel j’ai un peu travaillé) utilise un démon
servald
, qui tourne également sur d’autres architectures.
Ce n’est cependant pas la seule manière d’exécuter du code natif dans une application : la plus courante est d’appeler des fonctions natives (et non un exécutable) directement à partir de Java, en utilisant JNI. L’une et l’autre répondent à des besoins différents.