Open Inventor allows you to generate an MPEG output file from a scene graph.
With the SoMPEGRenderer SoMPEGRenderer SoMPEGRenderer , SoMPEGNavRenderer SoMPEGNavRenderer SoMPEGNavRenderer and SoMPEGFrameRenderer SoMPEGFrameRenderer SoMPEGFrameRenderer classes, you have three ways to generate an MPEG-1 video file from your application. The class SoMPEGRenderer SoMPEGRenderer SoMPEGRenderer simplifies the recording process with methods which automate it. The class SoNavMPEGRenderer registers all camera motions in your MPEG file. The class SoMPEGFrameRenderer SoMPEGFrameRenderer SoMPEGFrameRenderer allows you to generate your video file frame by frame.
The class SoMPEGRenderer SoMPEGRenderer SoMPEGRenderer encapsulates several common parameters, described below.
Background color of your video file:
Use SoMPEGRenderer::setBackgroundColor().
Dimensions (width, height) of your video file:
Use SoMPEGRenderer::setSize().
The width and the height of your video must be a multiple of 16, otherwise they are changed to a lower value that is a multiple of 16. |
Compression rate:
Use SoMPEGRenderer::setCompressionRate(). A value of 0 requests no compression; a value of 1 requests maximum compression.
Number of encoded frames per second:
Use SoMPEGRenderer::setNumFramesPerSecond(). This information is stored in your MPEG file and will be set the number of frames recorded for one second video. The possible values are 24, 25, 30 and 60 frames per second and will impact the size of the video.
Specific rendering attributes:
Use SoMPEGRenderer::setGLRenderAction(). If this action is not specified, an SoGLRenderAction SoGLRenderAction SoGLRenderAction is created automatically.
You can specify a file name with the method SoMPEGRenderer::openFile() or a file pointer with the method SoMPEGRenderer::setFilePointer().
When using the method SoMPEGRenderer::openFile(), do not forget to close your MPEG file with the method SoMPEGRenderer::closeFile(). |
Use the method SoMPEGRenderer::setSceneGraph() to specify the scene graph used for generating your video file.
In order to simplify the recording of a video, new methods have been introduced. These methods allow you to start, pause and stop a recording process. The frames added to the video with the SoMPEGRenderer::addFrame() method will be encoded in a separate thread in order to improve performance.
The renderer can be plugged into a render area or viewer, which will automatically send new drawn frames to it when the recording is started. The following code demonstrates this possibility.
Example 13.1. Plugging an MPEGRenderer into a render area
// Create the render area SoXxRenderArea* myRA = new SoXxRenderArea( mainWidget ); // Create the renderer SoMPEGRenderer* mpgRec = new SoMPEGRenderer(); // Set it in the render area myRA->setMPEGRecorder( mpgRec ); // Start the recording mpgRec->openFile( “test.mpg” ); mpgRec->record(); // Do some interactions with the render area mpgRec->stop(); // The recorder will be destroyed by the render area.
Note that this functionality is accessible from the popup menu of the viewers. It provides a user interface to set up the recording. If no renderer was specified, the viewer will create it. |
This class records all camera changes, then generates an MPEG output file. After specifying your MPEG file name, your scene graph, and other rendering options (see the section called “Common Parameters”), call SoMPEGNavRenderer::record() to record camera changes. Finally, call SoMPEGNavRenderer::stop() to stop recording and generate the MPEG output file.
Generating the MPEG output (after the call to SoMPEGNavRenderer::stop()) may take several seconds or even minutes depending on the length of your video. To give you an idea of the time it may take, at least 3 minutes are necessary for obtaining 1 minute of video with 25 frames per second. |
The example supplied in $OIVHOME/src/Inventor/examples/Features/MPEGRender/MPEGNavRenderer.cxx shows an example of use of SoMPEGNavRenderer SoMPEGNavRenderer SoMPEGNavRenderer . The following is a code fragment extract from this example.
Example 13.2. Extract of using SoMPEGNavRenderer
void myKeyPressCB(void *, SoEventCallback *eventCB) { const SoEvent *event = eventCB->getEvent(); // check for the keys being pressed if (SO_KEY_PRESS_EVENT(event, R)) { MPEGRenderer->record(); } else if (SO_KEY_PRESS_EVENT(event, S)) { MPEGRenderer->stop(); } else if (SO_KEY_PRESS_EVENT(event, C)) { MPEGRenderer->closeFile(); delete MPEGRenderer; exit(0); } } int main(int , char **argv) { // Initialize Inventor and Xt Widget myWindow = SoXt::init("MPEGFrameRenderer"); // Build the viewer in the applications main window SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow); // Read the geometry from a file and add to the scene SoInput myInput; if (!myInput.openFile("../../../data/jumpyMan.iv")) exit (1); SoSeparator *geomObject = SoDB::readAll(&myInput); if (geomObject == NULL) exit (1); // Build the scene graph SoSeparator *root = new SoSeparator; root->ref(); SoPerspectiveCamera *camera = new SoPerspectiveCamera; root->addChild(camera); root->addChild(geomObject); // Track the keyboard events SoEventCallback *myEventCB = new SoEventCallback; myEventCB->addEventCallback(SoKeyboardEvent::getClassTypeId(), myKeyPressCB, myViewer); root->addChild(myEventCB); // Attach the viewer to the scene graph myViewer->setSceneGraph(root); camera->viewAll(geomObject, myViewer->getViewportRegion()); // Create the MPEG renderer MPEGRenderer = new SoMPEGNavRenderer(myViewer->getSceneManager()->getSceneGraph()); MPEGRenderer->setSize (SbVec2s (300, 300)); MPEGRenderer->adjustNumFramesPerSecond(TRUE); int i = 0; char mpegFileName[20] = "MPEGOutput.mpg"; while (!MPEGRenderer->openFile(mpegFileName)) { sprintf(mpegFileName, "MPEGOutput%d.mpg", i); i++; } // Show the main window myViewer->show(); SoXt::show(myWindow); // Loop forever SoXt::mainLoop(); return 0; }
private SoSeparator _root; private SoWinExaminerViewer _viewer; private SoSwitch InfoSwitch; private string _currentPathData; private SoMPEGNavRenderer MPEGRenderer; [STAThread] static void Main() { MPEGNavRenderer demo = new MPEGNavRenderer(); demo.Start(); Application.Run(demo); } public void LaunchDemo() { try { _viewer = new SoWinExaminerViewer(_parent, "Name", true, SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER); } catch(Exception ex) { MessageBox.Show(ex.ToString()); } _root = new SoSeparator(); // SoGradientBackground bg = new SoGradientBackground(); // _root.AddChild(bg); SoMaterial material = new SoMaterial(); material.diffuseColor.SetValue(0.0f,0.0f,1.0f); _root.AddChild(material); SoSeparator geomObject = SoDB.ReadAll(_currentPathData + "/jumpyMan.iv"); // Build the scene graph InfoSwitch = DisplayInfo() ; _root.AddChild(InfoSwitch) ; SoPerspectiveCamera camera = new SoPerspectiveCamera() ; _root.AddChild(camera) ; _root.AddChild(geomObject) ; // Track the keyboard events SoEventCallback eventCB = new SoEventCallback(); eventCB.AddEventCallback(typeof (SoKeyboardEvent), new SoEventCallback.EventCB(keyCB)); _root.AddChild(eventCB); _viewer.SetSceneGraph(_root); _viewer.ViewAll(); camera.ViewAll(geomObject, _viewer.GetViewportRegion()); // Create the MPEG renderer MPEGRenderer = new SoMPEGNavRenderer(_viewer.GetSceneGraph()); MPEGRenderer.SetSize (new SbVec2s (300, 300)); MPEGRenderer.AdjustNumFramesPerSecond(true); MPEGRenderer.SetBitPerSec(-1); MPEGRenderer.SetCompressionRate(0.0f); int i = 1; while(File.Exists("MPEGOutput" + i + ".mpg")) i++; MPEGRenderer.OpenFile("MPEGOutput" + i + ".mpg"); } public void Start() { LaunchDemo(); //Show(); } public bool Init(Panel parent) { /* The demo Launcher always give a parent */ /* If we don't want to put the application on demoLauncher, we just need to */ /* Comment the setParent line and uncomment the show method*/ SetParent(parent); return true; } public void Stop() { HideViewer(); Close(); } public void SetParent(Panel P) { _parent = P; } public void HideViewer() { _viewer.Hide(); } private SoSwitch DisplayInfo() { // Informations SoSwitch infoSwitch = new SoSwitch() ; infoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_ALL; SoSeparator infoSep = new SoSeparator() ; infoSwitch.AddChild(infoSep) ; SoLightModel lModel = new SoLightModel() ; lModel.model.Value = SoLightModel.Models.BASE_COLOR; infoSep.AddChild(lModel) ; SoFont fontInfo = new SoFont() ; fontInfo.size.SetValue(12.0f) ; infoSep.AddChild(fontInfo) ; SoBaseColor infoColor = new SoBaseColor() ; infoColor.rgb.SetValue(new SbColor(1, 1, 0)) ; infoSep.AddChild(infoColor) ; SoTranslation transInfo = new SoTranslation() ; transInfo.translation.SetValue(-0.95f, 0.95f, 0.0f) ; infoSep.AddChild(transInfo) ; SoText2 infoText = new SoText2() ; string[] display = new string[4] { "H : Toggle this display ", "R : Record camera movements to the MPEG file ", "S : Stop recording ", "C : Generate the file MPEGOutputx.mpg and Exit " }; infoText.stringField.SetValues(0,display); infoSep.AddChild(infoText) ; return infoSwitch ; } private void keyCB(SoEventCallback sender) { SoKeyboardEvent evt = (SoKeyboardEvent) sender.GetEvent(); if (!SoKeyboardEvent.IsKeyReleaseEvent(evt, evt.GetKey())) return; if (evt.GetKey() == SoKeyboardEvent.Keys.H) { if(InfoSwitch.whichChild.Value == SoSwitch.WhichChild.SO_SWITCH_ALL) InfoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_NONE; else InfoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_ALL; } else if (evt.GetKey() == SoKeyboardEvent.Keys.R) { MPEGRenderer.Record(); } else if (evt.GetKey() == SoKeyboardEvent.Keys.S) { MPEGRenderer.Stop(); } else if (evt.GetKey() == SoKeyboardEvent.Keys.C) { MPEGRenderer.CloseFile(); this.Close(); } }
This class generates an MPEG movie, frame by frame. After specifying the MPEG file name, the scene graph, and other rendering options (see the section called “Common Parameters”), call SoMPEGFrameRenderer::recordFrame(float duration) to record a new frame in your MPEG video file. The duration field indicates how long to play the frame.
The example supplied in $OIVHOME/src/Inventor/examples/Features/MPEGRender/MPEGFrameRenderer.cxx shows an example of use of SoMPEGFrameRenderer SoMPEGFrameRenderer SoMPEGFrameRenderer . The following is a code fragment extract from this example.
Example 13.3. Generate an MPEG file, frame by frame
void myKeyPressCB (void *, SoEventCallback *eventCB) { const SoEvent *event = eventCB->getEvent(); // check for the keys being pressed if (SO_KEY_PRESS_EVENT(event, A)) { MPEGRenderer->recordFrame(2); } else if (SO_KEY_PRESS_EVENT(event, C)) { MPEGRenderer->closeFile(); delete MPEGRenderer; exit(0); } } int main(int , char **argv) { // Initialize Inventor and Xt Widget myWindow = SoXt::init("MPEGFrameRenderer"); // Build the viewer in the applications main window SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow); // Read the geometry from a file and add to the scene SoInput myInput; if (!myInput.openFile("../../../data/jumpyMan.iv")) exit (1); SoSeparator *geomObject = SoDB::readAll(&myInput); if (geomObject == NULL) exit (1); // Create the MPEG renderer MPEGRenderer = new SoMPEGFrameRenderer (); MPEGRenderer->setSize (SbVec2s (300, 300)); int i = 0; char mpegFileName[20] = "MPEGOutput.mpg"; while (!MPEGRenderer->openFile(mpegFileName)) { sprintf(mpegFileName, "MPEGOutput%d.mpg", i); i++; } // Build the scene graph SoSeparator *root = new SoSeparator; root->ref(); SoPerspectiveCamera *camera = new SoPerspectiveCamera; root->addChild(camera); root->addChild(geomObject); // Track the keyboard events SoEventCallback *myEventCB = new SoEventCallback; myEventCB->addEventCallback(SoKeyboardEvent::getClassTypeId(), myKeyPressCB, myViewer); root->addChild(myEventCB); // Attach the viewer to the scene graph myViewer->setSceneGraph(root); MPEGRenderer->setSceneGraph(myViewer->getSceneManager()->getSceneGraph()); camera->viewAll(geomObject, myViewer->getViewportRegion()); myViewer->show(); SoXt::show(myWindow); // Show the main window SoXt::mainLoop(); // Loop forever return 0; }
private SoSwitch InfoSwitch; private SoMPEGFrameRenderer MPEGRenderer; private SoSeparator _root; private string _currentPathData; private SoWinExaminerViewer _viewer; [STAThread] static void Main() { MPEGFrameRenderer demo = new MPEGFrameRenderer(); demo.Start(); Application.Run(demo); } public void LaunchDemo() { try { _viewer = new SoWinExaminerViewer(_parent, "Name", true, SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER); } catch(Exception ex) { MessageBox.Show(ex.ToString()); } // Create the MPEG renderer MPEGRenderer = new SoMPEGFrameRenderer(); MPEGRenderer.SetSize (new SbVec2s (300, 300)); MPEGRenderer.SetBitPerSec(-1); MPEGRenderer.SetCompressionRate(0.0f); int i = 1; while(File.Exists("MPEGOutput" + i + ".mpg")) i++; MPEGRenderer.OpenFile("MPEGOutput" + i + ".mpg"); _root = new SoSeparator(); // SoGradientBackground bg = new SoGradientBackground(); // _root.AddChild(bg); SoMaterial material = new SoMaterial(); material.diffuseColor.SetValue(0.0f,0.0f,1.0f); _root.AddChild(material); SoSeparator geomObject = SoDB.ReadAll(_currentPathData + "/jumpyMan.iv"); // Build the scene graph InfoSwitch = DisplayInfo() ; _root.AddChild(InfoSwitch) ; SoPerspectiveCamera camera = new SoPerspectiveCamera() ; _root.AddChild(camera) ; _root.AddChild(geomObject) ; // Track the keyboard events SoEventCallback eventCB = new SoEventCallback(); eventCB.AddEventCallback(typeof (SoKeyboardEvent), new SoEventCallback.EventCB(keyCB)); _root.AddChild(eventCB); _viewer.SetSceneGraph(_root); _viewer.ViewAll(); MPEGRenderer.SetSceneGraph(_viewer.GetSceneGraph()); camera.ViewAll(geomObject, _viewer.GetViewportRegion()); } public void Start() { LaunchDemo(); //Show(); } public bool Init(Panel parent) { /* The demo Launcher always give a parent */ /* If we don't want to put the application on demoLauncher, we just need to */ /* Comment the setParent line and uncomment the show method*/ SetParent(parent); return true; } public void Stop() { HideViewer(); Close(); } public void SetParent(Panel P) { _parent = P; } public void HideViewer() { _viewer.Hide(); } private SoSwitch DisplayInfo() { // Informations SoSwitch infoSwitch = new SoSwitch() ; infoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_ALL; SoSeparator infoSep = new SoSeparator() ; infoSwitch.AddChild(infoSep) ; SoLightModel lModel = new SoLightModel() ; lModel.model.Value = SoLightModel.Models.BASE_COLOR; infoSep.AddChild(lModel) ; SoFont fontInfo = new SoFont() ; fontInfo.size.Value = 12.0f ; infoSep.AddChild(fontInfo) ; SoBaseColor infoColor = new SoBaseColor() ; infoColor.rgb.SetValue(new SbColor(1, 1, 0)) ; infoSep.AddChild(infoColor) ; SoTranslation transInfo = new SoTranslation() ; transInfo.translation.SetValue(-0.95f, 0.95f, 0.0f) ; infoSep.AddChild(transInfo) ; SoText2 infoText = new SoText2() ; string[] display = new string[3] { "H : Toggle this display ", "A : Add a new frame to the MPEG file", "C : Generate the file MPEGOutputx.mpg and Exit " }; infoText.stringField.SetValues(0,display); infoSep.AddChild(infoText) ; return infoSwitch ; } private void keyCB(SoEventCallback sender) { SoKeyboardEvent evt = (SoKeyboardEvent) sender.GetEvent(); if (!SoKeyboardEvent.IsKeyReleaseEvent(evt, evt.GetKey())) return; if (evt.GetKey() == SoKeyboardEvent.Keys.H) { if(InfoSwitch.whichChild.Value == SoSwitch.WhichChild.SO_SWITCH_ALL) InfoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_NONE; else InfoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_ALL; } else if (evt.GetKey() == SoKeyboardEvent.Keys.A) { int savedSwitchValue = InfoSwitch.whichChild.Value ; InfoSwitch.whichChild.Value = SoSwitch.WhichChild.SO_SWITCH_NONE; MPEGRenderer.RecordFrame(2); InfoSwitch.whichChild.Value = savedSwitchValue; } else if (evt.GetKey() == SoKeyboardEvent.Keys.C) { MPEGRenderer.CloseFile(); this.Close(); } }
class ProcessKeyEvents extends SoEventCallbackCB { public void invoke(SoEventCallback event) { // check for the keys being pressed if ( SoKeyboardEvent.isKeyPressEvent(event.getEvent(), SoKeyboardEvent.Keys.P) ) { MPEGRenderer.recordFrame(2); event.setHandled(); } else if ( SoKeyboardEvent.isKeyPressEvent(event.getEvent(), SoKeyboardEvent.Keys.C) ) { MPEGRenderer.closeFile(); event.setHandled(); } } } @Override public void start() { super.start(); setLayout(new BorderLayout()); Panel panel = new Panel(new BorderLayout()); myViewer = new SwSimpleViewer(SwSimpleViewer.EXAMINER); // Build the viewer in the applications main window myViewer = new SwSimpleViewer(SwSceneViewer.EXAMINER); // Read the geometry from a file and add to the scene SoInput myInput = new SoInput(); if ( !myInput.openFile("../../../../data/jumpyMan.iv") ) return; SoSeparator geomObject = SoDB.readAll(myInput); if ( geomObject == null ) return; // Create the MPEG renderer MPEGRenderer = new SoMPEGFrameRenderer(); MPEGRenderer.setSize(new SbVec2s((short) 300, (short) 300)); int i = 0; String mpegFileName = "MPEGOutput.mpg"; while ( !MPEGRenderer.openFile(mpegFileName) ) { mpegFileName = "MPEGOutput" + i + ".mpg"; i++; } // Build the scene graph SoSeparator root = new SoSeparator(); SoPerspectiveCamera camera = new SoPerspectiveCamera(); root.addChild(camera); root.addChild(geomObject); // Track the keyboard events SoEventCallback myEventCB = new SoEventCallback(); myEventCB.addEventCallback(SoKeyboardEvent.class, new ProcessKeyEvents(), myViewer); root.addChild(myEventCB); // Create a viewer myViewer = new SwSimpleViewer(SwSimpleViewer.EXAMINER); // attach and show viewer myViewer.setSceneGraph(root); MPEGRenderer.setSceneGraph(myViewer.getScene().getSceneGraph()); panel.add(myViewer); add(panel); }