00001 /*!@file AppPsycho/psycho-movie2.C Psychophysics interactive display of movies */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/AppPsycho/psycho-movie2.C $ 00035 // $Id: psycho-movie2.C 14755 2011-04-29 05:55:18Z itti $ 00036 // 00037 00038 #include "Component/ModelManager.H" 00039 #include "Component/ModelOptionDef.H" 00040 #include "Audio/AudioWavFile.H" 00041 #include "Devices/AudioGrabber.H" 00042 #include "Devices/AudioMixer.H" 00043 #include "Devices/DeviceOpts.H" 00044 #include "Image/Image.H" 00045 #include "Media/MPEGStream.H" 00046 #include "Media/MediaOpts.H" 00047 #include "Psycho/PsychoDisplay.H" 00048 #include "Psycho/EyeTrackerConfigurator.H" 00049 #include "Psycho/EyeTracker.H" 00050 #include "Psycho/PsychoOpts.H" 00051 #include "Component/EventLog.H" 00052 #include "Component/ComponentOpts.H" 00053 #include "GUI/SDLdisplay.H" 00054 #include "Util/MathFunctions.H" 00055 #include "Util/Types.H" 00056 #include "Video/VideoFrame.H" 00057 #include "Neuro/NeuroOpts.H" 00058 #include "Image/DrawOps.H" 00059 #include "Image/ImageSet.H" 00060 #include "Image/ShapeOps.H" 00061 #include "GUI/GUIOpts.H" 00062 #include "Image/CutPaste.H" 00063 #include "Raster/Raster.H" 00064 00065 #include <vector> 00066 #include <pthread.h> 00067 00068 #define CACHELEN 150 00069 00070 volatile bool recordaudio = false; 00071 volatile bool keepgoing = true; 00072 volatile int recnb = 0; 00073 volatile bool audioA = false; 00074 volatile bool movingV = false; 00075 00076 static const ModelOptionDef OPT_AudioAfter = 00077 { MODOPT_FLAG, "AudioAfter", &MOC_DISPLAY, OPTEXP_SAVE, 00078 "Record audio after the movie presentation", 00079 "audio-after", '\0', "", "false" }; 00080 00081 static const ModelOptionDef OPT_MovingVideo = 00082 { MODOPT_FLAG, "MovingVideo", &MOC_DISPLAY, OPTEXP_SAVE, 00083 "Play the series of videos as moving videos or still videos", 00084 "moving-video", '\0', "", "false" }; 00085 00086 // ###################################################################### 00087 static void *audiorecorder(void *agbv) 00088 { 00089 bool recording = false; 00090 std::vector<AudioBuffer<byte> > rec; 00091 AudioGrabber *agb = (AudioGrabber *)agbv; 00092 00093 while(keepgoing) 00094 { 00095 // start recording? 00096 if (recording == false && recordaudio == true) 00097 { rec.clear(); recording = true; } 00098 00099 // stop recording? 00100 if (recording == true && recordaudio == false) 00101 { 00102 // save our current records 00103 char fname[100]; 00104 if (audioA == true) 00105 { 00106 if (movingV == true) 00107 sprintf(fname, "25sec_moving_audio%04d.wav", recnb); 00108 else 00109 sprintf(fname, "25sec_still_audio%04d.wav", recnb); 00110 } 00111 else 00112 { 00113 if (movingV == true) 00114 sprintf(fname, "3sec_moving_audio%04d.wav", recnb); 00115 else 00116 sprintf(fname, "3sec_still_audio%04d.wav", recnb); 00117 } 00118 writeAudioWavFile(fname, rec); 00119 00120 // ready for next recording: 00121 recnb ++; recording = false; 00122 } 00123 00124 // we grab the audio data all the time, so that it does not 00125 // start piling up into the internal buffers of the audio 00126 // grabber; but we will save it only if we are in recording 00127 // mode. Note: the call to grab() is blocking and hence sets 00128 // the pace of our loop here. With 256 samples at 11.025kHz 00129 // that's about 23ms: 00130 AudioBuffer<byte> data; 00131 agb->grab(data); 00132 if (data.nsamples() != 256U) 00133 LERROR("Recorded len %u is not 256!", data.nsamples()); 00134 00135 // continue recording? 00136 if (recording == true && recordaudio == true && data.nsamples() == 256U) 00137 // store in our queue of records: 00138 rec.push_back(data); 00139 } 00140 00141 // terminate thread: 00142 pthread_exit(0); 00143 return NULL; 00144 } 00145 00146 // ###################################################################### 00147 static bool cacheFrame(nub::soft_ref<InputMPEGStream>& mp, 00148 std::deque<VideoFrame>& cache) 00149 { 00150 const VideoFrame frame = mp->readVideoFrame(); 00151 if (!frame.initialized()) return false; // end of stream 00152 00153 cache.push_front(frame); 00154 return true; 00155 } 00156 00157 // ###################################################################### 00158 static int submain(const int argc, char** argv) 00159 { 00160 MYLOGVERB = LOG_INFO; // suppress debug messages 00161 00162 // Instantiate a ModelManager: 00163 ModelManager manager("Psycho Movie"); 00164 00165 OModelParam<bool> audioAfter(&OPT_AudioAfter, &manager); 00166 OModelParam<bool> movingVideo(&OPT_MovingVideo, &manager); 00167 00168 // Instantiate our various ModelComponents: 00169 nub::soft_ref<InputMPEGStream> mp 00170 (new InputMPEGStream(manager, "Input MPEG Stream", "InputMPEGStream")); 00171 manager.addSubComponent(mp); 00172 00173 nub::soft_ref<PsychoDisplay> d(new PsychoDisplay(manager)); 00174 manager.addSubComponent(d); 00175 00176 nub::soft_ref<AudioMixer> mix(new AudioMixer(manager)); 00177 manager.addSubComponent(mix); 00178 00179 nub::soft_ref<AudioGrabber> agb(new AudioGrabber(manager)); 00180 manager.addSubComponent(agb); 00181 00182 nub::soft_ref<EyeTrackerConfigurator> 00183 etc(new EyeTrackerConfigurator(manager)); 00184 manager.addSubComponent(etc); 00185 00186 nub::soft_ref<EventLog> el(new EventLog(manager)); 00187 manager.addSubComponent(el); 00188 00189 // set a few defaults: 00190 manager.setOptionValString(&OPT_InputMPEGStreamPreload, "true"); 00191 manager.setOptionValString(&OPT_EventLogFileName, "psychodata.psy"); 00192 manager.setOptionValString(&OPT_EyeTrackerType, "ISCAN"); 00193 manager.setOptionValString(&OPT_AudioMixerLineIn, "false"); 00194 manager.setOptionValString(&OPT_AudioMixerCdIn, "false"); 00195 manager.setOptionValString(&OPT_AudioMixerMicIn, "true"); 00196 manager.setOptionValString(&OPT_AudioGrabberBits, "8"); 00197 manager.setOptionValString(&OPT_AudioGrabberFreq, "11025"); 00198 manager.setOptionValString(&OPT_AudioGrabberBufSamples, "256"); 00199 manager.setOptionValString(&OPT_AudioGrabberChans, "1"); 00200 00201 // Parse command-line: 00202 if (manager.parseCommandLine(argc, argv, 00203 "<movie1.mpg> ... <movieN.mpg>", 1, -1)==false) 00204 return(1); 00205 00206 audioA = audioAfter.getVal(); 00207 movingV = movingVideo.getVal(); 00208 00209 // hook our various babies up and do post-command-line configs: 00210 nub::soft_ref<EyeTracker> et = etc->getET(); 00211 d->setEyeTracker(et); 00212 d->setEventLog(el); 00213 et->setEventLog(el); 00214 00215 // let's get all our ModelComponent instances started: 00216 manager.start(); 00217 00218 // let's display a static low-level ISCAN calibration grid: 00219 d->clearScreen(); 00220 d->displayISCANcalib(); 00221 d->waitForKey(); 00222 00223 // setup array of movie indices: 00224 uint nbmovies = manager.numExtraArgs(); int index[nbmovies]; 00225 for (uint i = 0; i < nbmovies; i ++) index[i] = i; 00226 LINFO("Randomizing movies..."); randShuffle(index,nbmovies); 00227 00228 // get the audio going: 00229 pthread_t runner; 00230 pthread_create(&runner, NULL, &audiorecorder, (void *)(agb.get())); 00231 char txt[100]; 00232 00233 // main loop: 00234 std::deque<VideoFrame> cache; 00235 00236 for (uint i = 0; i < nbmovies; i ++) 00237 { 00238 // let's do an eye-tracker calibration once in a while: 00239 int calibFreq = 10; 00240 00241 if(movingVideo.getVal() == false && audioAfter.getVal() == true) 00242 { 00243 calibFreq = 2; 00244 } 00245 00246 if ((i % calibFreq) == 0) 00247 { 00248 00249 d->displayText("<SPACE> for eye-tracker calibration"); 00250 int k = d->waitForKey(); 00251 if (k == ' ') et->calibrate(d); 00252 d->clearScreen(); 00253 if (i == 0) d->displayText("<SPACE> to start with the movies"); 00254 else d->displayText("<SPACE> to continue with the movies"); 00255 d->waitForKey(); 00256 } 00257 00258 // cache initial movie frames: 00259 d->clearScreen(); 00260 if (cache.size()) LFATAL("ooops, cache not empty?"); 00261 bool streaming = true; 00262 LINFO("Buffering '%s'...", manager.getExtraArg(index[i]).c_str()); 00263 // NOTE: we now specify --preload-mpeg=true with a 00264 // setOptionValString() a few lines up from here 00265 mp->setFileName(manager.getExtraArg(index[i])); 00266 for (uint j = 0; j < CACHELEN; j ++) 00267 { 00268 streaming = cacheFrame(mp, cache); 00269 if (streaming == false) break; // all movie frames got cached! 00270 } 00271 LINFO("'%s' ready.", manager.getExtraArg(index[i]).c_str()); 00272 00273 // give a chance to other processes (useful on single-CPU machines): 00274 sleep(1); if (system("sync")) LERROR("error in sync"); 00275 00276 // display fixation to indicate that we are ready: 00277 d->displayFixation(); 00278 00279 // ready to go whenever the user is ready: 00280 d->waitForKey(); int frame = 0; 00281 d->waitNextRequestedVsync(false, true); 00282 d->pushEvent(std::string("===== Playing movie: ") + 00283 manager.getExtraArg(index[i]) + " ====="); 00284 00285 // start the eye tracker: 00286 et->track(true); 00287 00288 // start audio recording, unless we were flagged to record later: 00289 if (audioAfter.getVal() == false) 00290 { 00291 sprintf(txt, "Start audio recording: audio%04d.wav", recnb); 00292 d->pushEvent(txt); 00293 recordaudio = true; 00294 } 00295 00296 // blink the fixation: 00297 d->displayFixationBlink(); 00298 00299 // create an overlay: 00300 d->createVideoOverlay(VIDFMT_YUV420P); // mpeg stream returns YUV420P 00301 00302 // play the movie: 00303 while(cache.size()) 00304 { 00305 // let's first cache one more frame: 00306 if (streaming) streaming = cacheFrame(mp, cache); 00307 00308 // get next frame to display and put it into our overlay: 00309 VideoFrame vidframe = cache.back(); 00310 d->displayVideoOverlay(vidframe, frame, 00311 SDLdisplay::NEXT_VSYNC); 00312 cache.pop_back(); 00313 00314 ++frame; 00315 } 00316 00317 // destroy the overlay. Somehow, mixing overlay displays and 00318 // normal displays does not work. With a single overlay created 00319 // before this loop and never destroyed, the first movie plays 00320 // ok but the other ones don't show up: 00321 d->destroyYUVoverlay(); 00322 d->clearScreen(); // sometimes 2 clearScreen() are necessary 00323 00324 00325 // do we want to record some audio after the movie is done? 00326 if (audioAfter.getVal()) 00327 { 00328 const int movieFreq = movingVideo.getVal() ? 5 : 1; 00329 00330 if (((i+1) % movieFreq) == 0) 00331 { 00332 d->displayText("Please describe what you saw in the last five videos."); 00333 sleep(1); 00334 00335 sprintf(txt, "Start audio recording after 5 movies: audio%04d.wav", recnb); 00336 d->pushEvent(txt); 00337 recordaudio = true; 00338 d->clearScreen(); 00339 d->displayFixation(); 00340 00341 usleep(25000000); // 25 secs of recording 00342 00343 // stop audio recording: 00344 sprintf(txt, "Stop audio recording after 5 movies: audio%04d.wav", recnb); 00345 d->pushEvent(txt); 00346 recordaudio = false; 00347 00348 d->clearScreen(); 00349 } 00350 } 00351 else 00352 { 00353 sleep(3); 00354 00355 // stop audio recording: 00356 sprintf(txt, "Stop audio recording: audio%04d.wav", recnb); 00357 d->pushEvent(txt); 00358 recordaudio = false; 00359 } 00360 00361 00362 // stop the eye tracker: 00363 et->track(false); 00364 } 00365 00366 d->clearScreen(); 00367 d->displayText("Experiment complete. Thank you!"); 00368 d->waitForKey(); 00369 00370 // kill the audio recording thread: 00371 keepgoing = false; 00372 sleep(1); if (system("sync")) LERROR("error in sync"); 00373 00374 // stop all our ModelComponents 00375 manager.stop(); 00376 00377 // all done! 00378 return 0; 00379 } 00380 00381 // ###################################################################### 00382 extern "C" int main(const int argc, char** argv) 00383 { 00384 // simple wrapper around submain() to catch exceptions (because we 00385 // want to allow PsychoDisplay to shut down cleanly; otherwise if we 00386 // abort while SDL is in fullscreen mode, the X server won't return 00387 // to its original resolution) 00388 try 00389 { 00390 return submain(argc, argv); 00391 } 00392 catch (...) 00393 { 00394 REPORT_CURRENT_EXCEPTION; 00395 } 00396 00397 return 1; 00398 } 00399 00400 // ###################################################################### 00401 /* So things look consistent in everyone's emacs... */ 00402 /* Local Variables: */ 00403 /* indent-tabs-mode: nil */ 00404 /* End: */