© Jan Newmarch 2017

Jan Newmarch, Raspberry Pi GPU Audio Video Programming , 10.1007/978-1-4842-2472-4_13

13. OpenMAX Video Processing on the Raspberry Pi

Jan Newmarch

(1)Oakleigh, Victoria, Australia

This chapter looks at video processing using the Broadcom GPU on the Raspberry Pi.

Building Programs

You can build the programs in this chapter using the following Makefile:

DMX_INC =  -I/opt/vc/include/ -I /opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux
EGL_INC =
OMX_INC =  -I /opt/vc/include/IL
OMX_ILCLIENT_INC = -I/opt/vc/src/hello_pi/libs/ilclient
INCLUDES = $(DMX_INC) $(EGL_INC) $(OMX_INC) $(OMX_ILCLIENT_INC)


CFLAGS=-g -DRASPBERRY_PI -DOMX_SKIP64BIT $(INCLUDES)
CPPFLAGS =


DMX_LIBS =  -L/opt/vc/lib/ -lbcm_host -lvcos -lvchiq_arm -lpthread
EGL_LIBS = -L/opt/vc/lib/ -lEGL -lGLESv2
OMX_LIBS = -lopenmaxil
OMX_ILCLIENT_LIBS = -L/opt/vc/src/hello_pi/libs/ilclient -lilclient


LDLIBS =  $(DMX_LIBS) $(EGL_LIBS) $(OMX_LIBS) $(OMX_ILCLIENT_LIBS)

all: il_decode_video  il_render_video

Video Components

The Raspberry Pi has a number of OpenMAX components specifically for video processing, as shown here:

  • camera

  • egl_render

  • video_decode

  • video_encode

  • video_render

  • video_scheduler

  • video_splitter

Video Formats

OpenMAX has a number of data structures used to get and set information about components. You have seen some of these before.

  • OMX_PORT_PARAM_TYPE: This is used with the index parameter OMX_IndexParamVideoInit in a call to OMX_GetParameter. This gives the number of video ports and the port number of the first port.

  • OMX_PARAM_PORTDEFINITIONTYPE: This is used with the index parameter OMX_PARAM_PORTDEFINITIONTYPE to give, for each port, the number of buffers, the size of each buffer, and the direction (input or output) of the port.

  • OMX_VIDEO_PORTDEFINITIONTYPE: This is a field of the OMX_PARAM_PORTDEFINITIONTYPE for videos.

  • OMX_VIDEO_PARAM_PORTFORMATTYPE: This is used to get information about the different formats supported by each port.

Some of these were discussed in Chapter 8.

You haven’t looked at the field OMX_VIDEO_PORTDEFINITIONTYPE, which is part of the port definition information. It contains the following relevant fields:

typedef struct OMX_VIDEO_PORTDEFINITIONTYPE {
    OMX_STRING cMIMEType;
    OMX_NATIVE_DEVICETYPE pNativeRender;
    OMX_U32 nFrameWidth;
    OMX_U32 nFrameHeight;
    OMX_S32 nStride;
    OMX_U32 nSliceHeight;
    OMX_U32 nBitrate;
    OMX_U32 xFramerate;
    OMX_BOOL bFlagErrorConcealment;
    OMX_VIDEO_CODINGTYPE eCompressionFormat;
    OMX_COLOR_FORMATTYPE eColorFormat;
    OMX_NATIVE_WINDOWTYPE pNativeWindow;
} OMX_VIDEO_PORTDEFINITIONTYPE;

The last two but one fields are the current values set for the port. The possible values are obtained from the next structure OMX_VIDEO_PARAM_PORTFORMATTYPE, so I will discuss it in the next paragraph. The major fields you get here, though, are parameters about the video size: nFrameWidth, nFrameHeight, nStride, and nSliceHeight. These are parameters you often need if you are, say, saving a decoded video in a different format such as MPEG4.

The OMX_VIDEO_PARAM_PORTFORMATTYPE is defined (in section 4.3.5 in the 1.1.2 specification) as follows:

typedef struct OMX_VIDEO_PARAM_PORTFORMATTYPE {
    OMX_U32 nSize;
    OMX_VERSIONTYPE nVersion;
    OMX_U32 nPortIndex;
    OMX_U32 nIndex;
    OMX_VIDEO_CODINGTYPE eCompressionFormat;
    OMX_COLOR_FORMATTYPE eColorFormat;
    OMX_U32 xFramerate;
} OMX_VIDEO_PARAM_PORTFORMATTYPE;

The first two fields are common to all OpenMAX structures. The nPortIndex field is the port you are looking at. The nIndex field is to distinguish between all the different format types supported by this port. The eCompressionFormat and eColorFormat fields give information about the format, while the last field gives the frame rate of the video in frames per second.

The values for OMX_VIDEO_CODINGTYPE are given in Table 4-66 of the 1.1.2 specification and on the RPi are given in the file /opt/vc/include/IL/OMX_Video.h, as shown here:

typedef enum OMX_VIDEO_CODINGTYPE {
    OMX_VIDEO_CodingUnused,     /** Value when coding is N/A */
    OMX_VIDEO_CodingAutoDetect, /** Autodetection of coding type */
    OMX_VIDEO_CodingMPEG2,      /** AKA: H.262 */
    OMX_VIDEO_CodingH263,       /** H.263 */
    OMX_VIDEO_CodingMPEG4,      /** MPEG-4 */
    OMX_VIDEO_CodingWMV,        /** all versions of Windows Media Video */
    OMX_VIDEO_CodingRV,         /** all versions of Real Video */
    OMX_VIDEO_CodingAVC,        /** H.264/AVC */
    OMX_VIDEO_CodingMJPEG,      /** Motion JPEG */
    OMX_VIDEO_CodingKhronosExtensions = 0x6F000000, /** Reserved region for introducing Khronos Standard Extensions */
    OMX_VIDEO_CodingVendorStartUnused = 0x7F000000, /** Reserved region for introducing Vendor Extensions */


    OMX_VIDEO_CodingVP6,        /** On2 VP6 */
    OMX_VIDEO_CodingVP7,        /** On2 VP7 */
    OMX_VIDEO_CodingVP8,        /** On2 VP8 */
    OMX_VIDEO_CodingYUV,        /* raw YUV video */
    OMX_VIDEO_CodingSorenson,   /** Sorenson */
    OMX_VIDEO_CodingTheora,     /** Theora */
    OMX_VIDEO_CodingMVC,        /** H.264/MVC */      


    OMX_VIDEO_CodingMax = 0x7FFFFFFF
} OMX_VIDEO_CODINGTYPE;

The type OMX_COLOR_FORMATTYPE is more informative. It is defined in the file /opt/vc/include/IL/OMX_IVCommon.h, as follows:

typedef enum OMX_COLOR_FORMATTYPE {
    OMX_COLOR_FormatUnused,
    OMX_COLOR_FormatMonochrome,
    OMX_COLOR_Format8bitRGB332,
    OMX_COLOR_Format12bitRGB444,
    OMX_COLOR_Format16bitARGB4444,
    OMX_COLOR_Format16bitARGB1555,
    OMX_COLOR_Format16bitRGB565,
    OMX_COLOR_Format16bitBGR565,
    OMX_COLOR_Format18bitRGB666,
    OMX_COLOR_Format18bitARGB1665,
    OMX_COLOR_Format19bitARGB1666,
    OMX_COLOR_Format24bitRGB888,
    OMX_COLOR_Format24bitBGR888,
    OMX_COLOR_Format24bitARGB1887,
    OMX_COLOR_Format25bitARGB1888,
    OMX_COLOR_Format32bitBGRA8888,
    OMX_COLOR_Format32bitARGB8888,
    OMX_COLOR_FormatYUV411Planar,
    OMX_COLOR_FormatYUV411PackedPlanar,
    OMX_COLOR_FormatYUV420Planar,
    OMX_COLOR_FormatYUV420PackedPlanar,
    OMX_COLOR_FormatYUV420SemiPlanar,
    OMX_COLOR_FormatYUV422Planar,
    OMX_COLOR_FormatYUV422PackedPlanar,
    OMX_COLOR_FormatYUV422SemiPlanar,
    OMX_COLOR_FormatYCbYCr,
    OMX_COLOR_FormatYCrYCb,
    OMX_COLOR_FormatCbYCrY,
    OMX_COLOR_FormatCrYCbY,
    OMX_COLOR_FormatYUV444Interleaved,
    OMX_COLOR_FormatRawBayer8bit,
    OMX_COLOR_FormatRawBayer10bit,
    OMX_COLOR_FormatRawBayer8bitcompressed,
    OMX_COLOR_FormatL2,
    OMX_COLOR_FormatL4,
    OMX_COLOR_FormatL8,
    OMX_COLOR_FormatL16,
    OMX_COLOR_FormatL24,
    OMX_COLOR_FormatL32,
    OMX_COLOR_FormatYUV420PackedSemiPlanar,
    OMX_COLOR_FormatYUV422PackedSemiPlanar ,
    OMX_COLOR_Format18BitBGR666,
    OMX_COLOR_Format24BitARGB6666,
    OMX_COLOR_Format24BitABGR6666,
    OMX_COLOR_FormatKhronosExtensions = 0x6F000000, /** Reserved region for introducing Khronos Standard Extensions */
    OMX_COLOR_FormatVendorStartUnused = 0x7F000000, /** Reserved region for introducing Vendor Extensions */
    OMX_COLOR_Format32bitABGR8888,
    OMX_COLOR_Format8bitPalette,
    OMX_COLOR_FormatYUVUV128,
    OMX_COLOR_FormatRawBayer12bit,
    OMX_COLOR_FormatBRCMEGL,
    OMX_COLOR_FormatBRCMOpaque,
    OMX_COLOR_FormatYVU420PackedPlanar,
    OMX_COLOR_FormatYVU420PackedSemiPlanar,
    OMX_COLOR_FormatMax = 0x7FFFFFFF
} OMX_COLOR_FORMATTYPE;

That’s a rather large number of formats!

Running the program info from Chapter 8 shows the following for the video_decode component:

Video ports:
  Ports start on 130
  There are 2 open ports
  Port 130 has 20 buffers (minimum 1) of size 81920
  Direction is input
    Supported video formats are:
    Video format encoding 0x14
    Video compression format 0x4
    Video format encoding 0x14
    Video compression format 0x7
    Video format encoding 0x14
    Video compression format 0x7F000007
    Video format encoding 0x14
    Video compression format 0x8
    Video format encoding 0x14
    Video compression format 0x5
    Video format encoding 0x14
    Video compression format 0x3
    Video format encoding 0x14
    Video compression format 0x2
    Video format encoding 0x14
    Video compression format 0x7F000001
    Video format encoding 0x14
    Video compression format 0x7F000002
    Video format encoding 0x14
    Video compression format 0x7F000003
    Video format encoding 0x14
    Video compression format 0x6
    Video format encoding 0x14
    Video compression format 0x7F000004
    Video format encoding 0x14
    Video compression format 0x7F000005
    Video format encoding 0x14
    Video compression format 0x7F000006
    Video format encoding 0x14
    Video compression format 0x0
    No more formats supported
  Port 131 has 1 buffers (minimum 1) of size 115200
  Direction is output
    Supported video formats are:
    Video format encoding 0x14
    Video compression format 0x0
    No more formats supported

The compression formats are as follows:

  • 0x2: OMX_VIDEO_CodingMPEG2

  • 0x3: OMX_VIDEO_CodingH263

  • 0x4: OMX_VIDEO_CodingMPEG4

  • 0x5: OMX_VIDEO_CodingWMV

  • 0x6: OMX_VIDEO_CodingRV

  • 0x7: OMX_VIDEO_CodingAVC

  • 0x8: OMX_VIDEO_CodingMJPEG

  • 0x7F000001: OMX_VIDEO_CodingVP6

  • 0x7F000002: OMX_VIDEO_CodingVP7

  • 0x7F000003: OMX_VIDEO_CodingVP8

  • 0x7F000004: OMX_VIDEO_CodingYUV

  • 0x7F000005: OMX_VIDEO_CodingSorenson

  • 0x7F000006: OMX_VIDEO_CodingTheora

Currently I have not confirmed any of these except for H.264 (CodingAVC) .

Decoding an H.264 File

The program to decode a video into an uncompressed format is essentially the same as the one for decoding an image. There is an essential difference: reading one block is not enough for the component to get information about the video file format, and multiple block reads generally have to occur.

In this example, you just slap a loop around reading the initial blocks until a PortSettingsChanged event occurs. The call to ilclient_wait_for_event will time out for the first set of calls, so you drop the timeout period from 10 seconds down to 2 seconds. This isn’t any good for interactive viewing, of course, but here you are just decoding.

The significant change from the image-decoding program is this initial loop:

    int port_settings_changed = 0;
    while (!port_settings_changed) {
        buff_header =
            ilclient_get_input_buffer(component,
                                      130,
                                      1 /* block */);
        if (buff_header != NULL) {
            read_into_buffer_and_empty(fp,
                                       component,
                                       buff_header,
                                       &toread);


            // If all the file has been read in, then
            // we have to re-read this first block.
            // Broadcom bug?
            if (toread <= 0) {
                printf("Rewinding ");
                // wind back to start and repeat
                fp = freopen(VIDEO, "r", fp);
                toread = get_file_size(VIDEO);
            }
        }


        // try if this block sets params for output port
        err = ilclient_wait_for_event(component,
                                      OMX_EventPortSettingsChanged,
                                      131, 0, 0, 1,
                                      ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED,
                                      2000) ;
        if (err < 0) {
            printf("Wait for port settings changed timed out ");
        } else {
            port_settings_changed = 1;
        }
    }

The full program is il_decode_video.c.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>


#include <OMX_Core.h>
#include <OMX_Component.h>


#include <bcm_host.h>
#include <ilclient.h>


#define video  "/opt/vc/src/hello_pi/hello_video/test.h264"

void printState(OMX_HANDLETYPE handle) {
    // elided
}


char *err2str(int err) {
    return "elided";
}


void eos_callback(void *userdata, COMPONENT_T *comp, OMX_U32 data) {
    fprintf(stderr, "Got eos event ");
}


void error_callback(void *userdata, COMPONENT_T *comp, OMX_U32 data) {
    fprintf(stderr, "OMX error %s ", err2str(data));
}


int get_file_size(char *fname) {
    struct stat st;


    if (stat(fname, &st) == -1) {
            perror("Stat'ing video file");
            return -1;
        }
    return(st.st_size);
}


