Automatisation encodage vidéo pour N900

L’objectif de ce billet et de mettre en œuvre un processus de transfert de vidéo depuis un répertoire d’un PC vers le N900 avec un ré-encodage des fichiers afin d’en assurer une lecture optimale sur le terminal. Ce cas d’utilisation s’avère utile quand on veut visionner des programmes TV enregistrés par exemple.

Nous disposons sur le PC d’un répertoire dans lequel on va mettre les vidéos à encoder pour transfert sur le N900. Disons que ce répertoire est le suivant:

/home/thierry/Vidéos/n900

L’idée est de passer au crible le répertoire ci-dessus à la recherche de fichier à convertir. Comme les fichiers ont une résolution d’image variable, nous allons utiliser un script qui sélectionne le bon encodage en fonction du fichier. Le billet précédent montre une correspondance entre chaine de la TNT par ADSL et la taille du fichier encodé.

Le fichier /home/thierry/Vidéos/n900/convert_all.sh suivant contient le script d’encodage:

#!/bin/bash

echo "***********************************"
echo "*                                 *"
echo "*    video converter for N900     *"
echo "*                                 *"
echo "***********************************"



INPUT_DIR=.
OUTPUT_DIR=$INPUT_DIR/converted

# do not allow concurrency
if [ -e "$INPUT_DIR/.lock" ]; then
 exit 1
else
 touch "$INPUT_DIR/.lock"
fi

function convert_file {
 original_def=$2
 original_file=$1
 converted_file="$OUTPUT_DIR/$original_file.mp4"

 if [ -e "$converted_file" ]; then
 return 1
 fi

 if [ $original_def == "mpeg" ]; then
 SIZE="400x480"
 elif [ $original_def == "dvd" ]; then
 SIZE="592x480"
 elif [ $original_def == "hd" ]; then
 SIZE="640x480"
 else
 echo "format $original_def inconnu"
 return 1
 fi

 (ffmpeg -i "$original_file" -acodec libfaac -vcodec libx264  -vpre hq -vpre baseline -crf 25 -maxrate 1000k -bufsize 2000k -threads 0 -flags2 -fastpskip  -s "$SIZE" "$converted_file" ) &
 wait
 if [ $? -eq 0 ]; then
 rm "$original_file"
 fi

}

find $INPUT_DIR -maxdepth 1 -type f ( -name "France 3*.ts" -o -name "France 4*.ts" -o -name "France 5*.ts" -o -name "La Chaîne*.ts" -o -name "NT1*.ts" -o -name "TMC*.ts" ) -print | ( while read i; do convert_file "$i" mpeg; done )

find $INPUT_DIR -maxdepth 1 -type f ( -name "DirectStar*.ts" -o -name "RTL9*.ts" -o -name "AB*.ts" ) -print | ( while read i; do convert_file "$i" dvd; done )

find $INPUT_DIR -maxdepth 1 -type f ( -name "Arte*.ts" -o -name "Direct 8*.ts" -o -name "France 2*.ts" -o -name "NRJ*.ts" ) -print | ( while read i; do convert_file "$i" hd; done )

rm "$INPUT_DIR/.lock"

Ce script encode les fichiers vers le sous répertoire /home/thierry/Vidéos/n900/converted en supprimant les fichiers originaux après leur conversion.

L’automatisation de la conversion se fait par un script que l’on va mettre dans /etc/crond.hourly/convertReplayN900 dont voici le contenu:

#!/bin/sh
N900_DIR=/home/thierry/Vidéos/n900
cd $N900_DIR
./convert_all.sh
chown -R thierry:thierry  ./converted

Ce script sera lancé toutes les heures. Il exécutera le premier script vu plus haut puis changera le propriétaire des fichiers encodés (ici avec l’utilisateur thierry) car sinon les fichiers encodés appartiendront à root.

