Loading an .mpg video and playing it

There is no SFML module for loading, saving, converting or displaying videos. However, FFMpeg is a C library made for this purpose. This library original purpose is to be used with SDL or OpenGL and GLUT on Linux, but it can also be compiled on Windows, Mac OS or BSD and be used with other libraries as long as one knows how to deal with each frame pixels. I won't explain how to compile FFMpeg, but only how to load an .mpg (or .avi, …) video using SFML. I'm using FFMpeg 0.4.8. I won't explain how to get the sound from the video neither.

First

Here are the basics we'll use for our application. One should include some SFML and FFMpeg headers. Since FFMpeg is written in C and our projects in C++, one should warn the compiler about it.

#include <SFML/Graphics.hpp>
extern "C"
{
  #include <libavformat/avformat.h>
  #include <libavcodec/avcodec.h>
}

Then we declare SFML objects. It's up to you to clean or structure this code. During this tutorial, content will matter more than form.

sf::RenderWindow App;
sf::Image im_video;
sf::Sprite sp_video;
sf::Uint8 *Data;

im_video will get the pixels table corresponding to each video frame. sp_video is the sprite used by SFML to draw the video on the screen. And Data is the pixels table used to transfer the video to SFML. We're going to declare FFMpeg stuff now.

AVFormatContext *pFormatCtx;
int             videoStream;
int             iFrameSize;
AVCodecContext  *pCodecCtx;
AVFrame         *pFrame;
AVFrame         *pFrameRGB;
uint8_t         *buffer;
AVPacket        packet;

These variables will be explained when being used during this tutorial.

Load a video

The first thing to do is to open the video file and to get this video ready to be played. For this purpose, we'll create a function called init_video(char* filename) returning an int. Once again, once you get the big picture, it's up to you to write this code your way.

  AVCodec   *pCodec;

We declare a pointer pCodec storing the video file decoding code.

  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);

We first call the function av_register_all() whose purpose is to get ready all the formats and codecs readable by FFMpeg. Then, we open the file and check if it's there. The pointer pFormatCtx gets the video. With the function av_find_stream_info(pFormatCtx), we search for the video streams, i.e pictures, sounds, etc. We check if any error occurs in the process. dump_format() sends log informations for debugging purpose.

	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 is a pointers table. We search for the video stream using this table, and move on the beginning of the video.

  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;

To conclude the first part, we look for the codec and check if FFMpeg can use it. Then, we open it with avcodec_open(). iFrameSize stores the total size of a video frame for displaying purpose.

Now that the file is opened, that codecs are checked and the video stream is found, we still have to store informations about the video before being able to display it on the screen.

  pFrame=avcodec_alloc_frame();
 
  pFrameRGB=avcodec_alloc_frame();
  if(pFrameRGB==NULL)
    return -1;

We get pFrame ready to store a video whose format is YUV, meaning Hue, Saturation and Brightness. Then we get pFrameRGB ready to store a video whose format is RGB, format used by 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);

Using numBytes we get the bytes amount required by an RGB24 picture, with the same size as the video, and we allocate the buffer with that bytes amount. Then the buffer is assigned to pFrameRGB.

  Data = new sf::Uint8[pCodecCtx->width * pCodecCtx->height * 4];
 
  return 0;
}

Then we conclude the function init_video(char* filename) by dealing with Data, the pixels table used a bridge connecting FFMpeg and SFML.

Read and display a video

We'll create a function display() reading the video and displaying one frame every time the main loop is processing.

void display()
{
  int frameFinished;
 
  if (av_read_packet(pFormatCtx, &packet) < 0)
  {
    close_video();
    //exit(0);
  }
 
  if(packet.stream_index==videoStream)
  {

We read the video and if for example the video is over we can close it and exit. We put an “if” since packet.stream_index is not always equal to videoStream. I didn't get it first… and when displaying the video, the picture started blinking :)

    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);
	  }

First we decode a picture from the video then, since the video is not over, we convert the picture to the RGB format we chose in the beginning.

    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);

We transfer the picture to SFML using the pixels frame and the LoadFromPixels() method of sf::Image.

  }
  // Draw the picture on the screen buffer
  App.Draw(sp_video);
}

We draw outside the “if” structure, because of what was previously explained :)

Close a video

Let's create a function close_video() now, which will just free variables used by FFMpeg.

void close_video()
{
  // Free the packet allocated by av_read_frame
  av_free_packet(&packet);
  // Free the RGB picture
  av_free(buffer);
  av_free(pFrameRGB);
  // Free the YUV picture
  av_free(pFrame);
  // Close the codec
  avcodec_close(pCodecCtx);
  // Close the video file
  av_close_input_file(pFormatCtx);
}

The main function

To conclude, we'll call all that stuff in the main() using SFML. Once compiled, everything should be ok, and you should enjoy your first SFML-like video :)

int main()
{
        // The init function
	if ( init_video("MaBelleVideo.avi") == 0 )
	{
          // Basic SFML code
	  App.Create( sf::VideoMode(pCodecCtx->width*2, pCodecCtx->height*2, 32),
	                              "Video avec SFML et FFMpeg"
	                            );
 
                // We create a picture, a white one for example
	  im_video.Create(pCodecCtx->width, pCodecCtx->height, sf::Color(255,255,255,255));
                // I like to disable smoothness, it actually depends on the video quality :)
	  im_video.SetSmooth(false);
                // We create the sprite
	  sp_video.SetImage(im_video);
                // You can use the sprite features on the video,
                // like scaling, just like you would on a simple still picture
 
          // The main loop
	  bool Running = true;
	  while (Running)
	  {
            // Events
	    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;
	    }
 
                        // The reading and displaying function of the video
	    display();
 
                        // Let's draw everything
	    App.Display();
	    App.SetFramerateLimit(50);
	  }
 
                // The function to close the video
	  close_video();
	  return EXIT_SUCCESS;
	}
  return EXIT_FAILURE;
}

I used Set.FramerateLimit(50) : if you got something better, that's up to you. 50 is the double of 25 frames per second. I've noticed that avcodec_decode_video() gives a video picture only once every two times. No picture is lost, it's just the way it works, I can't explain it :) So, setting the framerate limit to 50, we got the same quality as a 25 frames per sec rate. I hope everything is fine and than you know now how to put a video on your applications. Well, that's still silent films, but I guess that's better than nothing :)

 
en/tutorials/playvideoffmpeg.txt · Last modified: 2010/11/18 20:01 by ehmicky
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki