Decoding audio to a QNX buffer queue

Updated: April 19, 2023

You can use the OpenMAX AL media engine to decode an audio stream to a QNX buffer queue. You can then read the data items from the buffer queue, further process them if desired, and send them to the output.

In the following example, we create a media player and provide it with an audio file URL for the data source and a QNX buffer queue for the audio data sink. To start decoding, we start playback through the media player. We then use the XAQNXBufferQueueSinkItf interface to read the decoded audio data in buffers, and copy the data to an output file. When we've finished reading and copying a preset number of bytes, we stop playback.

Setting up audio playback

For reference, we define a structure type to hold references to the media engine objects we'll use to control playback and the QNX buffer queue we'll use as the engine's sink:
typedef struct omxal_objs
{
    /* Object Interfaces */
    XAObjectItf engine_decode;
    XAObjectItf player;
    XAPlayItf   player_itf;

    /* Buffer queue interfaces */
    XAQNXBufferQueueSinkItf decoder_bufferq_out_itf;
} omxal_objs_t;
The next step is to create the media engine. We assume our program defines num_opts, to store the number of engine option settings, and engine_opts, to store the settings, which enable features such as thread safety (for details, see the XA_ENGINEOPTION_* entries in the OpenMAX AL specification):
omxal_objs_t omxal_objs = {0};
/* Create the engine and store its reference in the engine_decode 
   field of our oxmal_objs_t structure */
XAresult res = xaCreateEngine( &(omxal_objs->engine_decode), 
                               num_opts, 
                               engine_opts, 
                               // We don't need any interfaces, so we
                               // don't define these other parameters 
                               0, NULL, NULL );

if (res != XA_RESULT_SUCCESS) {
    /* Error-handling code goes here */
}

Using the newly written engine_decode field in the omxal_objs object, we realize the engine in synchronous mode and get the engine interface and store a reference to it (in engine_decode_itf). For information about the required API calls, see the Realize() and GetInterface() descriptions in the OpenMAX AL specification.

We then define the source and sink that will be provided to the player. In the code excerpt below, we assume that an input file URL has been defined (in in_path); most likely, this URL would be provided by the user:
XADataLocator_URI uri = { XA_DATALOCATOR_URI, in_path };
XADataSource audio_src = { (void *)&uri, NULL };

XADataLocator_QNXBufferQueue buffer_queue_out = { 
    XA_DATALOCATOR_QNXBUFFERQUEUE,
    BUFFER_COUNT;
};
XADataSink audio_snk = { (void *)&buffer_queue_out, NULL };
We then create the Media Player object that will manage playback:
/* Use the engine interface to create the media player */
res = (*engine_decode_itf)->CreateMediaPlayer( engine_decode_itf, 
                                               &(omxal_objs->player),
                                               &audio_src, 
                                               NULL, 
                                               &audio_snk, 
                                               NULL, 
                                               ...);

Using the newly written player field of the omxal_objs object, we realize the player in synchronous mode and get the interfaces for the player and the player's audio output, which we store in the player_itf and decoder_bufferq_out_itf fields. (This entails calling Realize() and GetInterface() as before.)

Through the decoder_bufferq_out_itf field, which is an XAQNXBufferQueueSinkItf reference, we get formatting information about the media data. This information is typically used to prepare downstream system components for the next stage of processing the media data (e.g., setting up the audio backend). For this reason, it's useful to have the formatting details before getting the first buffer of data. We get these details using the GetFormat() function:
XADataFormat_QNXEncoded audio_info;
XAQNXBufferQueueSinkItf *decoder_bufferq_out_itf = 
    &omxal_objs->decoder_bufferq_out_itf;

XAresult res = (**decoder_bufferq_out_itf)->GetFormat(
                                *decoder_bufferq_out_itf, audio_info );
We can now start playback. This starts the flow of audio data through the media engine:
res = (*(omxal_objs->player_itf))->SetPlayState( omxal_objs->player_itf,
                                                 XA_PLAYSTATE_PLAYING );

Copying audio data from the QNX buffers to the output file

We now use the audio format details stored in the XAAudioStreamInformation_QNX object (which is contained in the XADataFormat_QNXEncoded object that was filled in by GetFormat()) to calculate a fixed number of bytes to read from the input file and copy to the output file. This number (which is stored in bytes_to_write) is used to allocate a buffer for storing the output data. As seen in this next excerpt, we consider several audio details as well as the number of seconds of data we want to decode. Your program might not use a fixed time interval like we do here; it could instead use a user-entered value or wait for an End of stream item from the buffer queue if decoding of the entire stream is desired:
/* Grab 2s of data */
int bytes_to_write = audio_info.audio_info.sampleRate / 1000 * 
                     audio_info.audio_info.channels * 
                     audio_info.audio_info.bitsPerSample / 8 * 
                     2;

unsigned char *write_buf;
if ( (write_buf = malloc( bytes_to_write ) ) == NULL ) {
    /* Error-handling code goes here */
}
To read the right number of bytes from the source (i.e., the input file), we use a loop to process individual buffers of decoded audio data, as the media engine sends them to sink. At the beginning of each iteration, we call the GetAndWait() function to wait as long as needed for a buffer to become available. We then check that the buffer has data and if so, use the data size, number of bytes copied so far, and limit in bytes_to_write to determine how many bytes can be written to the output buffer. We then do the data copy and update the byte counter and data pointer. Finally, we return the buffer memory by calling Done():
/* Keep track of how many bytes we've read from the audio source
   and written to the output buffer so we don't exceed the limit */
int bytes_written = 0;

while ( bytes_written < bytes_to_write ) {
    XAQNXBuffer *buffer = (**decoder_bufferq_out_itf)->GetAndWait(
                                       *decoder_bufferq_out_itf, NULL );
    if (buffer) {
        if ( (buffer->pBufferData) && (buffer->dataSize > 0) ) {
                /* Calculate the number of bytes to copy */
                int tocopy;
                if (bytes_written + buffer->dataSize > bytes_to_write) {
                    tocopy = bytes_to_write - bytes_written;
                } else {
                    tocopy = buffer->dataSize;
                }
                /* Do the copy and update our running totals */
                memcpy( write_buf, buffer->pBufferData, tocopy );
                buffer->dataUsed = buffer->dataSize;
                bytes_written += tocopy;
                write_buf += tocopy;
            }
        }
        /* Return the buffer memory because we're finished with it */
        if ( (**decoder_bufferq_out_itf)->Done(
                                     *decoder_bufferq_out_itf, buffer )
                != XA_RESULT_SUCCESS ) {
            /* Error-handling code goes here */
        }
    }
}
With all of the audio data stored in our output buffer (write_buf), we can now write that data to the output file. As with the input file path, we assume that the output file path has been preset (in out_path). At this point, your program could parse and process the data items (e.g., it could re-encode their audio content) before writing them to the output file. Here, we just do a direct copy:
int outputfd = open( out_path, 
                     O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR );
if (outputfd == -1) {
    /* Error-handling code goes here */
}

if ( write( outputfd, write_buf, bytes_written ) == -1 ) {
    /* Error-handling code goes here */
}

close( outputfd );
free( write_buf );

Stopping playback and cleaning up the OpenMAX AL objects

After copying all of the data to the output file, we stop playback to stop the media flow:
(*(omxal_objs->player_itf))->SetPlayState( omxal_objs->player_itf,
                                           XA_PLAYSTATE_STOPPED );
We then need to clean up the OpenMAX AL interfaces by calling Destroy() for each of the player and engine_decode fields in the omxal_objs object (for details, see the OpenMAX AL specification).