Il ne reste plus qu’à plannifier le transfert des fichiers encodés vers le N900. Comme d’habitude, ce sera au terminal d’initier le transfert car il est plus facile d’avoir un deamon rsync sur le PC que sur le N900. On suppose donc que le répertoire contenant les fichiers encodés est accessible par le module videos du serveur rsync.

Voici le script à utiliser depuis le terminal pour y rapatrier les fichiers convertis:

#!/bin/sh
rsync -v --remove-sent-files --ignore-existing 192.168.0.12::"videos/n900/converted/*.mp4" /home/user/MyDocs/.videos/replay

Le lecteur prendra soin de modifier l’adresse 192.168.0.12 par celle du PC où se trouve le serveur rsync.

Encodage de vidéos pour N900 en h264 avec ffmpeg

Nokia N900 communicator/internet tablet
Image via Wikipedia

Le lecteur intégré du n900 est capable de lire plusieurs formats de vidéo dont le plus intéressant est le h264 qui est le plus performant en terme de compression. Le décodage ce fait par la puce DSP ce qui impose des limitations si on veut profiter de l’accélération matérielle et du gain d’autonomie qui en découle.

Le h264 définie plusieurs niveaux de critères de performance des lecteurs. Le n900 est donné comme supportant le niveau baseline 3 mais il se trouve que ce n’est pas vrai. Des vidéos encodées baseline 3 par ffmpeg peuvent ne pas êtres lues par le lecteur intégré du n900. Il vaut mieux se contenter du baseline 2.1.

La baseline n’est pas le seul critère pour la conversion. La taille de l’image en est un autre. En effet la résolution de l’écran du N900 est 800×480. Il est donc inutile de convertir dans une taille supérieure. De plus cette résolution n’est pas multiple de 16 or cela est un critère qui facilite l’encodage. Donc il faut choisir une résolution plus petite mais respectant un facteur 16.

Ffmpeg est un outil en ligne de commande qui permet de convertir des fichiers vidéo. Les options principales sont :

  • -i le fichier d’entrée
  • -acodec le codec pour l’audio, libfaac
  • -vcodec le codec pour la video, libx264

Le choix de la résolution de la vidéo encodée dépend de celle de départ. Nous traitons ici le cas de fichier vidéo issus de la diffusion des chaines de la TNT via l’ADSL. Prenons l’exemple de la diffusion d’une chaine en 16:9 mais en simple définition. L’analyse des informations du flux enregistré (un fichier .ts) fait apparaitre une vidéo 480×576. C’est la résolution de stockage (SAR). Si on affichait l’image telle quelle, elle serait déformée car écrasée par les cotés. L’image s’affiche pourtant correctement dans un lecteur. C’est parce que le fichier contient aussi une information sur le facteur d’écrasement (PAR) qui indique de combien on doit étirer l’image dans chacun des axes pour obtenir la vidéo affichable. Le résultat est une image au format 16:9 (indiquée par la valeur DAR). Le lecteur vidéo est sensé interpréter correctement les informations de DAR et PAR afin d’étirer l’image et lui donner le bon aspect. Notre objectif étant de ne pas perdre de la surface d’image (couper l’image) tout en  maximisant la qualité, nous choisissons de réduire la hauteur à 480 ( résolution de l’écran du N900) et de conserver le SAR i.e réduire proportionnellement la largeur de l’image soit 400 (480*480/576=400). Par chance cette taille d’image est multiple de 16. Finalement une vidéo e 480×576 sera redimensionnée en 400×480.

Il existe malheureusement de nombreuses résolutions de stockage (SAR) et la règle de 3 ne tombe pas toujours pile sur un multiple de 16. Heureusement que ffmpeg modifie automatiquement le PAR afin de conserver le DAR. Donc on peut choisir arbitrairement une résolution d’image de stockage multiple de 16 en prenant en compte les 2 critères

  1. maximisation de la taille pour ne pas trop perdre en information
  2. ne pas dépasser les limites de la résolution de l’écran du N900

