Chapter 3. Simple Audio Output Program

This example program outputs a short beep. To keep it simple, a few details (primarily error-checking) are skipped. This program only includes the operations required to produce the beep. The steps are as follows:

Note: Consult the online example code for more advanced programs.

Step 1: Include the ml.h and mlu.h Files

To begin, you will need the following files:




Provides the core ML library functionality


Provides simple utility functions built on the core library

You may choose to use only the core library or you may find it convenient to use the simpler utility functions.

Include the files as follows:

#include <ML/ml.h>
#include <ML/mlu.h>

Step 2: Locate a Device

You must query the capabilities of the system to find a suitable digital media device with which to perform your audio output task. To do that, you must search the ML capability tree, which contains information on every ML device on the system.

In your search, you should start at the top of the tree as follows:

  1. Query the local system to find the first physical device that matches your desired device name.

  2. Look in that device to find its first output jack.

  3. Find an output path that goes through that jack.

In this case, assuming that the device name is being passed in as a command-line argument, you can use some of the utility functions to find a suitable output path:

MLint64 devId=0;
MLint64 jackId=0;
MLint64 pathId=0;

mluFindDeviceByName( ML_SYSTEM_LOCALHOST, argv[1], &devId );
mluFindFirstOutputJack( devId, &jackId );
mluFindPathToJack( jackId, &pathid, memoryAlignment );

Step 3: Open the Device Output Path

An open device output path provides your application with a dedicated connection to the hardware. It also allocates system resources for use in subsequent operations. The device path is opened with an mlOpen call as follows:

mlOpen( pathId, NULL, &openPath );

If the mlOpen call is successful, you will get an open path identifier. All operations using that path must use its identifier.

Note: Sometimes an mlOpen call can fail due to insufficient resources (typically because too many applications may already be using the same physical device).

Step 4: Set Up the Audio Device Path

Set up the path you just opened for your operation. In this case, you will use signed 16-bit audio samples with the following:

  • A single (mono) audio channel

  • A gain of -12dB

  • A sample rate of 44.1kHz

In ML, applications communicate with devices using messages. These messages are known as MLpv messages, because they consist of a list of param/value pairs. An MLpv ends with an ML_END to indicate completion.

For example:

mlpv controls[5];
MLreal64 gain = -12; /* decibels */

controls[0].param = ML_AUDIO_FORMAT_INT32;
controls[0].value.int32 = ML_AUDIO_FORMAT_S16;
controls[1].param = ML_AUDIO_CHANNELS_INT32;
controls[1].value.int32 = 1;
controls[2].param = ML_AUDIO_GAINS_REAL64_ARRAY;
controls[2].value.pReal64 = &gain
controls[2].length = 1;
controls[3].param = ML_AUDIO_SAMPLE_RATE_REAL64;
controls[3].value.real64 = 44100.0;
controls[4].param = ML_END;

Notice that this message contains both scalar parameters (for example, the number of audio channels) and an array parameter (the array of audio gains).

Step 5: Set Controls on Audio Device Path

After you have constructed the MLpv controls message, you must set the controls on the open audio path as follows:

mlSetControls(openPath, controls);

This call makes all the desired control settings and does not return until those settings have been sent to the hardware. If it returns successfully, it indicates that all of the control changes have been committed to the device (and you are free to delete or alter the controls message).

Note: All control changes within a single controls message are processed atomically: either the call succeeds (and they are all applied) or the call fails (and none are applied).

Assuming that the call succeeded, the path is now set up and ready to receive audio data.

Step 6: Send Buffer to Device for Processing

This example assumes that you have already allocated a buffer in memory and filled it with audio samples. To send that buffer to the device for processing, do the following:

  1. Construct an MLpv message that describes the buffer. That message must include both a pointer to the buffer and the length of the buffer (in bytes):

     MLpv msg[2];
      msg[0].param = ML_AUDIO_BUFFER_POINTER;
      msg[0].value.pByte = ourAudioBuffer;
      msg[0].length = sizeof(ourAudioBuffer);
      msg[1].param = ML_END;

  2. Send the buffers message to the opened path:

    mlSendBuffers(openPath, msg);

When the message is sent, it is placed on a queue of messages going to the device. The mlSendBuffers call does very little work: it gives the message a cursory look before sending it to the device for later processing.

Note: Unlike the mlSetControls call, the mlSendBuffers call does not wait for the device to process the message, it simply enqueues it and then returns.

Step 7: Begin Message Processing

You must tell the device to start processing enqueued messages. This is done with the mlBeginTransfer call as follows:


The program can sleep while the device is busy working on the message as follows:


Using sleep is simple, but the example in Chapter 4, “Realistic Audio Output Program” shows a better approach. See “Step 6: Begin the Transfer” in Chapter 4.

Step 8: Receive the Reply Message

As the device processes each message, it generates a reply message that is sent back to our application. By examining that reply, you can confirm that the buffer was transferred successfully, as follows:

MLint32 messageType;
MLpv* message;

mlReceiveMessage(openPath, &messageType, &Message );

if( messageType == ML_BUFFERS_COMPLETE )
  printf("Buffer transferred!\n");

Step 9: Close the Path

Once you have verified that the buffer transferred successfully, you can close the path as follows:


Closing the path ends active transfer and frees any resources allocated when the path was opened.