static void set_video_decoder_input_format(COMPONENT_T *component) {
   // set input video format
    printf("Setting video decoder format ");
    OMX_VIDEO_PARAM_PORTFORMATTYPE videoPortFormat;
    //setHeader(&videoPortFormat,  sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
    memset(&videoPortFormat, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
    videoPortFormat.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
    videoPortFormat.nVersion.nVersion = OMX_VERSION;


    videoPortFormat.nPortIndex = 130;
    videoPortFormat.eCompressionFormat = OMX_VIDEO_CodingAVC;
    OMX_SetParameter(ilclient_get_handle(component),
                     OMX_IndexParamVideoPortFormat, &videoPortFormat);


}

OMX_ERRORTYPE read_into_buffer_and_empty(FILE *fp,
                                         COMPONENT_T *component,
                                         OMX_BUFFERHEADERTYPE *buff_header,
                                         int *toread) {
    OMX_ERRORTYPE r;


    int buff_size = buff_header->nAllocLen;
    int nread = fread(buff_header->pBuffer, 1, buff_size, fp);


    buff_header->nFilledLen = nread;
    *toread -= nread;
    printf("Read %d, %d still left ", nread, *toread);


    if (*toread <= 0) {
        printf("Setting EOS on input ");
        buff_header->nFlags |= OMX_BUFFERFLAG_EOS;
    }
    r = OMX_EmptyThisBuffer(ilclient_get_handle(component),
                        buff_header);
    if (r != OMX_ErrorNone) {
        fprintf(stderr, "Empty buffer error %s ",
                err2str(r));
    }
    return r;
}


OMX_ERRORTYPE save_info_from_filled_buffer(COMPONENT_T *component,
                                           OMX_BUFFERHEADERTYPE * buff_header) {
    OMX_ERRORTYPE r ;


    printf("Got a filled buffer with %d, allocated %d ",
           buff_header->nFilledLen,
           buff_header->nAllocLen);
    if (buff_header->nFlags & OMX_BUFFERFLAG_EOS) {
        printf("Got EOS on output ");
        exit(0);
    }


    // and then refill it
    r = OMX_FillThisBuffer(ilclient_get_handle(component),
                           buff_header);
    if (r != OMX_ErrorNone) {
        fprintf(stderr, "Fill buffer error %s ",
                err2str(r));
    }
    return r;
}


void get_output_port_settings(COMPONENT_T *component) {
    OMX_PARAM_PORTDEFINITIONTYPE portdef;


    printf("Port settings changed ");
    // need to setup the input for the resizer with the output of the
    // decoder
    portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
    portdef.nVersion.nVersion = OMX_VERSION;
    portdef.nPortIndex = 131;
    OMX_GetParameter(ilclient_get_handle(component),
                     OMX_IndexParamPortDefinition, &portdef);


    unsigned int uWidth =
        (unsigned int) portdef.format.video.nFrameWidth;
    unsigned int uHeight =
        (unsigned int) portdef.format.video.nFrameHeight;
    unsigned int uStride =
        (unsigned int) portdef.format.video.nStride;
    unsigned int uSliceHeight =
        (unsigned int) portdef.format.video.nSliceHeight;
    printf("Frame width %d, frame height %d, stride %d, slice height %d ",
           uWidth,
           uHeight,
           uStride,
           uSliceHeight);
    printf("Getting format Compression 0x%x Color Format: 0x%x ",
           (unsigned int) portdef.format.video.eCompressionFormat,
           (unsigned int) portdef.format.video.eColorFormat);
}


int main(int argc, char** argv) {

    int i;
    char *componentName;
    int err;
    ILCLIENT_T  *handle;
    COMPONENT_T *component;
    FILE *fp = fopen(video, "r");
    int toread = get_file_size(video);
    OMX_BUFFERHEADERTYPE *buff_header;


    componentName = "video_decode";

    bcm_host_init();

    handle = ilclient_init() ;
    if (handle == NULL) {
        fprintf(stderr, "IL client init failed ");
        exit(1);
    }


   if (OMX_Init() != OMX_ErrorNone) {
        ilclient_destroy(handle);
        fprintf(stderr, "OMX init failed ");
        exit(1);
    }


   ilclient_set_error_callback(handle,
                               error_callback,
                               NULL);
   ilclient_set_eos_callback(handle,
                             eos_callback,
                             NULL);


    err = ilclient_create_component(handle,
                                &component,
                                componentName,
                                ILCLIENT_DISABLE_ALL_PORTS
                                    |
                                    ILCLIENT_ENABLE_INPUT_BUFFERS
                                    |
                                    ILCLIENT_ENABLE_OUTPUT_BUFFERS
                                );
    if (err == -1) {
        fprintf(stderr, "Component create failed ");
        exit(1);
    }
    printState(ilclient_get_handle(component));


    err = ilclient_change_component_state(component,
                                          OMX_StateIdle);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }
    printState(ilclient_get_handle(component));


    // must be before we enable buffers
    set_video_decoder_input_format(component);


    // input port
    ilclient_enable_port_buffers(component, 130,
                                 NULL, NULL, NULL);
    ilclient_enable_port(component, 130);


    err = ilclient_change_component_state(component,
                                          OMX_StateExecuting);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Executing ");
        exit(1);
    }
    printState(ilclient_get_handle(component));


    // Read the first block so that the component can get
    // the dimensions of the video and call port settings
    // changed on the output port to configure it
    int port_settings_changed = 0;
    while (!port_settings_changed) {
    buff_header =
        ilclient_get_input_buffer(component,
                                  130,
                                  1 /* block */);
    if (buff_header != NULL) {
        read_into_buffer_and_empty(fp,
                                   component,
                                   buff_header,
                                   &toread);


        // If all the file has been read in, then
        // we have to re-read this first block.
        // Broadcom bug?
        if (toread <= 0) {
            printf("Rewinding ");
            // wind back to start and repeat
            fp = freopen(video, "r", fp);
            toread = get_file_size(video);
        }
    }


    // wait for first input block to set params for output port
    err = ilclient_wait_for_event(component,
                                  OMX_EventPortSettingsChanged,
                                  131, 0, 0, 1,
                                  ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED,
                                  2000);
    if (err < 0) {
        printf("Wait for port settings changed timed out ");
    } else {
        port_settings_changed = 1;
    }
    err = ilclient_remove_event(component,
                                  OMX_EventPortSettingsChanged,
                                  131, 0, 0, 1);
    if (err < 0) {
        printf("Wait for remove port settings changed timed out ");
    } else {
        port_settings_changed = 1;
    }
    }


    get_output_port_settings(component);

    // now enable output port since port params have been set
    ilclient_enable_port_buffers(component, 131,
                                 NULL, NULL, NULL);
    ilclient_enable_port(component, 131);


    // now work through the file
    while (toread > 0) {
        OMX_ERRORTYPE r;


        // do we have an input buffer we can fill and empty?
        buff_header =
            ilclient_get_input_buffer(component,
                                      130,
                                      1 /* block */);
        if (buff_header != NULL) {
            read_into_buffer_and_empty(fp,
                                   component,
                                   buff_header,
                                   &toread);
        }


        // do we have an output buffer that has been filled?
        buff_header =
            ilclient_get_output_buffer(component,
                                      131,
                                      0 /* no block */);
        if (buff_header != NULL) {
            save_info_from_filled_buffer(component,
                                         buff_header);
        } else {
            printf("No filled buffer ");
        }
    }


    while (1) {
        printf("Getting last output buffers ");
        buff_header =
            ilclient_get_output_buffer(component,
                                       131,
                                       1 /* block */);
        if (buff_header != NULL) {
            save_info_from_filled_buffer(component,
                                         buff_header);
        }
    }
    exit(0);
}