En faisant un test sur les chaines de la TNT sur une Freebox, voici ce que l’on obtient (par abus de langage nous appelons SAR la taille de l’image)

Source SAR source DAR SAR encodée
France 2 1440×1080 16:9 640×480
France 3 544×576 16:9 400×480
France 4 480×576 16:9 400×480
France 5 480×576 16:9 400×480
Arte 1440×1080 16:9 640×480
Direct 8 1440×1080 16:9 640×480
La chaîne parlementaire 480×576 16:9 400×480
RTL9 720×576 16:9 592×480
TMC 544×576 16:9 400×480
NRJ 12 1440×1080 16:9 640×480
NT1 544×576 16:9 400×480
DirectStar 720×576 16:9 592×480

La dernière colonne du tableau précédent donne la taille de l’image dans la vidéo encodée. Cette taille est spécifiée par l’option -s dans ffmpeg.

Le codec h264 nécessite un certain nombre de réglage que voici:

  • -vpre baseline définie des réglages pour que la vidéo soit conforme au profil baseline (i.e. pour appareil mobile ou à faible ressource)
  • -vpre hq définie des paramètres pour obtenir une qualité élevé (fixe le niveau 3 du profil baseline ?)
  • -crf 25 indique la qualité de l’image souhaitée  (petite valeur pour meilleur qualité) en contrepartie d’un usage de bande passante (bitrate) variable
  • -maxrate 1000k indique a bande passante (bitrate) maximum. Cela est utile pour limiter la taille de la vidéo et le besoin en bande passante I/O.  1000kbit/s.
  • -bufsize 2000k buffer necessité par l’option -maxrate. 2000kbit.
  • -flags2 -fastpskip permet d’augmenter légèrement la qualité

Non spécifique au h264 mais supporté par tous les processeurs récents :

  • -threads 0 indique d’utiliser les capacités multithread du CPU

Supposons que l’on ait un fichier video (France2.ts)  issu d’un enregistrement de la chaine France 2, voici la commande de conversion
$ ffmpeg -i France2.ts -acodec libfaac -vcodec libx264 -vpre hq -vpre baseline -crf 25 -maxrate 1000k -bufsize 2000k -threads 0 -flags2 -fastpskip -s 640x480 France2.mp4

Au bout d’un temps assez long puisque supérieur à la durée de la vidéo originale (sur un Core2 Duo 2.4 Ghz et 4 Go de mémoire), on obtient un fichier mp3 lisible sur le N900 via le lecteur multimédia intégré.

Astuces pour faire des tests unitaires en Java

Bug
Image by Ecyrd via Flickr

Les tests unitaires ont pour but de tester le fonctionnement de chaque module, objet indépendamment de l’environnement qui dans lequel il sera utilisé par la suite. L’objectif est de garantir que le contrat de de module ou de la classe est bien respecté.

Le développeur inexpérimenté aura tendance à coder tous les modules avant des les assembler puis de constater que cela fonctionne ou pas. Ce test dit d’intégration ne permet pas de trouver rapidement l’origine du problème.

Pour autant est-ce que le test d’intégration doit être négligé ? En effet une bonne pratique est l’analyse descendante illustrant le vieille adage “diviser pour mieux régner”. En partant du haut, les premières méthodes utilisent alors des modules ou classes non encore écrites. Doit-on tester ces grosses briques ? Si oui comment ?

Le test doit être un acquis dogmatique. Un code non testé est un code sans valeur. La réticence première à tester est souvent justifiée par l’impossibilité à ce stade du développement d’avoir l’écosystème pour tester. Par exemple le développeur va dire “je n’ai pas encore tel ou tel module qui doit me fournir des données” ou bien “je n’ai pas encore la couche de persistance”. Il faut être clair. On test ce qui est utile et ce qui est utile c’est l’implémentation des fonctionnalités, des spécifications. Si on ne peut pas tester un code terminé c’est signe d’une mauvais;e conception: on mélange dans une même méthode ou classe des responsabilités différentes. Un code bien écrit est un code testable !

