Raspberry Pi OpenGL and OpenMAX IL “Hello World!” Applications

As you may already know, Raspberry Pi has released their first SD card image with Debian. This morning, I explained how to use that image in qemu.

I’ve been waiting for samples to take advantage of the power Videocore GPU inside Broadcom BCM2835 SoC used in the Raspberry Pi board and the goods news is that they added Hello World code samples in C to make use of those capabilities.

The sample are located in /opt/vc/src/hello_pi directory:

  • hello_audio – Audio output demo using OpenMAX IL through the ilcient helper library
  • hello_triangle – A rotating cube rendered with OpenGL ES with 3 images used as textures on the cube faces.
  • hello_video – Video decode demo using OpenMAX IL through the ilcient helper library

You can either compile those samples in the board or cross-compile them in your host machine. Since you need the GPU, you will obviously not be able to test those in the emulator but it’s still good to be able to study the code before getting the hardware.

If you don’t have time to check the source code inside the SD card image, here’s video.c OpenMAX IL sample code to playback a h.264 video:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bcm_host.h"
#include "ilclient.h"

static int video_decode_test(char *filename)
{
OMX_VIDEO_PARAM_PORTFORMATTYPE format;
OMX_TIME_CONFIG_CLOCKSTATETYPE cstate;
COMPONENT_T *video_decode = NULL, *video_scheduler = NULL, *video_render = NULL, *clock = NULL;
COMPONENT_T *list[5];
TUNNEL_T tunnel[4];
ILCLIENT_T *client;
FILE *in;
int status = 0;
unsigned char *data = NULL;
unsigned int data_len = 0;
int find_start_codes = 0;
int packet_size = 16<<10;
memset(list, 0, sizeof(list));
memset(tunnel, 0, sizeof(tunnel));

if((in = fopen(filename, "rb")) == NULL)
return -2;

if((client = ilclient_init()) == NULL)
{
fclose(in);
return -3;
}

if(OMX_Init() != OMX_ErrorNone)
{
ilclient_destroy(client);
fclose(in);
return -4;
}

if(find_start_codes && (data = malloc(packet_size+4)) == NULL)
{
status = -16;
if(OMX_Deinit() != OMX_ErrorNone)
status = -17;
ilclient_destroy(client);
fclose(in);
return status;
}
// create video_decode
if(ilclient_create_component(client, &video_decode, "video_decode", ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0)
status = -14;
list[0] = video_decode;

// create video_render
if(status == 0 && ilclient_create_component(client, &video_render, "video_render", ILCLIENT_DISABLE_ALL_PORTS) != 0)
status = -14;
list[1] = video_render;

// create clock
if(status == 0 && ilclient_create_component(client, &clock, "clock", ILCLIENT_DISABLE_ALL_PORTS) != 0)
status = -14;
list[2] = clock;
memset(&cstate, 0, sizeof(cstate));
cstate.nSize = sizeof(cstate);
cstate.nVersion.nVersion = OMX_VERSION;
cstate.eState = OMX_TIME_ClockStateWaitingForStartTime;
cstate.nWaitMask = 1;
if(clock != NULL && OMX_SetParameter(ILC_GET_HANDLE(clock), OMX_IndexConfigTimeClockState, &cstate) != OMX_ErrorNone)
status = -13;

// create video_scheduler
if(status == 0 && ilclient_create_component(client, &video_scheduler, "video_scheduler", ILCLIENT_DISABLE_ALL_PORTS) != 0)
status = -14;
list[3] = video_scheduler;

set_tunnel(tunnel, video_decode, 131, video_scheduler, 10);
set_tunnel(tunnel+1, video_scheduler, 11, video_render, 90);
set_tunnel(tunnel+2, clock, 80, video_scheduler, 12);

// setup clock tunnel first
if(status == 0 && ilclient_setup_tunnel(tunnel+2, 0, 0) != 0)
status = -15;
else
ilclient_change_component_state(clock, OMX_StateExecuting);

if(status == 0)
ilclient_change_component_state(video_decode, OMX_StateIdle);

memset(&format, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
format.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
format.nVersion.nVersion = OMX_VERSION;
format.nPortIndex = 130;
format.eCompressionFormat = OMX_VIDEO_CodingAVC;

if(status == 0 &&
OMX_SetParameter(ILC_GET_HANDLE(video_decode), OMX_IndexParamVideoPortFormat, &format) == OMX_ErrorNone &&
ilclient_enable_port_buffers(video_decode, 130, NULL, NULL, NULL) == 0)
{
OMX_BUFFERHEADERTYPE *buf;
int port_settings_changed = 0;
int first_packet = 1;

ilclient_change_component_state(video_decode, OMX_StateExecuting);
while((buf = ilclient_get_input_buffer(video_decode, 130, 1)) != NULL)
{
// feed data and wait until we get port settings changed
unsigned char *dest = find_start_codes ? data + data_len : buf->pBuffer;

data_len += fread(dest, 1, packet_size+(find_start_codes*4)-data_len, in);

if(port_settings_changed == 0 &&
((data_len > 0 && ilclient_remove_event(video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1) == 0) ||
(data_len == 0 && ilclient_wait_for_event(video_decode, OMX_EventPortSettingsChanged, 131, 0, 0, 1,
ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED, 10000) == 0)))
{
port_settings_changed = 1;

if(ilclient_setup_tunnel(tunnel, 0, 0) != 0)
{
status = -7;
break;
}

ilclient_change_component_state(video_scheduler, OMX_StateExecuting);

// now setup tunnel to video_render
if(ilclient_setup_tunnel(tunnel+1, 0, 1000) != 0)
{
status = -12;
break;
}

ilclient_change_component_state(video_render, OMX_StateExecuting);
}
if(!data_len)
break;

if(find_start_codes)
{
int i, start = -1, len = 0;
int max_len = data_len > packet_size ? packet_size : data_len;
for(i=2; i<max_len; i++)
{
if(data[i-2] == 0 && data[i-1] == 0 && data[i] == 1)
{
len = 3;
start = i-2;

// check for 4 byte start code
if(i > 2 && data[i-3] == 0)
{
len++;
start--;
}

break;
}
}

if(start == 0)
{
// start code is next, so just send that
buf->nFilledLen = len;
}
else if(start == -1)
{
// no start codes seen, send the first block
buf->nFilledLen = max_len;
}
else
{
// start code in the middle of the buffer, send up to the code
buf->nFilledLen = start;
}

memcpy(buf->pBuffer, data, buf->nFilledLen);
memmove(data, data + buf->nFilledLen, data_len - buf->nFilledLen);
data_len -= buf->nFilledLen;
}
else
{
buf->nFilledLen = data_len;
data_len = 0;
}
buf->nOffset = 0;
if(first_packet)
{
buf->nFlags = OMX_BUFFERFLAG_STARTTIME;
first_packet = 0;
}
else
buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;

if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(video_decode), buf) != OMX_ErrorNone)
{
status = -6;
break;
}

}

buf->nFilledLen = 0;
buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS;
if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(video_decode), buf) != OMX_ErrorNone)
status = -20;

