Depuis quelque temps les GIF sont revenus à la mode, c'est marrant (ou pas) mais peu pratique, à tel point que des sites comme Twitter les convertissent en une vidéo H.264 dans un container mp4. Hormis un gain de taille conséquent, une vidéo à aussi l'avantage de pouvoir être mise en pause.

Du coup comment réaliser cette conversion ? Une solution simple et efficace c'est d'utiliser FFmpeg. Le codec x264 sera utilisé pour réaliser l'encodage, exemple.

ffmpeg -i image.gif -pix_fmt yuv420p video.mp4

Cette commande fonctionne très bien mais l'accélération matérielle n'est pas utilisée, dans l'absolu ce n'est pas grave vu que l'encodage d'un GIF est une opération très rapide, mais ce n'est pas très pas fun, la solution : Le coder moi même.

C'est ici qu'intervient VideoToolbox, un framework de compression/décompression vidéo disponible publiquement depuis OS X 10.8 et iOS 8. Le soucis c'est que la documentation est inexistante et vu que c'est un sujet de niche le Stack Overflow Driven Development ne vous aidera pas des masses. La solution c'est donc de regarder les fichiers de headers et lire les commentaires.

Bon au final c'est pas très sorcier, juste l'affaire de quelques fonctions avec une liste de paramètres à rallonge.

On commence par créer une session d'encodage.

Ce qu'il faut noter c'est le flag kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder qui permet d'activer l'accélération matérielle si possible. À ne pas confondre avec kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder qui force son activation, et donc échouera si ce n'est pas possible.

Si la création de la session a réussi, il est possible de spécifier des paramètres additionnels comme le nombre de FPS, le nombre de keyframe, le bitrate, etc… avec les fonctions VTSessionSetProperty / VTSessionSetProperties.

Ensuite il n'y a plus qu'à fournir des frames, elles doivent être sous la forme d'un CVImageBuffer. Je passe sous silence le code pour récupérer les frames d'un GIF, c'est trivial avec les APIs CGImageSource...

Note, lorsque cette fonction retourne il n'est pas garanti que la compression soit terminée.
Voici justement l'implémentation du callback qui est appelé quand une frame est compressée ou non et nous fourni une référence via un CMSampleBuffer si c'est le cas.

Une fois qu'on a envoyé toutes les frames à la session il ne reste qu'à indiquer que l'on a fini.

Pour sauvegarder le tout le plus simple est d'utiliser le framework AVFoundation et les APIs AVAssetWriter, que je ne détaillerai pas ici vu qu'il y a déjà plein d'exemples sur le net.

Un gist avec le code complet et fonctionnel est dispo ici.