voyons comment écrire du code testable à travers des exemples.

Utilisation de classe non encore implémentée

Supposons que l’on ait une classe A qui fait des choses intéressantes mais qui doit les algorithmes sont paramétrés. Bien sûr la mécanique de paramétrage avec fichier de configuration ou configuration en base n’existe pas encore..
Si on code en due la configuration dans A, il va falloir revenir sur A pour y mettre le code définitif d’accès à la configuration. Or il y a une règle qui dit un code bien conçu doit marcher avec du code non encore écrit. Pour y parvenir on utilise le vielle adage diviser pour mieux régner qui sera incarné par une interface. La classe A sera paramétrée par une interface IAConfig passée au constructeur pour faire simple. Dès lors le test unitaire ne pose plus de problème puisqu’il va porter la configuration en dur et le code de A fonctionnera dans son utilisation réelle au sein de l’application.

Signature de méthode incompatible

Ce cas peut s’illustrer par une méthode m qui prend en argument un type la rendant difficile à tester. Par exemple disons une méthode qui prend un java.io.File en argument. Le développeur pourra se plaindre qu’il n’est pas aisé de faire un test unitaire fonctionnant sur n’importe quelle machine. Il a raison. Référencer un File dans un test JUnit sous Maven demande quelques lignes de code. Il faut filtrer un fichier de propriété contenant project.basedir=${project.basedir} qui sera placé en resource de test. Puis dans le test unitaire charger le fichier de propriétes avec un getResourceAsStream() et enfin construire le chemin vers le fichier. Pas direct mais faisable.

D’ailleurs pourquoi faire compliqué quand on peut faire simple? Un code bien écrit est un code testable. A-t-on vraiment besoin d’un File en paramètre? Utilise-t-on des propriétés ou des méthodes spécifiques au File? Sans doute non, donc un InputStream devrait être suffisant. L’idée est de changer la signature de la méthode pour la rendre le moins spécifique possible, ainsi elle sera plus facilement testable. Dans son utilisation réelle l’appelant peut manipuler un File s’il le souhaite et passer un FileInpuStream à la méthode dûment testée.

Cas de la base de données avec des DAO

Une dernière raison invoquée pour refouler les tests est la présence d’une base de données. Le premier point est d’éviter de mettre de la logique dans le traitement SQL. Si ce n’est pas passible il faut investir dans du DBUnit parce que cela signifie que la valeur ajoutée, l’implémentation des spécifications est dans le code SQL… Vraiment? La plupart du temps le code métier dans le SQL est un signe de médiocrité.

Le deuxième point est que l’absence de code métier dans la couche de persistance fait qu’il n’est pas vraiment besoin de la tester. Par exemple si on utilise un pattern DAO, on ne va pas tester les DAO si ils ne font que CRUD.

Le 3e point est que le code à tester se trouve au dessus de la couche de persistance donc testable en bouchonnant les DAO : on ne se mock pas.

L’utilisation de DAO permet de substituer facilement les implémentation SQL avec des implémentations de test. En effet le bon pattern DAO ne signifie pas seulement isoler le code SQL dans des classdes DAO mais aussi rendre les DAO interchangeable. On obtient cela on utilisant une fabrique abstraite de DAO que j’appelle DAOFactoryManager, classe intermédiaire entre le code métier et les DAO. Le DAOFactoryManager est configurable avec le type d’implémentation. Dans son utilisation réelle il renvoit par exemple une fabrique de DAO pour MySQL et en test une fabrique de mock. Le code métier doit y voir que du feux en ne manipulant qu’une interface IDAOFactory obtenue auprès du DAOFactoryManager et des interface pour chaque DAO obtenus depuis le IDAOFactory.
Cela semble lourd mais les bienfaits valent le coût. En effet le code métier est testable sans implémentation de la couche de persistance. Le developpeur peut à loisir paramétrer un MockDAOFactory dans la phase de préparation des acteurs du test, puis appeler la méthode métier qui va lors de son execution appeler le DAOFactorManager configurer en mode test, et finalement utiliser des DAO “mockés”.