The program runs with no command-line parameters . The Videocore library comes with a standard H.264 example clip, /opt/vc/src/hello_pi/hello_video/test.h264, from Big Buck Bunny. It reads multiple frames before a PortSettngsChanged event occurs at which time the video format can be determined.

...
Read 81920, 30353369 still left
Empty buffer done
Wait for port settings changed timed out
Wait for remove port settings changed timed out
Read 81920, 30271449 still left
Empty buffer done
Wait for remove port settings changed timed out
Port settings changed
Frame width 1920, frame height 1080, stride 1920, slice height 1088
Getting format Compression 0x0 Color Format: 0x14
...

Rendering an H.264 Video

In a similar manner to decoding an image, rendering a video closely follows the code for rendering an image. However, there is now a major difference in how you wait for the initial PortSettingsChanged event. You have seen these two possibilities so far:

  • Read the first part of the data and then block, waiting for the PortSettingsChanged event that you know will come.

  • Keep reading data, each time block waiting for the PortSettingsChanged event and timing out if it doesn’t arrive.

Neither of these two is acceptable for the immediate rendering of videos. It may take more than one read, and waiting on timeouts is too slow. Instead, you need to adopt an alternative approach based on the ilclient_remove_event function, which returns true if it can remove an event from the list of those events already seen.

The revised wait for the PortSettingsChanged event is as follows:

while there is more data
    read a new buffer
    empty the buffer


    if a PortSettingsChanged event has occurred
        break


    if there is no more data
        block waiting for a PortSettingsChanged event
        if failed on timeout
            error
        else
            break

(This algorithm should be generally applicable. But it breaks on the tunneled image rendering for unknown reasons.)

The video-rendering program using this is il_render_video.c.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>


#include <OMX_Core.h>
#include <OMX_Component.h>


#include <bcm_host.h>
#include <ilclient.h>


#define VIDEO "/opt/vc/src/hello_pi/hello_video/test.h264"
char *video_file = VIDEO;


void printState(OMX_HANDLETYPE handle) {
    // elided
}


char *err2str(int err) {
    return "elided";
}


void eos_callback(void *userdata, COMPONENT_T *comp, OMX_U32 data) {
    fprintf(stderr, "Got eos event ");
}


void error_callback(void *userdata, COMPONENT_T *comp, OMX_U32 data) {
    fprintf(stderr, "OMX error %s ", err2str(data));
}


int get_file_size(char *fname) {
    struct stat st;


    if (stat(fname, &st) == -1) {
        perror("Stat'ing video file");
        return -1;
    }
    return(st.st_size);
}


unsigned int uWidth;
unsigned int uHeight;


OMX_ERRORTYPE read_into_buffer_and_empty(FILE *fp,
                                         COMPONENT_T *component,
                                         OMX_BUFFERHEADERTYPE *buff_header,
                                         int *toread) {
    OMX_ERRORTYPE r;


    int buff_size = buff_header->nAllocLen;
    int nread = fread(buff_header->pBuffer, 1, buff_size, fp);


    buff_header->nFilledLen = nread;
    *toread -= nread;
    printf("Read %d, %d still left ", nread, *toread);


    if (*toread <= 0) {
        printf("Setting EOS on input ");
        buff_header->nFlags |= OMX_BUFFERFLAG_EOS;
    }
    r = OMX_EmptyThisBuffer(ilclient_get_handle(component),
                            buff_header);
    if (r != OMX_ErrorNone) {
        fprintf(stderr, "Empty buffer error %s ",
                err2str(r));
    }
    return r;
}


