Description:
Just provides an object oriented way to allow video to play in SFML using ffmpeg.
Thanks To:
Changelog:
Installation and usage:
To use, you must have avcodec, avformat and swscale installed. You'll probably have to provide the correct path to .h files. Also, you need to link against avcodec, avformat and swscale. If using Ubuntu or any of its variants, just do this:
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev
In windows, you have to download the shared-dev version for win32/win64 here: http://ffmpeg.arrozcru.org/autobuilds/
You use the Video class just as you would use sf::Image, except that you have to manually call the Update function each loop.
This is better explained by an example: (main.cpp):
#include <iostream> #include "video.hpp" int main() { Video viddy("myVideo.mkv"); sf::Sprite sprite(viddy); int w = viddy.GetWidth(); int h = viddy.GetHeight(); sf::RenderWindow App(sf::VideoMode(w, h), "Video with SFML"); sf::Clock clock; bool Running = true; while (Running) { float time = clock.GetElapsedTime(); clock.Reset(); viddy.Update(time); App.Clear(); App.Draw(sprite); App.Display(); 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; } } App.Close(); return EXIT_SUCCESS; }
Here is video.hpp:
#ifndef VIDEO_H #define VIDEO_H #include <SFML/Graphics.hpp> #include <iostream> #include <string> using namespace std; typedef unsigned int nuint; #define __STDC_CONSTANT_MACROS extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> } class Video { private: //Variables sf::Image m_Image; bool m_bVideoLoaded; bool m_bImageNeedsUpdate; AVFormatContext *m_pFormatCtx; sf::Uint8 m_iVideoStream; sf::Uint32 m_iFrameSize; AVCodecContext *m_pCodecCtx; AVFrame *m_pFrame; AVFrame *m_pFrameRGB; sf::Uint8 *m_pBuffer; AVPacket m_Packet; AVCodec *m_pCodec; SwsContext *img_convert_ctx; string m_sFilename; float m_fSecondsPerFrame; float m_fTimePassedSinceLastFrameUpdate; //Functions void UpdateImage(); void LoadNextFrame(); public: Video(); explicit Video(const string& filename = ""); bool LoadFromFile(const string& filename); void Update(float time); sf::Vector2i Size() const { return sf::Vector2i(GetWidth(), GetHeight()); } nuint GetWidth() const { return m_pCodecCtx->width; } nuint GetHeight() const { return m_pCodecCtx->height; } float GetFrameRate() const { return 1/m_fSecondsPerFrame; } operator const sf::Image&() const {return m_Image;} ~Video(); sf::Color GetPixel(nuint x, nuint y) const; void CloseVideo(); private: Video(const Video& rhs); Video& operator=(const Video& rhs); }; #endif // VIDEO_H
And here is video.cpp:
#include "video.hpp" Video::Video() : m_Image(), m_bVideoLoaded(false), m_bImageNeedsUpdate(false), m_pFormatCtx(NULL), m_iVideoStream(0), m_iFrameSize(0), m_pCodecCtx(NULL), m_pFrame(NULL), m_pFrameRGB(NULL), m_pBuffer(NULL), m_Packet(), m_pCodec(NULL), img_convert_ctx(NULL), m_sFilename(""), m_fSecondsPerFrame(0), m_fTimePassedSinceLastFrameUpdate(0) { av_register_all(); } Video::Video(const string& filename) : m_Image(), m_bVideoLoaded(false), m_bImageNeedsUpdate(false), m_pFormatCtx(NULL), m_iVideoStream(0), m_iFrameSize(0), m_pCodecCtx(NULL), m_pFrame(NULL), m_pFrameRGB(NULL), m_pBuffer(NULL), m_Packet(), m_pCodec(NULL), img_convert_ctx(NULL), m_sFilename(""), m_fSecondsPerFrame(0), m_fTimePassedSinceLastFrameUpdate(0) { av_register_all(); if (!filename.empty()) { if (!LoadFromFile(filename)) { cout << "Could not load video!" << endl; //Probably should throw exception. } } } bool Video::LoadFromFile(const string& filename) { CloseVideo(); m_sFilename = filename; const char * const file = m_sFilename.c_str(); if(av_open_input_file(&m_pFormatCtx, file, NULL, 0, NULL) != 0) { cout << "Unexisting file!" << endl;; return false; } if(av_find_stream_info(m_pFormatCtx) < 0) { cout << "Couldn't find stream information!" << endl; return false; } dump_format(m_pFormatCtx, 0, file, 0); m_iVideoStream = -1; for(nuint i = 0; i < m_pFormatCtx->nb_streams; i++) { if(m_pFormatCtx->streams[i]->codec->coder_type == CODEC_TYPE_VIDEO) { m_iVideoStream = i; break; } } if(m_iVideoStream == -1) return false; m_pCodecCtx = m_pFormatCtx->streams[m_iVideoStream]->codec; m_pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id); AVRational r = m_pFormatCtx->streams[m_iVideoStream]->avg_frame_rate; AVRational r2 = m_pFormatCtx->streams[m_iVideoStream]->r_frame_rate; if ((!r.num || !r.den) && (!r2.num || !r2.den)) { std::cerr << "Video_video::Initialize() - unable to get the video frame rate. Using 25 fps." << std::endl; m_fSecondsPerFrame = 1.f / 25; } else { if (r.num && r.den) m_fSecondsPerFrame = 1.f/((float)r.num / r.den); else m_fSecondsPerFrame = 1.f/((float)r2.num / r2.den); } if(m_pCodec == NULL) { cout << "Unsupported codec!" << endl; return false; } if(avcodec_open(m_pCodecCtx, m_pCodec) < 0) { cout << "Could not open Codec Context" << endl; return false; } m_iFrameSize = m_pCodecCtx->width * m_pCodecCtx->height * 3; m_pFrame = avcodec_alloc_frame(); m_pFrameRGB = avcodec_alloc_frame(); if(m_pFrameRGB == NULL || m_pFrame == NULL) { cout << "Error allocating frame" << endl; return false; } int numBytes = avpicture_get_size(PIX_FMT_RGBA, m_pCodecCtx->width, m_pCodecCtx->height); m_pBuffer = (sf::Uint8 *)av_malloc(numBytes * sizeof(sf::Uint8)); avpicture_fill((AVPicture *)m_pFrameRGB, m_pBuffer, PIX_FMT_RGBA, m_pCodecCtx->width, m_pCodecCtx->height); img_convert_ctx = sws_getContext(m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt, m_pCodecCtx->width, m_pCodecCtx->height, PIX_FMT_RGBA, SWS_FAST_BILINEAR, NULL, NULL, NULL); m_bVideoLoaded = true; m_bImageNeedsUpdate = true; m_Image.Create(GetWidth(), GetHeight(), sf::Color::Black); Update(10000); return true; } //Load From File void Video::Update(float time) { if (m_bVideoLoaded) { m_fTimePassedSinceLastFrameUpdate += time; UpdateImage(); if (m_fTimePassedSinceLastFrameUpdate > m_fSecondsPerFrame) { m_fTimePassedSinceLastFrameUpdate = 0; LoadNextFrame(); } } } void Video::LoadNextFrame() { do { av_free_packet(&m_Packet); int result = av_read_frame(m_pFormatCtx, &m_Packet); if (result < 0) { CloseVideo(); LoadFromFile(m_sFilename); m_fTimePassedSinceLastFrameUpdate = 0; return; } } while (m_Packet.stream_index != m_iVideoStream); int frameFinished = 0; avcodec_decode_video2(m_pCodecCtx, m_pFrame, &frameFinished, &m_Packet); if (frameFinished) { sws_scale(img_convert_ctx, m_pFrame->data, m_pFrame->linesize, 0, m_pCodecCtx->height, m_pFrameRGB->data, m_pFrameRGB->linesize); m_bImageNeedsUpdate = true; } } sf::Color Video::GetPixel(nuint x, nuint y) const { nuint i = 3*(y*GetWidth()+x); sf::Uint8 red = m_pFrameRGB->data[0][i]; sf::Uint8 green = m_pFrameRGB->data[0][i+1]; sf::Uint8 blue = m_pFrameRGB->data[0][i+2]; return sf::Color(red,green,blue,255); } void Video::UpdateImage() { if (m_bImageNeedsUpdate) { m_Image.UpdatePixels(m_pBuffer); m_bImageNeedsUpdate = false; } } void Video::CloseVideo() { if (m_bVideoLoaded) { av_free_packet(&m_Packet); av_free(m_pBuffer); av_free(m_pFrameRGB); av_free(m_pFrame); avcodec_close(m_pCodecCtx); av_close_input_file(m_pFormatCtx); sws_freeContext(img_convert_ctx); m_bVideoLoaded = false; m_bImageNeedsUpdate = false; } } Video::~Video() { CloseVideo(); }
There are still many things to do, such as seeking, pausing, audio, etc. Tell me if you find this useful.