// wait for EOS from render
ilclient_wait_for_event(video_render, OMX_EventBufferFlag, 90, 0, OMX_BUFFERFLAG_EOS, 0,
ILCLIENT_BUFFER_FLAG_EOS, 10000);

// need to flush the renderer to allow video_decode to disable its input port
ilclient_flush_tunnels(tunnel, 0);

ilclient_disable_port_buffers(video_decode, 130, NULL, NULL, NULL);
}

fclose(in);

ilclient_disable_tunnel(tunnel);
ilclient_disable_tunnel(tunnel+1);
ilclient_disable_tunnel(tunnel+2);
ilclient_teardown_tunnels(tunnel);
ilclient_state_transition(list, OMX_StateIdle);
ilclient_state_transition(list, OMX_StateLoaded);

ilclient_cleanup_components(list);

OMX_Deinit();

ilclient_destroy(client);
return status;
}

int main (int argc, char **argv)
{
if (argc < 2) {
printf("Usage: %s <filename>\n", argv[0]);
exit(1);
}
bcm_host_init();
return video_decode_test(argv[1]);
}

That’s it. It make take some time for me to understand as I’m not quite familiar with OpenMAX although parts of it seems similar to what Sigma Designs does in their media processors (loop to send to video decode and wait for End Of Stream).

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter