topical media & game development
lib-of-vs-libs-openFrameworks-video-ofVideoPlayer.cpp / cpp
include <ofVideoPlayer.h>
include <ofUtils.h>
ifdef TARGET_LINUX
#include <gst/video/video.h>
endif
//--------------------------------------------------------------
ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------------------------------
bool createMovieFromPath(char * path, Movie &movie);
bool createMovieFromPath(char * path, Movie &movie){
Boolean isdir = false;
OSErr result = 0;
FSSpec theFSSpec;
short actualResId = DoTheRightThing;
#ifdef TARGET_WIN32
result = NativePathNameToFSSpec (path, &theFSSpec, 0);
if (result != noErr) {
ofLog(OF_LOG_ERROR,"NativePathNameToFSSpec failed \%d", result);
ofLog(OF_LOG_ERROR,"Error loading movie");
return false;
}
#endif
#ifdef TARGET_OSX
FSRef fsref;
result = FSPathMakeRef((const UInt8*)path, &fsref, &isdir);
if (result) {
ofLog(OF_LOG_ERROR,"FSPathMakeRef failed \%d", result);
ofLog(OF_LOG_ERROR,"Error loading movie");
return false;
}
result = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &theFSSpec, NULL);
if (result) {
ofLog(OF_LOG_ERROR,"FSGetCatalogInfo failed \%d", result);
ofLog(OF_LOG_ERROR,"Error loading movie");
return false;
}
#endif
short movieResFile;
result = OpenMovieFile (&theFSSpec, &movieResFile, fsRdPerm);
if (result == noErr) {
short movieResID = 0;
result = NewMovieFromFile(&movie, movieResFile, &movieResID, (unsigned char *) 0, newMovieActive, (Boolean *) 0);
if (result == noErr){
CloseMovieFile (movieResFile);
} else {
ofLog(OF_LOG_ERROR,"NewMovieFromFile failed \%d", result);
return false;
}
} else {
ofLog(OF_LOG_ERROR,"OpenMovieFile failed \%d", result);
return false;
}
return true;
}
//--------------------------------------------------------------
bool createMovieFromURL(string urlIn, Movie &movie){
char * url = (char *)urlIn.c_str();
Handle urlDataRef;
OSErr err;
urlDataRef = NewHandle(strlen(url) + 1);
if ( ( err = MemError()) != noErr){ ofLog(OF_LOG_ERROR,"createMovieFromURL: error creating url handle"); return false;}
BlockMoveData(url, *urlDataRef, strlen(url) + 1);
err = NewMovieFromDataRef(&movie, newMovieActive,nil, urlDataRef, URLDataHandlerSubType);
DisposeHandle(urlDataRef);
if(err != noErr){
ofLog(OF_LOG_ERROR,"createMovieFromURL: error loading url");
return false;
}else{
return true;
}
return false;
}
//--------------------------------------------------------------
OSErr DrawCompleteProc(Movie theMovie, long refCon);
OSErr DrawCompleteProc(Movie theMovie, long refCon){
ofVideoPlayer * ofvp = (ofVideoPlayer *)refCon;
#if defined(TARGET_OSX) && defined(BIG_ENDIAN)
convertPixels(ofvp->offscreenGWorldPixels, ofvp->pixels, ofvp->width, ofvp->height);
#endif
ofvp->bHavePixelsChanged = true;
if (ofvp->bUseTexture == true){
ofvp->tex.loadData(ofvp->pixels, ofvp->width, ofvp->height, GL_RGB);
}
return noErr;
}
//--------------------------------------------------------------
else
//--------------------------------------------------------------
include "gst/app/gstappsink.h"
static bool plugin_registered = false;
static bool gst_inited = false;
//------------------------------------
void ofGstDataLock(ofGstVideoData * data){
pthread_mutex_lock( &(data->buffer_mutex) );
}
//------------------------------------
void ofGstDataUnlock(ofGstVideoData * data){
pthread_mutex_unlock( &(data->buffer_mutex) );
}
// called when the appsink notifies us that there is a new buffer ready for
// processing
static void
on_new_buffer_from_source (GstElement * elt, ofGstVideoData * data)
{
guint size;
GstBuffer *buffer;
//get the buffer from appsink
buffer = gst_app_sink_pull_buffer (GST_APP_SINK (elt));
size = GST_BUFFER_SIZE (buffer);
//ofLog(OF_LOG_VERBOSE,"new buffer of size \%d", size);
ofGstDataLock(data);
if(data->pixels){
memcpy (data->pixels, GST_BUFFER_DATA (buffer), size);
data->bHasPixelsChanged=true;
}
ofGstDataUnlock(data);
if( !data->nFrames && data->pipelineState==GST_STATE_PLAYING && data->speed==1 ){
data->nFrames=data->durationNanos/buffer->duration;
}
we don't need the appsink buffer anymore
gst_buffer_unref (buffer);
}
static gboolean
appsink_plugin_init (GstPlugin * plugin)
{
gst_element_register (plugin, "appsink", GST_RANK_NONE, GST_TYPE_APP_SINK);
return TRUE;
}
//--------------------------------------------------------------
endif
//--------------------------------------------------------------
//---------------------------------------------------------------------------
ofVideoPlayer::ofVideoPlayer (){
bLoaded = false;
width = 0;
height = 0;
speed = 1;
bUseTexture = true;
bStarted = false;
pixels = NULL;
nFrames = 0;
bPaused = false;
//--------------------------------------------------------------
#ifndef TARGET_LINUX // !linux = quicktime...
//--------------------------------------------------------------
moviePtr = NULL;
allocated = false;
offscreenGWorld = NULL;
//--------------------------------------------------------------
#else
//--------------------------------------------------------------
gstPipeline = NULL;
bIsFrameNew = false;
loopMode = OF_LOOP_NONE;
durationNanos = 0;
bIsMovieDone = false;
posChangingPaused = 0;
isStream = false;
gstData.durationNanos = 0;
gstData.nFrames = 0;
gstData.speed = speed;
pthread_mutex_init(&(gstData.buffer_mutex),NULL);
pthread_mutex_init(&seek_mutex,NULL);
if(!g_thread_supported()){
g_thread_init(NULL);
}
if(!gst_inited){
gst_init (NULL, NULL);
gst_inited=true;
}
if(!plugin_registered){
gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR,
"appsink", "Element application sink",
appsink_plugin_init, "0.1", "LGPL", "ofVideoPlayer", "openFrameworks",
"http://openframeworks.cc/");
plugin_registered=true;
}
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
}
//---------------------------------------------------------------------------
unsigned char * ofVideoPlayer::getPixels(){
return pixels;
}
//------------------------------------
//for getting a reference to the texture
ofTexture & ofVideoPlayer::getTextureReference(){
if(!tex.bAllocated() ){
ofLog(OF_LOG_WARNING, "ofVideoPlayer - getTextureReference - texture is not allocated");
}
return tex;
}
//---------------------------------------------------------------------------
bool ofVideoPlayer::isFrameNew(){
return bIsFrameNew;
}
//--------------------------------------------------------------------
void ofVideoPlayer::update(){
idleMovie();
}
//---------------------------------------------------------------------------
void ofVideoPlayer::idleMovie(){
//--------------------------------------------------------------
#ifndef TARGET_LINUX // !linux = quicktime...
//--------------------------------------------------------------
if (bLoaded == true){
#if defined(TARGET_WIN32) || defined(QT_USE_MOVIETASK)
MoviesTask(moviePtr,0);
#endif
//--------------------------------------------------------------
#else // linux.
//--------------------------------------------------------------
gstHandleMessage();
if (bLoaded == true){
ofGstDataLock(&gstData);
bHavePixelsChanged = gstData.bHasPixelsChanged;
if (bHavePixelsChanged){
gstData.bHasPixelsChanged=false;
bIsMovieDone = false;
if(bUseTexture)
tex.loadData(pixels, width, height, GL_RGB);
}
ofGstDataUnlock(&gstData);
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
// ---------------------------------------------------
// on all platforms,
// do "new"ness ever time we idle...
// before "isFrameNew" was clearning,
// people had issues with that...
// and it was badly named so now, newness happens
// per-idle not per isNew call
// ---------------------------------------------------
bIsFrameNew = bHavePixelsChanged;
if (bHavePixelsChanged == true) {
bHavePixelsChanged = false;
}
}
}
//---------------------------------------------------------------------------
void ofVideoPlayer::closeMovie(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
if (bLoaded == true){
DisposeMovie (moviePtr);
#ifdef TARGET_WIN32
DisposeMovieDrawingCompleteUPP (myDrawCompleteProc);
#endif
moviePtr = NULL;
}
//--------------------------------------
#else
//--------------------------------------
if(bLoaded){
gst_element_set_state(GST_ELEMENT(gstPipeline), GST_STATE_NULL);
//gst_object_unref(gstSink);
gst_object_unref(gstPipeline);
}
//--------------------------------------
#endif
//--------------------------------------
bLoaded = false;
}
//---------------------------------------------------------------------------
void ofVideoPlayer::close(){
closeMovie();
}
//---------------------------------------------------------------------------
ofVideoPlayer::~ofVideoPlayer(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
closeMovie();
if(allocated) delete[] pixels;
if(allocated) delete[] offscreenGWorldPixels;
if ((offscreenGWorld)) DisposeGWorld((offscreenGWorld));
//--------------------------------------
#else
//--------------------------------------
closeMovie();
if (pixels != NULL){
delete[] pixels;
}
//--------------------------------------
#endif
//--------------------------------------
tex.clear();
}
//--------------------------------------
ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
void ofVideoPlayer::createImgMemAndGWorld(){
Rect movieRect;
movieRect.top = 0;
movieRect.left = 0;
movieRect.bottom = height;
movieRect.right = width;
offscreenGWorldPixels = new unsigned char[4 * width * height + 32];
pixels = new unsigned char[width*height*3];
#if defined(TARGET_OSX) && defined(BIG_ENDIAN)
QTNewGWorldFromPtr (&(offscreenGWorld), k32ARGBPixelFormat, &(movieRect), NULL, NULL, 0, (offscreenGWorldPixels), 4 * width);
#else
QTNewGWorldFromPtr (&(offscreenGWorld), k24RGBPixelFormat, &(movieRect), NULL, NULL, 0, (pixels), 3 * width);
#endif
LockPixels(GetGWorldPixMap(offscreenGWorld));
SetGWorld (offscreenGWorld, NULL);
SetMovieGWorld (moviePtr, offscreenGWorld, nil);
//------------------------------------ texture stuff:
if (bUseTexture){
// create the texture, set the pixels to black and
// upload them to the texture (so at least we see nothing black the callback)
tex.allocate(width,height,GL_RGB);
memset(pixels, 0, width*height*3);
tex.loadData(pixels, width, height, GL_RGB);
allocated = true;
}
}
//--------------------------------------
endif
//--------------------------------------
//---------------------------------------------------------------------------
bool ofVideoPlayer::loadMovie(string name){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
initializeQuicktime(); // init quicktime
closeMovie(); // if we have a movie open, close it
bLoaded = false; // try to load now
if( name.substr(0, 7) == "http://" || name.substr(0,7) == "rtsp://" ){
if(! createMovieFromURL(name, moviePtr) ) return false;
}else{
name = ofToDataPath(name);
if( !createMovieFromPath((char *)name.c_str(), moviePtr) ) return false;
}
bool bDoWeAlreadyHaveAGworld = false;
if (width != 0 && height != 0){
bDoWeAlreadyHaveAGworld = true;
}
Rect movieRect;
GetMovieBox(moviePtr, &(movieRect));
if (bDoWeAlreadyHaveAGworld){
// is the gworld the same size, then lets *not* de-allocate and reallocate:
if (width == movieRect.right &&
height == movieRect.bottom){
SetMovieGWorld (moviePtr, offscreenGWorld, nil);
} else {
width = movieRect.right;
height = movieRect.bottom;
delete(pixels);
delete(offscreenGWorldPixels);
if ((offscreenGWorld)) DisposeGWorld((offscreenGWorld));
createImgMemAndGWorld();
}
} else {
width = movieRect.right;
height = movieRect.bottom;
createImgMemAndGWorld();
}
if (moviePtr == NULL){
return false;
}
//----------------- callback method
MovieDrawingCompleteUPP myDrawCompleteProc;
myDrawCompleteProc = NewMovieDrawingCompleteUPP (DrawCompleteProc);
SetMovieDrawingCompleteProc (moviePtr, movieDrawingCallWhenChanged, myDrawCompleteProc, (long)this);
// ------------- get the total # of frames:
nFrames = 0;
TimeValue curMovieTime;
curMovieTime = 0;
TimeValue duration;
//OSType whichMediaType = VIDEO_TYPE; // mingw chokes on this
OSType whichMediaType = FOUR_CHAR_CODE('vide');
short flags = nextTimeMediaSample + nextTimeEdgeOK;
while( curMovieTime >= 0 ) {
nFrames++;
GetMovieNextInterestingTime(moviePtr,flags,1,&whichMediaType,curMovieTime,0,&curMovieTime,&duration);
flags = nextTimeMediaSample;
}
nFrames--; // there's an extra time step at the end of themovie
// ------------- get some pixels in there ------
GoToBeginningOfMovie(moviePtr);
SetMovieActiveSegment(moviePtr, -1,-1);
MoviesTask(moviePtr,0);
#if defined(TARGET_OSX) && defined(BIG_ENDIAN)
convertPixels(offscreenGWorldPixels, pixels, width, height);
#endif
if (bUseTexture == true){
tex.loadData(pixels, width, height, GL_RGB);
}
bStarted = false;
bLoaded = true;
bPlaying = false;
bHavePixelsChanged = false;
speed = 1;
return true;
//--------------------------------------
#else
//--------------------------------------
bLoaded = false;
bPaused = true;
speed = 1.0f;
bHavePixelsChanged = false;
if( name.find( "://",0 ) == string::npos){
name = "file://"+ofToDataPath(name,true);
isStream = false;
}else{
isStream = true;
}
ofLog(OF_LOG_VERBOSE,"loading "+name);
gstData.loop = g_main_loop_new (NULL, FALSE);
gstPipeline = gst_element_factory_make("playbin","player");
g_object_set(G_OBJECT(gstPipeline), "uri", name.c_str(), NULL);
// create the oF appsink for video rgb without sync to clock
gstSink = gst_element_factory_make("appsink", NULL);
GstCaps *caps = gst_caps_new_simple("video/x-raw-rgb", NULL);
gst_app_sink_set_caps(GST_APP_SINK(gstSink), caps);
gst_caps_unref(caps);
gst_base_sink_set_sync(GST_BASE_SINK(gstSink), false);
g_object_set (G_OBJECT(gstPipeline),"video-sink",gstSink,NULL);
GstElement *audioSink = gst_element_factory_make("gconfaudiosink", NULL);
g_object_set (G_OBJECT(gstPipeline),"audio-sink",audioSink,NULL);
// pause the pipeline
if(gst_element_set_state(GST_ELEMENT(gstPipeline), GST_STATE_PAUSED) ==
GST_STATE_CHANGE_FAILURE) {
ofLog(OF_LOG_ERROR, "GStreamer: unable to set pipeline to paused\n");
gst_object_unref(gstPipeline);
return false;
}
gstData.pipeline=gstPipeline;
// set the appsink to emit signals to get eos and errors
g_object_set (G_OBJECT (gstSink), "emit-signals", TRUE, "sync", TRUE, NULL);
g_signal_connect (gstSink, "new-buffer", G_CALLBACK (on_new_buffer_from_source), &gstData);
if(!isStream){
return allocate();
}
// unreference all elements so they get deleted on close
gst_object_unref(audioSink);
return true;
//--------------------------------------
#endif
//--------------------------------------
}
//--------------------------------------------------------
void ofVideoPlayer::start(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
if (bLoaded == true && bStarted == false){
SetMovieActive(moviePtr, true);
//------------------ set the movie rate to default
//------------------ and preroll, so the first frames come correct
TimeValue timeNow = GetMovieTime(moviePtr, 0);
Fixed playRate = GetMoviePreferredRate(moviePtr); //Not being used!
PrerollMovie(moviePtr, timeNow, X2Fix(speed));
SetMovieRate(moviePtr, X2Fix(speed));
setLoopState(OF_LOOP_NORMAL);
// get some pixels in there right away:
MoviesTask(moviePtr,0);
#if defined(TARGET_OSX) && defined(BIG_ENDIAN)
convertPixels(offscreenGWorldPixels, pixels, width, height);
#endif
bHavePixelsChanged = true;
if (bUseTexture == true){
tex.loadData(pixels, width, height, GL_RGB);
}
bStarted = true;
bPlaying = true;
}
//--------------------------------------
#else
//--------------------------------------
bHavePixelsChanged = true;
bStarted = true;
bPlaying = true;
setPaused(false);
//--------------------------------------
#endif
//--------------------------------------
}
//--------------------------------------------------------
void ofVideoPlayer::play(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
if (!bStarted){
start();
}else {
// ------------ lower level "startMovie"
// ------------ use desired speed & time (-1,1,etc) to Preroll...
bPlaying = true;
TimeValue timeNow;
timeNow = GetMovieTime(moviePtr, nil);
PrerollMovie(moviePtr, timeNow, X2Fix(speed));
SetMovieRate(moviePtr, X2Fix(speed));
MoviesTask(moviePtr, 0);
}
//--------------------------------------
#else
//--------------------------------------
if (!bStarted){
start();
}else {
bPlaying = true;
setPaused(false);
}
//--------------------------------------
#endif
//--------------------------------------
}
//--------------------------------------------------------
void ofVideoPlayer::stop(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
StopMovie (moviePtr);
SetMovieActive (moviePtr, false);
bStarted = false;
//--------------------------------------
#else
//--------------------------------------
setPaused(true);
//--------------------------------------
#endif
//--------------------------------------
}
//--------------------------------------------------------
void ofVideoPlayer::setVolume(int volume){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
SetMovieVolume(moviePtr, volume);
//--------------------------------------
#else
//--------------------------------------
gdouble gvolume = CLAMP(volume,0,10);
g_object_set(G_OBJECT(gstPipeline), "volume", gvolume, NULL);
//--------------------------------------
#endif
//--------------------------------------
}
//--------------------------------------------------------
void ofVideoPlayer::setLoopState(int state){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
TimeBase myTimeBase;
long myFlags = 0L;
myTimeBase = GetMovieTimeBase(moviePtr);
myFlags = GetTimeBaseFlags(myTimeBase);
switch (state) {
case OF_LOOP_NORMAL:
myFlags |= loopTimeBase;
myFlags &= ~palindromeLoopTimeBase;
SetMoviePlayHints(moviePtr, hintsLoop, hintsLoop);
SetMoviePlayHints(moviePtr, 0L, hintsPalindrome);
break;
case OF_LOOP_PALINDROME:
myFlags |= loopTimeBase;
myFlags |= palindromeLoopTimeBase;
SetMoviePlayHints(moviePtr, hintsLoop, hintsLoop);
SetMoviePlayHints(moviePtr, hintsPalindrome, hintsPalindrome);
break;
case OF_LOOP_NONE:
default:
myFlags &= ~loopTimeBase;
myFlags &= ~palindromeLoopTimeBase;
SetMoviePlayHints(moviePtr, 0L, hintsLoop |
hintsPalindrome);
break;
}
SetTimeBaseFlags(myTimeBase, myFlags);
//--------------------------------------
#else
//--------------------------------------
loopMode = state;
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
void ofVideoPlayer::setPosition(float pct){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
TimeRecord tr;
tr.base = GetMovieTimeBase(moviePtr);
long total = GetMovieDuration(moviePtr );
long newPos = (long)((float)total * pct);
SetMovieTimeValue(moviePtr, newPos);
MoviesTask(moviePtr,0);
//--------------------------------------
#else
//--------------------------------------
//pct = CLAMP(pct, 0,1);// check between 0 and 1;
GstFormat format = GST_FORMAT_TIME;
GstSeekFlags flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_ACCURATE);
gint64 pos = (guint64)((double)pct*(double)durationNanos);
if(bPaused){
seek_lock();
gst_element_set_state (gstPipeline, GST_STATE_PLAYING);
posChangingPaused=true;
seek_unlock();
}
if(speed>0){
if(!gst_element_seek(GST_ELEMENT(gstPipeline),speed, format,
flags,
GST_SEEK_TYPE_SET,
pos,
GST_SEEK_TYPE_SET,
-1)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to change speed");
}
}else{
if(!gst_element_seek(GST_ELEMENT(gstPipeline),speed, format,
flags,
GST_SEEK_TYPE_SET,
0,
GST_SEEK_TYPE_SET,
pos)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to change speed");
}
}
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
void ofVideoPlayer::setFrame(int frame){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
// frame 0 = first frame...
// this is the simple way...
//float durationPerFrame = getDuration() / getTotalNumFrames();
// seems that freezing, doing this and unfreezing seems to work alot
// better then just SetMovieTimeValue() ;
if (!bPaused) SetMovieRate(moviePtr, X2Fix(0));
// this is better with mpeg, etc:
double frameRate = 0;
double movieTimeScale = 0;
MovieGetStaticFrameRate(moviePtr, &frameRate);
movieTimeScale = GetMovieTimeScale(moviePtr);
if (frameRate > 0){
double frameDuration = 1 / frameRate;
TimeValue t = (TimeValue)(frame * frameDuration * movieTimeScale);
SetMovieTimeValue(moviePtr, t);
MoviesTask(moviePtr, 0);
}
if (!bPaused) SetMovieRate(moviePtr, X2Fix(speed));
//--------------------------------------
#else
//--------------------------------------
float pct = (float)frame / (float)gstData.nFrames;
setPosition(pct);
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
float ofVideoPlayer::getDuration(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
return (float) (GetMovieDuration (moviePtr) / (double) GetMovieTimeScale (moviePtr));
//--------------------------------------
#else
//--------------------------------------
return (float)durationNanos/(float)GST_SECOND;
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
float ofVideoPlayer::getPosition(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
long total = GetMovieDuration(moviePtr);
long current = GetMovieTime(moviePtr, nil);
float pct = ((float)current/(float)total);
return pct;
//--------------------------------------
#else
//--------------------------------------
gint64 pos=0;
GstFormat format=GST_FORMAT_TIME;
if(!gst_element_query_position(GST_ELEMENT(gstPipeline),&format,&pos))
ofLog(OF_LOG_ERROR,"GStreamer: cannot query position");
return (float)pos/(float)durationNanos;
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
int ofVideoPlayer::getCurrentFrame(){
int frame = 0;
// zach I think this may fail on variable length frames...
float pos = getPosition();
float framePosInFloat = ((float)getTotalNumFrames() * pos);
int framePosInInt = (int)framePosInFloat;
float floatRemainder = (framePosInFloat - framePosInInt);
if (floatRemainder > 0.5f) framePosInInt = framePosInInt + 1;
//frame = (int)ceil((getTotalNumFrames() * getPosition()));
frame = framePosInInt;
return frame;
}
//---------------------------------------------------------------------------
bool ofVideoPlayer::getIsMovieDone(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
bool bIsMovieDone = (bool)IsMovieDone(moviePtr);
return bIsMovieDone;
//--------------------------------------
#else
//--------------------------------------
return bIsMovieDone;
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
void ofVideoPlayer::firstFrame(){
setFrame(0);
}
//---------------------------------------------------------------------------
void ofVideoPlayer::nextFrame(){
setFrame(getCurrentFrame() + 1);
}
//---------------------------------------------------------------------------
void ofVideoPlayer::previousFrame(){
setFrame(getCurrentFrame() - 1);
}
//---------------------------------------------------------------------------
void ofVideoPlayer::setSpeed(float _speed){
speed = _speed;
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
if (bPlaying == true){
//setMovieRate actually plays, so let's call it only when we are playing
SetMovieRate(moviePtr, X2Fix(speed));
}
//--------------------------------------
#else
//--------------------------------------
GstFormat format = GST_FORMAT_TIME;
GstSeekFlags flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH |GST_SEEK_FLAG_ACCURATE);
gint64 pos;
if(speed==0){
gst_element_set_state (gstPipeline, GST_STATE_PAUSED);
return;
}
if(!gst_element_query_position(GST_ELEMENT(gstPipeline),&format,&pos))
ofLog(OF_LOG_ERROR,"GStreamer: cannot query position");
if(!bPaused)
gst_element_set_state (gstPipeline, GST_STATE_PLAYING);
if(speed>0){
if(!gst_element_seek(GST_ELEMENT(gstPipeline),speed, format,
flags,
GST_SEEK_TYPE_SET,
pos,
GST_SEEK_TYPE_SET,
-1)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to change speed");
}
}else{
if(!gst_element_seek(GST_ELEMENT(gstPipeline),speed, format,
flags,
GST_SEEK_TYPE_SET,
0,
GST_SEEK_TYPE_SET,
pos)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to change speed");
}
}
ofLog(OF_LOG_VERBOSE,"Gstreamer: speed change to \%f", speed);
//--------------------------------------
#endif
//--------------------------------------
}
//---------------------------------------------------------------------------
float ofVideoPlayer::getSpeed(){
return speed;
}
//---------------------------------------------------------------------------
void ofVideoPlayer::setPaused(bool _bPause){
bPaused = _bPause;
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
// there might be a more "quicktime-ish" way (or smarter way)
// to do this for now, to pause, just set the movie's speed to zero,
// on un-pause, set the movie's speed to "speed"
// (and hope that speed != 0...)
if (bPlaying == true){
if (bPaused == true) SetMovieRate(moviePtr, X2Fix(0));
else SetMovieRate(moviePtr, X2Fix(speed));
}
//--------------------------------------
#else
//--------------------------------------
//timeLastIdle = ofGetElapsedTimeMillis();
if(bLoaded){
if(bPaused)
gst_element_set_state (gstPipeline, GST_STATE_PAUSED);
else
gst_element_set_state (gstPipeline, GST_STATE_PLAYING);
}
//--------------------------------------
#endif
//--------------------------------------
}
//------------------------------------
void ofVideoPlayer::setUseTexture(bool bUse){
bUseTexture = bUse;
}
//we could cap these values - but it might be more useful
//to be able to set anchor points outside the image
//----------------------------------------------------------
void ofVideoPlayer::setAnchorPercent(float xPct, float yPct){
if (bUseTexture)tex.setAnchorPercent(xPct, yPct);
}
//----------------------------------------------------------
void ofVideoPlayer::setAnchorPoint(int x, int y){
if (bUseTexture)tex.setAnchorPoint(x, y);
}
//----------------------------------------------------------
void ofVideoPlayer::resetAnchor(){
if (bUseTexture)tex.resetAnchor();
}
//------------------------------------
void ofVideoPlayer::draw(float _x, float _y, float _w, float _h){
if (bUseTexture){
tex.draw(_x, _y, _w, _h);
}
}
//------------------------------------
void ofVideoPlayer::draw(float _x, float _y){
draw(_x, _y, (float)width, (float)height);
}
//------------------------------------
int ofVideoPlayer::getTotalNumFrames(){
//--------------------------------------
#ifdef OF_VIDEO_PLAYER_QUICKTIME
//--------------------------------------
return nFrames;
//--------------------------------------
#else
//--------------------------------------
return gstData.nFrames;
//--------------------------------------
#endif
//--------------------------------------
}
//----------------------------------------------------------
float ofVideoPlayer::getHeight(){
return (float)height;
}
//----------------------------------------------------------
float ofVideoPlayer::getWidth(){
return (float)width;
}
//--------------------------------------
ifdef OF_VIDEO_PLAYER_GSTREAMER
//--------------------------------------
//----------------------------------------------------------
bool ofVideoPlayer::allocate(){
// wait for paused state to query the duration
GstState state = GST_STATE_PAUSED;
gst_element_get_state(gstPipeline,&state,NULL,2*GST_SECOND);
GstFormat format=GST_FORMAT_TIME;
if(!gst_element_query_duration(gstPipeline,&format,&durationNanos))
ofLog(OF_LOG_WARNING,"GStreamer: cannot query time duration");
gstData.durationNanos = durationNanos;
gstData.nFrames = 0;
// query width, height, fps and do data allocation
if (GstPad* pad = gst_element_get_static_pad(gstSink, "sink")) {
if(gst_video_get_size(GST_PAD(pad), &width, &height) && bUseTexture){
pixels=new unsigned char[width*height*3];
gstData.pixels=pixels;
memset(pixels,0,width*height*3);
tex.allocate(width,height,GL_RGB);
tex.loadData(pixels,width,height,GL_RGB);
allocated = true;
}else{
ofLog(OF_LOG_ERROR,"GStreamer: cannot query width and height");
return false;
}
/*GstCaps * caps = gst_pad_get_caps(pad);
if(caps){
int fps_n;
int fps_d;
if(gst_video_parse_caps_framerate (caps,&fps_n,&fps_d))
ofLog(OF_LOG_VERBOSE,"fps_n:\%d fps_d:\%d",fps_n,fps_d);
else
ofLog(OF_LOG_WARNING,"Gstreamer: cannot get framerate, frame seek won't work");
}else{
ofLog(OF_LOG_WARNING,"Gstreamer: cannot get pad caps, frame seek won't work");
}*/
gst_object_unref(GST_OBJECT(pad));
}else{
ofLog(OF_LOG_ERROR,"GStreamer: cannot get sink pad");
return false;
}
bLoaded = true;
return bLoaded;
}
//----------------------------------------------------------
void ofVideoPlayer::gstHandleMessage()
{
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(gstPipeline));
while(gst_bus_have_pending(bus)) {
GstMessage* msg = gst_bus_pop(bus);
ofLog(OF_LOG_VERBOSE,"GStreamer: Got \%s message", GST_MESSAGE_TYPE_NAME(msg));
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_BUFFERING:
gint pctBuffered;
gst_message_parse_buffering(msg,&pctBuffered);
ofLog(OF_LOG_VERBOSE,"GStreamer: buffering \%i%", pctBuffered);
if(isStream && !bLoaded){
allocate();
}
if(pctBuffered<100){
gst_element_set_state (gstPipeline, GST_STATE_PAUSED);
}else if(!bPaused){
gst_element_set_state (gstPipeline, GST_STATE_PLAYING);
}
break;
case GST_MESSAGE_DURATION:{
GstFormat format=GST_FORMAT_TIME;
if(!gst_element_query_duration(gstPipeline,&format,&durationNanos))
ofLog(OF_LOG_WARNING,"GStreamer: cannot query duration");
}break;
case GST_MESSAGE_STATE_CHANGED:
GstState oldstate, newstate, pendstate;
gst_message_parse_state_changed(msg, &oldstate, &newstate, &pendstate);
gstData.pipelineState=newstate;
seek_lock();
if(posChangingPaused && newstate==GST_STATE_PLAYING){
gst_element_set_state (gstPipeline, GST_STATE_PAUSED);
posChangingPaused=false;
}
seek_unlock();
ofLog(OF_LOG_VERBOSE,"GStreamer: state changed from \%d to \%d (\%d)", oldstate, newstate, pendstate);
break;
case GST_MESSAGE_ASYNC_DONE:
gstData.speed=speed;
ofLog(OF_LOG_VERBOSE,"GStreamer: async done");
break;
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;
gst_message_parse_error(msg, &err, &debug);
ofLog(OF_LOG_ERROR, "GStreamer Plugin: Embedded video playback halted; module \%s reported: \%s",
gst_element_get_name(GST_MESSAGE_SRC (msg)), err->message);
g_error_free(err);
g_free(debug);
gst_element_set_state(GST_ELEMENT(gstPipeline), GST_STATE_NULL);
}break;
case GST_MESSAGE_EOS:
ofLog(OF_LOG_VERBOSE,"GStreamer: end of the stream.");
bIsMovieDone = true;
switch(loopMode){
case OF_LOOP_NORMAL:{
GstFormat format = GST_FORMAT_TIME;
GstSeekFlags flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH |GST_SEEK_FLAG_KEY_UNIT);
gint64 pos;
gst_element_query_position(GST_ELEMENT(gstPipeline),&format,&pos);
float loopSpeed;
if(pos>0)
loopSpeed=-speed;
else
loopSpeed=speed;
if(!gst_element_seek(GST_ELEMENT(gstPipeline),
speed,
format,
flags,
GST_SEEK_TYPE_SET,
0,
GST_SEEK_TYPE_SET,
durationNanos)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to seek");
}
}break;
case OF_LOOP_PALINDROME:{
GstFormat format = GST_FORMAT_TIME;
GstSeekFlags flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH |GST_SEEK_FLAG_KEY_UNIT);
gint64 pos;
gst_element_query_position(GST_ELEMENT(gstPipeline),&format,&pos);
float loopSpeed;
if(pos>0)
loopSpeed=-speed;
else
loopSpeed=speed;
if(!gst_element_seek(GST_ELEMENT(gstPipeline),
loopSpeed,
GST_FORMAT_UNDEFINED,
flags,
GST_SEEK_TYPE_NONE,
0,
GST_SEEK_TYPE_NONE,
0)) {
ofLog(OF_LOG_WARNING,"GStreamer: unable to seek");
}
}break;
}
break;
default:
ofLog(OF_LOG_VERBOSE,"GStreamer: unhandled message");
break;
}
gst_message_unref(msg);
}
gst_object_unref(GST_OBJECT(bus));
}
void ofVideoPlayer::seek_lock(){
pthread_mutex_lock( &seek_mutex );
}
void ofVideoPlayer::seek_unlock(){
pthread_mutex_unlock( &seek_mutex );
}
//--------------------------------------
endif
//--------------------------------------
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.