SFML ne possède pas de module pour charger, sauvegarder, convertir ou afficher de la video. Pour cela, il existe une librairie conçu en langage C intitulé FFMpeg. Cette librairie est au départ prévu pour fonctionner avec SDL ou éventuellement OpenGL et GLUT sous Linux, mais il est aussi possible de le compiler sous Windows, Mac OS et BSD et de s'en servir avec d'autres librairies à partir du moment où on sait comment manipuler les pixels de chaque image. Ce tutoriel n'explique pas comment effectuer la compilation de FFMpeg, mais uniquement le moyen de charger une video .mpg (ou .avi…) avec SFML. Il se base sur FFMpeg version 0.4.8. L'acquisition du son n'est pas traitée ici.
Voici d'abord les éléments de bases dont on va avoir besoin pour notre programme. Il faut inclure les fichiers pour SFML et FFMpeg. FFMpeg étant codé en C et nos projets en C++, il est souhaitable de l'indiquer au compilateur.
#include <SFML/Graphics.hpp> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> }
On va déclarer ce qui concerne SFML. Libre à vous de structurer le code d'une manière plus propre. Pour ce tutoriel, je m'attache plus au fond qu'à la forme.
sf::RenderWindow App; sf::Image im_video; sf::Sprite sp_video; sf::Uint8 *Data;
im_video va recevoir le tableau de pixels contenant chaque image provenant de la video. sp_video est le sprite qu'utilisera SFML pour dessiner à l'écran notre video. Enfin Data est le tableau de pixels sans lequel on ne pourrait pas transferer la video à SFML. On va maintenant déclarer ce qui concerne FFMpeg.
AVFormatContext *pFormatCtx; int videoStream; int iFrameSize; AVCodecContext *pCodecCtx; AVFrame *pFrame; AVFrame *pFrameRGB; uint8_t *buffer; AVPacket packet;
Ces variables seront expliqué au moment de leur utilisation dans la suite de ce tutoriel.
La première chose à faire est d'ouvrir le fichier de la video et préparer cette dernière pour la lecture. Pour faire cela, on va faire une fonction init_video(char* filename) qui retourne un entier. Encore une fois, lorsque vous aurez bien compris le fonctionnement de ce programme, vous serez libre de structurer le code autrement en orienté object.
AVCodec *pCodec;
On déclare un pointeur pCodec qui va recevoir le code de décodage du fichier video.
av_register_all(); if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0) { fprintf(stderr, "Unexisting file!\n"); return -1; } if(av_find_stream_info(pFormatCtx)<0) { fprintf(stderr, "Couldn't find stream information!\n"); return -1; } dump_format(pFormatCtx, 0, filename, 0);
On appelle d'abord la procédure av_register_all() qui prépare tout les formats et codec que FFMpeg est capable de lire. Ensuite, on ouvre le fichier et vérifie en même temps qu'il est bien présent. Le pointeur pFormatCtx récupère la video et avec la fonction av_find_stream_info(pFormatCtx) on cherche les flux video, c'est à dire les images, les sons… On prévoit aussi les éventuelles erreurs. dump_format() permet d'envoyer des informations sur la boîte de commande pour les débugages.
videoStream=-1; for(int i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec.codec_type==CODEC_TYPE_VIDEO) { videoStream=i; break; } } if(videoStream==-1) return -1; pCodecCtx=&pFormatCtx->streams[videoStream]->codec;
pFormatCtx→streams est un tableau de pointeurs. On cherche dedans le flux video, puis on se place au début du film.
pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; } if(avcodec_open(pCodecCtx, pCodec)<0) return -1; iFrameSize = pCodecCtx->width * pCodecCtx->height * 3;
Pour finir cette première partie, on cherche le codec et verifie si FFMpeg le supporte. Enfin, on l'ouvre avec avcodec_open(). iFrameSize sert à mémoriser la taille totale d'une frame de la video pour l'affichage.
Maintenant que le fichier est ouvert, les codecs sont vérifiés et le flux video est trouvé il faut encore stocker les informations de la video avant de pouvoir enfin afficher notre film à l'écran.
pFrame=avcodec_alloc_frame(); pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB==NULL) return -1;
On prépare pFrame pour stocker notre video qui est au format YUV, c'est à dire Teinte, Saturation et Luminosité. Ensuite, on prépare pFrameRGB pour stocker la video au format RGB, avec lequel travail SFML.
int numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
Avec numBytes on récupère le nombre d'octets d'une image au format RGB24 et aux dimensions de la video et on alloue le buffer avec. Ensuite, le buffer est assigné à pFrameRGB.
Data = new sf::Uint8[pCodecCtx->width * pCodecCtx->height * 4]; return 0; }
Enfin, on prépare Data, notre tableau de pixels qui va servir de pont entre FFMpeg et SFML. Ainsi ce termine notre fonction init_video(char* filename).
On va créer une fonction display() qui va se charger de lire la video et de dessiner chaque image à chaque tour de la boucle principale.
void display() { int frameFinished; if (av_read_packet(pFormatCtx, &packet) < 0) { close_video(); //exit(0); } if(packet.stream_index==videoStream) {
On lit dans la video et si elle est à la fin on peu par exemple fermer la video et sortir. On met une condition car packet.stream_index n'est pas toujours égal à videostream. Je n'avais pas saisis cela au départ, et en dessinant dans la condition ça avait provoqué un clignotement de l'image :)
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); if(frameFinished) { // Convert the image from its native format to RGB img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); }
D'abord, on décode une image provenant de la video, puis le film n'est pas fini, on convertit l'image dans le format RGB choisit au départ.
int j = 0; for(int i = 0 ; i < (iFrameSize) ; i+=3) { Data[j] = pFrameRGB->data[0][i]; Data[j+1] = pFrameRGB->data[0][i+1]; Data[j+2] = pFrameRGB->data[0][i+2]; Data[j+3] = 255; j+=4; } im_video.LoadFromPixels(pCodecCtx->width, pCodecCtx->height, Data);
On transfert l'image à SFML à l'aide de notre tableau de pixels et de la méthode LoadFromPixels de sf::Image.
} // Dessiner l'image sur le tampon de l'écran App.Draw(sp_video); }
On dessine en dehors de la condition, pour la raison que vous connaissez maintenant :)
Créons maintenant une fonction close_video(), dans laquelle on va simplement libérer les variables utile à FFMpeg.
void close_video() { // Libérer le packet alloué par av_read_frame av_free_packet(&packet); // Libérer l'image RGB av_free(buffer); av_free(pFrameRGB); // Libérer l'image YUV av_free(pFrame); // Fermer le codec avcodec_close(pCodecCtx); // Fermer le fichier video av_close_input_file(pFormatCtx); }
Pour achever notre programme, on va appeller tout ce beau monde dans le main avec SFML. Une fois compilé, si tout va bien, vous devriez apprécier votre première video à la sauce SFML :)
int main() { // Notre fonction pour initialiser la video if ( init_video("MaBelleVideo.avi") == 0 ) { // Code SFML de base App.Create( sf::VideoMode(pCodecCtx->width*2, pCodecCtx->height*2, 32), "Video avec SFML et FFMpeg" ); // On crée notre image, en blanc par exemple im_video.Create(pCodecCtx->width, pCodecCtx->height, sf::Color(255,255,255,255)); // J'aime bien ne pas mettre le smooth, ça dépend de la qualité de la video :) im_video.SetSmooth(false); // On crée notre sprite sp_video.SetImage(im_video); // Vous pouvez utiliser les fonctionnalité du sprite sur la video, // comme le scale, de la même manière qu'une simple image fixe // La boucle principale bool Running = true; while (Running) { // Les évènements sf::Event Event; while (App.GetEvent(Event)) { if (Event.Type == sf::Event::Closed) Running = false; if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) Running = false; } // Notre fonction de lecture et dessin de la video display(); // On affiche tout ça App.Display(); App.SetFramerateLimit(50); } // Notre fonction pour fermer la video close_video(); return EXIT_SUCCESS; } return EXIT_FAILURE; }
J'ai utilisé Set.FramerateLimit(50), si vous trouvez mieux, y a pas de soucis. Le 50 correspond au double de 25 images par secondes. J'ai remarqué que avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); ne donne une image video qu'une fois sur deux à chaque tour. Il n'y a pas de perte d'image, c'est juste un fonctionnement comme-ça que je ne saurais vous expliquer :) Donc, en mettant la limite à 50 images on retrouve la même cadence que pour 25 images par secondes.
J'espère en avoir dit suffisamment pour vous permettre d'intégrer une video dans vos programme. Bon, c'est du cinema muet pour l'instant, mais il faut bien commencer par quelque chose :)