On prendra juste soin de ne pas liée le DAOFactoryManager avec les objets mocké. En effet le manager de DAO est une classe qui ira en production, alors que les mock qui sont des classes de tests ne le devraient pas. En java il suffit d’utiliser la réflexion pour charger la classe dynamiquement avec un :

Class.forName("com.acme,app.dao.MockDAOFactory").newInstance()

Lors de la phase de test la classe sera disponible mais pas en production.

Tests d’intégrations

Nous entendons pas test d’intégration les tests des méthodes de façades. Ce sont celles qui orchestre les appels aux objets métiers pour accomplir les services attendus de l’application. Oublions un instant toute la pollution intellectuelle apportée par les EJB. Dans un contexte uniquement objet, on doit avoir des façades qui constituent les points d’entrées sur les fonctions du système: ce sont les implémentations informatiques des cas d’utilisations. Par exemple une application qui va tous les soirs lire une base de données puis envoyer des mails de rappel  à tous les utilisateurs portera une méthode dédié à ce cas d’utilisation dans sa façade. Cette façade pourra être appelé en ligne de commande ou par une IHM Swig ou web. Le métier ne changeant pas, le fait de garantir le fonctionnement de la façade est aussi important que de garantir le fonctionnement unitaire. ll faut tester la façade et ce n’est pas faire une entorse à la définition de test unitaire. Quand on teste une méthode qui utilise la classe ArrayList, on suppose que celle-ci n’est pas boguée et on ne la ” mock” pas. De même une fois que les briques de bases sont développées et testés unitairement, on doit les considérer comme des instructions de base et les utiliser dans la façade que l’on va tester “unitairement”.

Ce niveau de test d’intégration est pour moi pas moins que nécessaire. Alors gardons à l’esprit qu’un code non testé est un code sans valeur. Tester doit être une seconde nature qui oblige à vérifier que le code fonctionne et en les automatisant ils deviennent répétables sans effort permettant ainsi de détecter les régressions. Alors testons, testons, nos utilisateurs nous le rendront !

Passage en IPv6 sous Ubuntu 10.10 avec Free

Illustration of how IPv4 address is converted ...
Image via Wikipedia

La pénurie d’adresse IPv4 est consommée. Les adresses de la forme xxx.xxx.xxx.xxx ne sont plus disponibles. L’utilisation de IPv6 est heureusement facile avec Ubuntu 10.10.

En effet il suffit de configurer sa box pour gérer l’IPv6. Dans l’interface d’administration de Free il y a une case à cocher pour activer l’IPv6. Après un redémarrage de la freebox il faut aller dans l’outil réseau pour activer l’IPv6 sur la carte réseau. Il était sans doute désactivé chez moi car inutile lors de l’installation du système.

Et voilà pour tester la connexion, pointer un navigateur sur //ipv6.google.com ou encore visiter les sites suivants //www.myipv6.org/ et //test-ipv6.com/.

Attention dans le cas où un proxy est utilisé, entre le navigateur et le site web désiré, le proxy doit être compatible IPv6. Dans mon cas Squid 2.7 empêche les tests précédents de passer. En effet ce n’est qu’à partir de la version 3 que Squid gère Ipv6. Bien que Ubuntu 10.10 supporte officiellement la version 2.7, la version 3 est dans le dépot. Son installation se fait sans encombre. Les sites de test indiquent alors que mon système est prêt pour IPv6.

Notons que pour accéder directement à un site par son adresse IPv6 il faut mettre l’adresse IP entre crochet.