HOWTO: Decode to Texture

Starboard declares the interfaces necessary to allow applications to query for video frames from the media player, and have them returned as texture objects (e.g. GLES textures). This is useful if the application would like to apply a geometrical transformation to the rendered video, in order to support 360 spherical video playback for example. Additionally, if a Starboard platform implementation does not support punch-through video playback, then applications can choose to use decode-to-texture instead.

API Overview

Decode-to-texture support involves multiple Starboard API functions spanning both the starboard/player.h and starboard/decode_target.h Starboard interface header files. Support for decode-to-texture began in version 4 of the Starboard API.

In particular, the following function implementations require consideration for decode-to-texture support:

From starboard/player.h,

  • SbPlayerCreate()
  • SbPlayerOutputModeSupported()
  • SbPlayerGetCurrentFrame()

From starboard/decode_target.h,

  • SbDecodeTargetRelease()
  • SbDecodeTargetGetInfo()

Note that it is possible that you may not need to use the SbDecodeTargetGraphicsContextProvider parameter of SbPlayerCreate(). More on this later.

Example Application Usage Pattern

We now describe an example, and typical, sequence of steps that an application will take when it wishes to make use of decode-to-texture support.

Decode-to-texture sequence diagram

  1. An application with the desire to make use of decode-to-texture will first call SbPlayerOutputModeSupported(), passing in kSbPlayerOutputModeDecodeToTexture for its output_mode parameter. If the function returns false, the application learns that decode-to-texture is not supported by the platform and it will not continue with a decode-to-texture flow.

  2. If SbPlayerOutputModeSupported() returns true, the application will call SbPlayerCreate(), passing in kSbPlayerOutputModeDecodeToTexture for the output_mode parameter, and also providing a valid provider parameter (more on this later). At this point, the Starboard platform is expected to have created a player with the decode-to-texture output mode.

  3. Once the player is started and playback has begun, the application‘s renderer thread (this may be a different thread than the one that called SbPlayerCreate()) will repeatedly and frequently call SbPlayerGetCurrentFrame(). Since this function will be called from the application’s renderer thread, it should be thread-safe. If the platform uses a GLES renderer, it is guaranteed that this function will be called with the GLES renderer context set as current. This function is expected to return the video frame that is to be displayed at the time the function is called as a SbDecodeTarget object. The SbPlayerGetCurrentFrame() will be called at the renderer‘s frequency, i.e. the application render loop’s frame rate. If the application‘s frame rate is higher than the video’s frame rate, then the same video frame will sometimes be returned in consecutive calls to SbPlayerGetCurrentFrame(). If the video‘s frame rate is higher than the application’s (this should be rare), then some video frames will never be returned by calls to SbPlayerGetCurrentFrame(); in other words, video frames will be dropped.

  4. Once the application has acquired a valid SbDecodeTarget object through a call to SbPlayerGetCurrentFrame(), it will call SbDecodeTargetGetInfo() on it to extract information about the opaque SbDecodeTarget object. The SbDecodeTargetGetInfo() function fills out a SbDecodeTargetInfo structure which contains information about the decoded frame and, most importantly, a reference to a GLES texture ID on GLES platforms, or a reference to a SbBlitterSurface object on Starboard Blitter API platforms. The application can then use this texture/surface handle to render the video frame as it wishes.

  5. When the application is finished using the SbDecodeTarget that it has aquired through the SbPlayerGetCurrentFrame() function, it will call SbDecodeTargetRelease() on it. The Starboard platform implementation should ensure that the SbDecodeTarget object returned by SbPlayerGetCurrentFrame() remains valid until the corresponding call to SbDecodeTargetRelease() is made. A call to SbDecodeTargetRelease() will be made to match each call to SbPlayerGetCurrentFrame().

The SbDecodeTargetGraphicsContextProvider object

It is completely possible that a platform's Starboard implementation can properly implement decode-to-texture support without dealing with the SbDecodeTargetGraphicsContextProvider object (passed in to SbPlayerCreate()). The SbDecodeTargetGraphicsContextProvider reference gives platforms references to the graphics objects that will later be used to render the decoded frames. For example, on Blitter API platforms, a reference to the SbBlitterDevice object will be a mamber of SbDecodeTargetGraphicsContextProvider. For EGL platforms, a EGLDisplay and EGLContext will be available, but additionally a SbDecodeTargetGlesContextRunner function pointer will be provided that will allow you to run arbitrary code on the renderer thread with the EGLContext held current. This may be useful if your SbDecodeTarget creation code will required making GLES calls (e.g. glGenTextures()) in which a EGLContext must be held current.

Performance Considerations

The decode-to-texture Starboard API is specifically designed to allow Starboard implementations to have the player decode directly to a texture, so that the application can then reference and render with that texture without at any point performing a pixel copy. The decode-to-texture path can therefore be highly performant.

It is still recommended however that platforms support the punch-through player mode if possible. When using the decode-to-texture player output mode, the video may be rendered within the application‘s render loop, which means that non-video-related time complexity in the application’s render loop can affect video playback's apparent frame rate, potentially resulting in dropped frames. The platform can likely configure punch-through video to refresh on its own loop, decoupling it from the application render loop.

Implementation Strategies

Working with “push” players

If your player implementation is setup with a “push” framework where frames are pushed out as soon as they are decoded, then you will need to cache those frames (along with their timestamps) so that they can be passed on to the application when SbPlayerGetCurrentFrame() is called. This same strategy applies if the player pushes frames only when they are meant to be rendered.