static void set_video_decoder_input_format(COMPONENT_T *component) {
    int err;


    // set input video format
    printf("Setting video decoder format ");
    OMX_VIDEO_PARAM_PORTFORMATTYPE videoPortFormat;
    //setHeader(&videoPortFormat,  sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
    memset(&videoPortFormat, 0, sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE));
    videoPortFormat.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE);
    videoPortFormat.nVersion.nVersion = OMX_VERSION;


    videoPortFormat.nPortIndex = 130;
    videoPortFormat.eCompressionFormat = OMX_VIDEO_CodingAVC;


    err = OMX_SetParameter(ilclient_get_handle(component),
                           OMX_IndexParamVideoPortFormat, &videoPortFormat);
    if (err != OMX_ErrorNone) {
        fprintf(stderr, "Error setting video decoder format %s ", err2str(err));
        exit(1);
    } else {
        printf("Video decoder format set up ok ");
    }


}

void setup_decodeComponent(ILCLIENT_T  *handle,
                           char *decodeComponentName,
                           COMPONENT_T **decodeComponent) {
    int err;


    err = ilclient_create_component(handle,
                                    decodeComponent,
                                    decodeComponentName,
                                    ILCLIENT_DISABLE_ALL_PORTS
                                    |
                                    ILCLIENT_ENABLE_INPUT_BUFFERS
                                    |
                                    ILCLIENT_ENABLE_OUTPUT_BUFFERS
                                    );
    if (err == -1) {
        fprintf(stderr, "DecodeComponent create failed ");
        exit(1);
    }
    printState(ilclient_get_handle(*decodeComponent));


    err = ilclient_change_component_state(*decodeComponent,
                                          OMX_StateIdle);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }
    printState(ilclient_get_handle(*decodeComponent));


    // must be before we enable buffers
    set_video_decoder_input_format(*decodeComponent);
}


void setup_renderComponent(ILCLIENT_T  *handle,
                           char *renderComponentName,
                           COMPONENT_T **renderComponent) {
    int err;


    err = ilclient_create_component(handle,
                                    renderComponent,
                                    renderComponentName,
                                    ILCLIENT_DISABLE_ALL_PORTS
                                    |
                                    ILCLIENT_ENABLE_INPUT_BUFFERS
                                    );
    if (err == -1) {
        fprintf(stderr, "RenderComponent create failed ");
        exit(1);
    }
    printState(ilclient_get_handle(*renderComponent));


    err = ilclient_change_component_state(*renderComponent,
                                          OMX_StateIdle);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }
    printState(ilclient_get_handle(*renderComponent));
}


int main(int argc, char** argv) {

    int i;
    char *decodeComponentName;
    char *renderComponentName;
    int err;
    ILCLIENT_T  *handle;
    COMPONENT_T *decodeComponent;
    COMPONENT_T *renderComponent;
    FILE *fp;
    int toread;
    OMX_BUFFERHEADERTYPE *buff_header;


    if (argc == 2) {
        video_file = argv[1];
    }
    if ((fp = fopen(video_file, "r")) == NULL)  {
        fprintf(stderr, "Can't open: %s ", video_file);
        exit(2);
    }
    if ((toread = get_file_size(video_file)) == -1)  {
        fprintf(stderr, "Can't stat: %s ", video_file);
        exit(2);
    }


    decodeComponentName = "video_decode";
    renderComponentName = "video_render";


    bcm_host_init();

    handle = ilclient_init();
    if (handle == NULL) {
        fprintf(stderr, "IL client init failed ");
        exit(1);
    }


    if (OMX_Init() != OMX_ErrorNone) {
        ilclient_destroy(handle);
        fprintf(stderr, "OMX init failed ");
        exit(1);
    }


    ilclient_set_error_callback(handle,
                                error_callback,
                                NULL);
    ilclient_set_eos_callback(handle,
                              eos_callback,
                              NULL);


    setup_decodeComponent(handle, decodeComponentName, &decodeComponent);
    setup_renderComponent(handle, renderComponentName, &renderComponent);
    // both components now in Idle state, no buffers, ports disabled


    // input port
    ilclient_enable_port_buffers(decodeComponent, 130,
                                 NULL, NULL, NULL);
    ilclient_enable_port(decodeComponent, 130);


    err = ilclient_change_component_state(decodeComponent,
                                          OMX_StateExecuting);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Executing ");
        exit(1);
    }
    printState(ilclient_get_handle(decodeComponent));


    // Read blocks and break out when we see a
    // PortSettingsChanged
    while (toread > 0) {
        buff_header =
            ilclient_get_input_buffer(decodeComponent,
                                      130,
                                      1 /* block */);
        if (buff_header != NULL) {
            read_into_buffer_and_empty(fp,
                                       decodeComponent,
                                       buff_header,
                                       &toread);


            // If all the file has been read in, then
            // we have to re-read this first block.
            // Broadcom bug?
            if (toread <= 0) {
                printf("Rewinding ");
                // wind back to start and repeat
                fp = freopen(video_file, "r", fp);
                toread = get_file_size(video_file);
            }
        }


        if (toread > 0 && ilclient_remove_event(decodeComponent,
                                                OMX_EventPortSettingsChanged,
                                                131, 0, 0, 1) == 0) {
            printf("Removed port settings event ");
            break;
        } else {
            printf("No portr settting seen yet ");
        }
        // wait for first input block to set params for output port
        if (toread == 0) {
            // wait for first input block to set params for output port
            err = ilclient_wait_for_event(decodeComponent,
                                         OMX_EventPortSettingsChanged,
                                         131, 0, 0, 1,
                                         ILCLIENT_EVENT_ERROR | ILCLIENT_PARAMETER_CHANGED,
                                         2000);
            if (err < 0) {
                fprintf(stderr, "No port settings change ");
                //exit(1);
            } else {
                printf("Port settings changed ");
                break;
            }
        }
    }


    // set the decode component to idle and disable its ports
    err = ilclient_change_component_state(decodeComponent,
                                          OMX_StateIdle);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }
    ilclient_disable_port(decodeComponent, 131);
    ilclient_disable_port_buffers(decodeComponent, 131,
                                  NULL, NULL, NULL);


    // set up the tunnel between decode and render ports
    err = OMX_SetupTunnel(ilclient_get_handle(decodeComponent),
                          131,
                          ilclient_get_handle(renderComponent),
                          90);
    if (err != OMX_ErrorNone) {
        fprintf(stderr, "Error setting up tunnel %X ", err);
        exit(1);
    } else {
        printf("Tunnel set up ok ");
    }


    // Okay to go back to processing data
    // enable the decode output ports


    OMX_SendCommand(ilclient_get_handle(decodeComponent),
                    OMX_CommandPortEnable, 131, NULL);


    ilclient_enable_port(decodeComponent, 131);

    // enable the render output ports

    OMX_SendCommand(ilclient_get_handle(renderComponent),
                    OMX_CommandPortEnable, 90, NULL);


    ilclient_enable_port(renderComponent, 90);

    // set both components to executing state
    err = ilclient_change_component_state(decodeComponent,
                                          OMX_StateExecuting);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }
    err = ilclient_change_component_state(renderComponent,
                                          OMX_StateExecuting);
    if (err < 0) {
        fprintf(stderr, "Couldn't change state to Idle ");
        exit(1);
    }


    // now work through the file
    while (toread > 0) {
        OMX_ERRORTYPE r;


        // do we have a decode input buffer we can fill and empty?
        buff_header =
            ilclient_get_input_buffer(decodeComponent,
                                      130,
                                      1 /* block */);
        if (buff_header != NULL) {
            read_into_buffer_and_empty(fp,
                                       decodeComponent,
                                       buff_header,
                                       &toread);
        }
    }


    ilclient_wait_for_event(renderComponent,
                            OMX_EventBufferFlag,
                            90, 0, OMX_BUFFERFLAG_EOS, 0,
                            ILCLIENT_BUFFER_FLAG_EOS, 10000);
    printf("EOS on render ");


    sleep(10);

    exit(0);
}

The program takes the default H.264 but can be overridden by a command-line option. It plays the short segment and then terminates.

Going Full-Screen

The Broadcom version of OpenMAX has a nonstandard configuration mechanism that can be applied to video-rendering components. This can be done as follows:

    OMX_CONFIG_DISPLAYREGIONTYPE display_region;
    display_region.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE);
    display_region.nVersion.nVersion = OMX_VERSION;
    display_region.nPortIndex = 90;


    display_region.set = OMX_DISPLAY_SET_FULLSCREEN | OMX_DISPLAY_SET_NOASPECT;
    display_region.fullscreen = OMX_TRUE;
    display_region.noaspect = OMX_TRUE;
    err = OMX_SetConfig(ilclient_get_handle(*renderComponent),
                  OMX_IndexConfigDisplayRegion,
                  &display_region);
    if(err != OMX_ErrorNone) {
        fprintf(stderr, "Failed to set Full screen %x %s ",
                err, err2str(err));
        exit(1);
    }

Conclusion

This chapter has looked at decoding and rendering H.264 video streams. Playing MP4 files is covered in Chapter 16.

Resources

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset