/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: David Hasenfratz co-programmer: */ /* this is for debug output. It just says, that all calls to PRINT() belong to the DEBUG_MODULE_MEDIA module For more information refere to https://www.orxonox.net/cgi-bin/trac.cgi/wiki/DebugOutput */ #define DEBUG_MODULE_MEDIA // include your own header #include "movie_player.h" #include "util/loading/resource_manager.h" // header for debug output #include "debug.h" ObjectListDefinition(MoviePlayer); MoviePlayer::MoviePlayer(const std::string& filename) { // set the class id for the base object this->registerObject(this, MoviePlayer::_objectList); status = STOP; timer = 0; frame_number = 0; mediaLoaded = false; if (!filename.empty()) this->loadMovie(filename); } MoviePlayer::~MoviePlayer() { this->unloadMedia(); } void MoviePlayer::unloadMedia() { // check whether a movie is already loaded if(!mediaLoaded) return; if (glIsTexture(texture)) glDeleteTextures(1, &texture); // Free the RGB image delete [] buffer; av_free(RGB_frame); // Free the frame av_free(frame); // Close the codec avcodec_close(codec_context); // Close the video file av_close_input_file(format_context); status = STOP; timer = 0; frame_number = 0; mediaLoaded = false; } bool MoviePlayer::loadMovie(const std::string& filename) { this->unloadMedia(); if(filename.empty()) return false; // check whether file exists if(!ResourceManager::isInDataDir(filename)) { PRINTF(1)("Could not find %s\n", filename.c_str()); return false; } // register all formats and codecs av_register_all(); // Open video file if (av_open_input_file(&format_context, ResourceManager::getFullName(filename).c_str(), NULL, 0, NULL) !=0 ) { PRINTF(1)("Could not open %s\n", ResourceManager::getFullName(filename).c_str()); return false; } // Retrieve stream information if (av_find_stream_info(format_context) < 0) { PRINTF(1)("Could not find stream information in %s\n", ResourceManager::getFullName(filename).c_str()); return false; } // Find the first video stream and take it video_stream = av_find_default_stream_index(format_context); if(video_stream == -1) { PRINTF(1)("Could not find a video stream in %s\n", ResourceManager::getFullName(filename).c_str()); return false; } // Get a pointer to the codec context for the video stream codec_context = format_context->streams[video_stream]->codec; // Find the decoder for the video stream codec = avcodec_find_decoder(codec_context->codec_id); if (codec == NULL) { PRINTF(1)("Could not find codec\n"); return false; } // Open codec if (avcodec_open(codec_context, codec) < 0) { PRINTF(1)("Could not open codec\n"); return false; } // Allocate video frame frame = avcodec_alloc_frame(); RGB_frame = avcodec_alloc_frame(); // Determine required buffer size and allocate buffer num_bytes = avpicture_get_size(PIX_FMT_RGB24, codec_context->width, codec_context->height); buffer=new uint8_t[num_bytes]; // Assign appropriate parts of buffer to image planes in RGB_frame avpicture_fill((AVPicture *)RGB_frame, buffer, PIX_FMT_RGB24, codec_context->width, codec_context->height); // data buffer for the texture data = new uint8_t[codec_context->width*codec_context->height*3*sizeof(uint8_t)]; // Calculate fps fps = av_q2d(format_context->streams[video_stream]->r_frame_rate); // NOTE: fix this fps problem!! if(fps < 0 || fps > 1000) fps = 30; // duration duration = format_context->duration / 1000000LL; // create texture glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); mediaLoaded = true; return true; } void MoviePlayer::getNextFrame() { // get next frame if(av_read_frame(format_context, &packet) >= 0) { // Is this a packet from the video stream? if(packet.stream_index == video_stream) { int frame_finished; // Decode video frame avcodec_decode_video(codec_context, frame, &frame_finished, packet.data, packet.size); // Free the packet that was allocated by av_read_frame av_free_packet(&packet); // Did we get a video frame? if(frame_finished) { frame_number++; //PRINTF(0)("frame_number: %i\n", frame_number); // Conversion from YUV to RGB // Most codecs return images in YUV 420 format // (one luminance and two chrominance channels, with the chrominance // channels samples at half the spatial resolution of the luminance channel) img_convert((AVPicture*)RGB_frame, PIX_FMT_RGB24, (AVPicture*)frame, codec_context->pix_fmt, codec_context->width, codec_context->height); for(int i = 0; i < codec_context->height; i++) memcpy(&data[i*codec_context->width*3], ((AVPicture*)RGB_frame)->data[0]+i * ((AVPicture*)RGB_frame)->linesize[0], codec_context->width*sizeof(uint8_t)*3); glBindTexture(GL_TEXTURE_2D, texture); // update the texture glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, codec_context->width, codec_context->height, GL_RGB, GL_UNSIGNED_BYTE, data); // build the MipMaps gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, codec_context->width, codec_context->height, GL_RGB, GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); } else { av_free_packet(&packet); this->getNextFrame(); } } else { av_free_packet(&packet); this->getNextFrame(); } } else this->stop(); } void MoviePlayer::skipFrame(int frames) { while(frames != 0) { if(av_read_frame(format_context, &packet) < 0) break; if(packet.stream_index == video_stream) { int frame_finished; // We have to decode the frame to not get ugly fragments avcodec_decode_video(codec_context, frame, &frame_finished, packet.data, packet.size); // Did we get a video frame? if(frame_finished) { frames--; frame_number++; } } av_free_packet(&packet); } this->getNextFrame(); } bool MoviePlayer::gotoFrame(int frames) { if(!mediaLoaded) { PRINTF(0)("Load first the media file with loadMovie\n"); return false; } int err; // seek doesnt work for the first two frames // you will get ugly fragments if(frames < 2) { // go to the begin of the video err = av_seek_frame(format_context, video_stream, 0, AVSEEK_FLAG_BACKWARD); if(err < 0) { PRINTF(1)("Could not seek to frame 0\n"); return false; } this->frame_number = 0; } else { // seeks to the nearest keyframe // NOTE: there is only about every 5s a keyframe! err = av_seek_frame(format_context, video_stream, frames, AVSEEK_FLAG_BACKWARD); if(err < 0) { PRINTF(1)("Could not seek to frame %i\n", frames); return false; } // go from the keyframe to the exact position codec_context->hurry_up = 1; do { if(av_read_frame(format_context, &packet) < 0) { PRINTF(1)("Could not seek to frame %i\n", frames); return false; } if(packet.stream_index == video_stream) { if(packet.pts >= frames-1) break; int frame_finished; avcodec_decode_video(codec_context, frame, &frame_finished, packet.data, packet.size); av_free_packet(&packet); } } while(1); codec_context->hurry_up = 0; this->frame_number = frames; } return true; } void MoviePlayer::start(float start_time) { //start_frame = start_time * fps; start_frame = 0; if(this->gotoFrame(start_frame)) { status = PLAY; timer = 0; } } void MoviePlayer::resume() { if(status == PAUSE) status = PLAY; } void MoviePlayer::pause() { if(status == PLAY) status = PAUSE; } void MoviePlayer::stop() { status = STOP; } void MoviePlayer::tick(float dt) { if(status == PLAY) { timer += dt; actual_frame = (int)(timer * fps + start_frame); if(actual_frame != frame_number) { if(actual_frame - frame_number == 1) this->getNextFrame(); else this->skipFrame(actual_frame - frame_number - 1); } } } GLuint MoviePlayer::getTexture() { return this->texture; } void MoviePlayer::setFPS(float fps) { if(fps > 0) this->fps = fps; } float MoviePlayer::getFPS() { return this->fps; } const MP_STATUS MoviePlayer::getStatus() { return this->status; } void MoviePlayer::printInformation() { if(!mediaLoaded) { PRINTF(0)("Load first the media file with loadMovie\n"); return; } PRINTF(0)("========================\n"); PRINTF(0)("========================\n"); PRINTF(0)("= MEDIACONTAINER =\n"); PRINTF(0)("========================\n"); PRINTF(0)("========================\n"); PRINTF(0)("= AVFormatContext =\n"); PRINTF(0)("========================\n"); PRINTF(0)("filename: %s\n", format_context->filename); PRINTF(0)("nb_streams: %i\n", format_context->nb_streams); PRINTF(0)("duration: (%02d:%02d:%02d)\n", duration/3600, (duration%3600)/60, duration%60); PRINTF(0)("file_size: %ikb\n", format_context->file_size/1024); PRINTF(0)("bit_rate: %ikb/s\n", format_context->bit_rate/1000); PRINTF(0)("nb_frames: %i\n", format_context->streams[video_stream]->nb_frames); PRINTF(0)("r_frame_rate: %i\n", format_context->streams[video_stream]->r_frame_rate.num); PRINTF(0)("fps: %0.2f\n", fps); PRINTF(0)("========================\n"); PRINTF(0)("= AVCodecContext =\n"); PRINTF(0)("========================\n"); PRINTF(0)("width: %i\n", codec_context->width); PRINTF(0)("height: %i\n", codec_context->height); PRINTF(0)("time_base.den: %i\n", codec_context->time_base.den); PRINTF(0)("time_base.num: %i\n", codec_context->time_base.num); PRINTF(0)("========================\n"); PRINTF(0)("= AVCodec =\n"); PRINTF(0)("========================\n"); PRINTF(0)("codec name: %s\n", codec->name); PRINTF(0)("========================\n"); PRINTF(0)("========================\n"); }