13.2. MPEG Renderer

  • SoMPEGRenderer

    • SoMPEGNavRenderer

    • SoMPEGFrameRenderer

Figure 13.1. MPEG output classes



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.

Generate MPEG video output from a scene graph.
Generate MPEG video output from a scene graph.
Generate MPEG video output from a scene graph.

Figure 13.2. Generate MPEG video output from a scene graph.



Common Parameters

The class SoMPEGRenderer SoMPEGRenderer SoMPEGRenderer encapsulates several common parameters, described below.

Specify rendering options:

  • 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.

Specify MPEG output name:

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().

Specify the scene graph:

Use the method SoMPEGRenderer::setSceneGraph() to specify the scene graph used for generating your video file.

Recording a Video:

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.3. 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.

Using SoMPEGNavRenderer

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.4. 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();
	}
}
     
            
          


Using SoMPEGFrameRenderer

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.5. 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);
}