Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Simple C++ video player for non-photographic animation

This thread is locked; no one can reply to it. rss feed Print
Simple C++ video player for non-photographic animation
Trent Gamblin
Member #261
April 2000
avatar

So for the game I'm making, we have a cartoon art style and the graphics guy is doing some flash animations for it. Rather than try and play SWF, he's exporting the animation as animated gifs which I convert to individual 8 bit pngs in a zip file.

I tried every feasible codec, but none give the quality as these gifs. The animations have only a handful of colors. Getting similar quality out of Bink, Theora etc created much bigger files.

About audio: in our case we're going to add audio "queues" to the api I'm going to present in a minute. Basic stuff like at 12.12 seconds play sound X. However, if you want real "FMV" with audio, this player should (I haven't tested it but it should) keep in sync with audio if you start the audio in the "start" method of the Video_Player class.

The reason I'm posting it here is 1) maybe someone will spot a bug 2) maybe someone would find it useful 3) maybe it'd just be interesting for someone to look at.

I have a large framework of stuff for the game and a little bit of it is used here. General::Point is just a class with x and y members. Wrap::Bitmap can be substituted with ALLEGRO_BITMAP (It's just a way for me to maintain a list of loaded/created images so I can destroy/reload them all if needed.) The FPS is locked at 16 (my graphics artists preferred working habits) but that's easy to change.

This is a trivial example for playing a video with this API. The "10" in the constructor is the number of frames to buffer. When the video is done playing, "draw" returns true.

Video_Player *vp = new Video_Player("videos/bird", 10);
vp->start();
while (true) {
  vp->update();
  if (vp->draw()) {
    break;
  }
  al_flip_display();
}
delete vp;

So here's the header/interface:

#SelectExpand
1#include <allegro5/allegro.h> 2 3#include <string> 4 5#include "general.h" 6#include "wrap.h" 7 8class Video_Player { 9public: 10 static const int FPS = 16; // FIXME? 11 12 struct Thread_Data { 13 ALLEGRO_MUTEX *mutex; 14 ALLEGRO_COND *loaded; 15 bool done; 16 std::string dirname; 17 int start_frame; 18 int num_frames_to_load; 19 ALLEGRO_BITMAP **bitmaps; 20 bool end_thread; 21 }; 22 23 void start(); 24 bool draw(); // returns true when video is done 25 void update(); 26 27 void set_offset(General::Point<float> offset); 28 29 Video_Player(std::string dirname, int buffer_size_in_frames); 30 ~Video_Player(); 31 32protected: 33 struct Video_Frame { 34 Wrap::Bitmap *bitmap; 35 int frame_num; 36 }; 37 38 void get_frames(int number); 39 40 int buffer_size_in_frames; 41 General::Point<float> offset; 42 43 std::vector<Video_Frame> frames; 44 int total_frames; 45 int total_frames_loaded; 46 double start_time; 47 48 Thread_Data thread_data; 49 50 bool started; 51};

And finally the implementation:

#SelectExpand
1#include "video_player.h" 2 3static void *loader_thread(void *data) 4{ 5 Video_Player::Thread_Data *thread_data = (Video_Player::Thread_Data *)data; 6 7 General::set_physfs_file_interface(); 8 al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); 9 10 while (!thread_data->end_thread) { 11 al_lock_mutex(thread_data->mutex); 12 if (thread_data->num_frames_to_load > 0) { 13 thread_data->bitmaps = new ALLEGRO_BITMAP *[thread_data->num_frames_to_load]; 14 for (int i = 0; i < thread_data->num_frames_to_load; i++) { 15 char buf[1000]; 16 snprintf(buf, 1000, "%s/%06d.png", thread_data->dirname.c_str(), thread_data->start_frame+i); 17 thread_data->bitmaps[i] = al_load_bitmap(buf); 18 } 19 thread_data->num_frames_to_load = 0; 20 thread_data->done = true; 21 al_broadcast_cond(thread_data->loaded); 22 } 23 al_unlock_mutex(thread_data->mutex); 24 al_rest(0.001); 25 } 26 27 return NULL; 28} 29 30void Video_Player::start() 31{ 32 start_time = al_get_time(); 33} 34 35// returns true when video is done 36bool Video_Player::draw() 37{ 38 double now = al_get_time(); 39 double elapsed = now - start_time; 40 double total_len = total_frames * (1.0/FPS); 41 int frame; 42 43 if (elapsed >= total_len) { 44 frame = total_frames-1; 45 } 46 else { 47 frame = total_frames * (elapsed / total_len); 48 } 49 50 // Drop passed frames 51 while (frames.size() > 0 && frames[0].frame_num < frame) { 52 frames.erase(frames.begin()); 53 } 54 55 if (frames.size() == 0) { 56 // This might mean something bad happened 57 return true; 58 } 59 60 al_draw_bitmap(frames[frames.size()-1].bitmap->bitmap, offset.x, offset.y, 0); 61 62 if (elapsed >= total_len) { 63 return true; 64 } 65 66 return false; 67} 68 69void Video_Player::update() 70{ 71 if (total_frames_loaded >= total_frames) { 72 return; 73 } 74 75 int needed = MIN(total_frames-total_frames_loaded, buffer_size_in_frames - (int)frames.size()); 76 if (needed > 0) { 77 get_frames(needed); 78 } 79} 80 81void Video_Player::set_offset(General::Point<float> offset) 82{ 83 this->offset = offset; 84} 85 86void Video_Player::get_frames(int num) 87{ 88 // Make the request 89 al_lock_mutex(thread_data.mutex); 90 thread_data.start_frame = total_frames_loaded; 91 thread_data.num_frames_to_load = num; 92 thread_data.done = false; 93 al_unlock_mutex(thread_data.mutex); 94 95 // Wait for it 96 General::log_message("VIDEO: Waiting on condition..."); 97 al_lock_mutex(thread_data.mutex); 98 while (!thread_data.done) { 99 al_wait_cond(thread_data.loaded, thread_data.mutex); 100 } 101 al_unlock_mutex(thread_data.mutex); 102 General::log_message("VIDEO: Thread loaded frames!"); 103 104 // Convert to video bitmaps 105 for (int i = 0; i < num; i++) { 106 printf("i=%d\n", i); 107 printf("bitmaps[%d]=%p\n", i, thread_data.bitmaps[i]); 108 ALLEGRO_BITMAP *bmp = al_clone_bitmap(thread_data.bitmaps[i]); 109 printf("bmp=%p\n", bmp); 110 Wrap::Bitmap *b = new Wrap::Bitmap(bmp, ""); 111 Video_Frame frame; 112 frame.bitmap = b; 113 frame.frame_num = total_frames_loaded + i; 114 frames.push_back(frame); 115 al_destroy_bitmap(thread_data.bitmaps[i]); 116 } 117 delete[] thread_data.bitmaps; 118 119 total_frames_loaded += num; 120 if (total_frames_loaded >= total_frames) { 121 thread_data.end_thread = true; 122 } 123 printf("total_frames_loaded=%d\n", total_frames_loaded); 124} 125 126Video_Player::Video_Player(std::string dirname, int buffer_size_in_frames) : 127 buffer_size_in_frames(buffer_size_in_frames), 128 offset(0, 0) 129{ 130 thread_data.dirname = dirname; 131 132 // Count frames 133 total_frames = 0; 134 for (; total_frames < 0xffff /* RANDOM */; total_frames++) { 135 char filename[1000]; 136 snprintf(filename, 1000, "%s/%06d.png", thread_data.dirname.c_str(), total_frames); 137 if (!General::exists(filename)) { 138 break; 139 } 140 } 141 total_frames_loaded = 0; 142 General::log_message("Video frame count=" + General::itos(total_frames)); 143 144 // Setup threading 145 thread_data.mutex = al_create_mutex(); 146 thread_data.loaded = al_create_cond(); 147 148 // Start the loading thread 149 al_run_detached_thread(loader_thread, (void *)&thread_data); 150 151 // Request a full buffer of frames to start 152 get_frames(MIN(buffer_size_in_frames, total_frames)); 153} 154 155Video_Player::~Video_Player() 156{ 157 al_destroy_mutex(thread_data.mutex); 158 al_destroy_cond(thread_data.loaded); 159 160 // Cleanup potentially unused frames (due to skipping/lag) 161 for (size_t i = 0; i < frames.size(); i++) { 162 Wrap::destroy_bitmap(frames[i].bitmap); 163 } 164}

The player supports an offset which tells it where on the screen to draw. I'm going to use this to "mix" video with real gameplay by using motion blur to transition.

Any useful comments are appreciated.

EDIT: I should note that before doing any of this I setup the PHYSFS addon to load all data from a zip file.

Go to: