Skip to main content
The audio samples demonstrate the PS3 audio system, including basic playback, event-based synchronization, and multi-channel audio output.

Available Audio Samples

audiotest

Basic audio playback with sine wave generation

audiosync

Event-based audio synchronization for sample playback

PS3 Audio Architecture

The PS3 audio system uses a block-based architecture:
  • Audio ports provide audio output channels
  • Blocks are fixed-size buffers (AUDIO_BLOCK_SAMPLES per block)
  • Hardware reads blocks automatically at the sample rate
  • Application writes ahead of the hardware read pointer

Audio Parameters

  • Channels: 2 (stereo) or 8 (multi-channel)
  • Block counts: 8, 16, or 32 blocks per port
  • Sample format: Float32 (-1.0 to 1.0)
  • Sample rate: 48 kHz

audiotest - Basic Audio Playback

Location: samples/audio/audiotest/ Demonstrates simple audio output by generating sine waves.

What It Demonstrates

  • Audio library initialization
  • Opening and configuring audio ports
  • Generating audio samples (sine waves)
  • Block-based buffer filling
  • Staying ahead of hardware playback
  • Proper shutdown sequence

Implementation

samples/audio/audiotest/source/main.c
#include <audio/audio.h>
#include <psl1ght/lv2/timer.h>
#include <math.h>
#include <assert.h>
#include <stdio.h>

#define PI 3.14159265f

void fillBuffer(float *buf)
{
    static float pos = 0;

    for (unsigned int i = 0; i < AUDIO_BLOCK_SAMPLES; ++i)
    {
        // Fill with sine waves
        buf[i*2+0] = sin(pos);        // Left channel
        buf[i*2+1] = sin(pos*2);      // Right channel (different frequency)
        
        pos += 0.01f;
        if(pos > M_PI)
            pos -= 2*M_PI;
    }
}

u32 playOneBlock(u64 *readIndex, float *audioDataStart)
{
    static u32 audio_block_index = 1;

    // Get hardware read position
    u64 current_block = *readIndex;

    // Don't overwrite blocks the hardware is reading
    if(audio_block_index == current_block)
    {
        return 0;
    }
    
    printf("playOneBlock: %ld, %d\n", current_block, audio_block_index);

    // Calculate buffer position
    float *buf = audioDataStart + 2 * AUDIO_BLOCK_SAMPLES * audio_block_index;
    fillBuffer(buf);

    // Advance to next block (wrap around)
    audio_block_index = (audio_block_index + 1) % AUDIO_BLOCK_8;

    return 1;
}

int main(int argc, const char* argv[])
{
    AudioPortParam params;
    AudioPortConfig config;
    u32 portNum;

    // Initialize the audio system
    int ret = audioInit();
    printf("audioInit: %d\n", ret);

    // Set audio parameters
    params.numChannels = AUDIO_PORT_2CH;   // Stereo
    params.numBlocks = AUDIO_BLOCK_8;      // 8 blocks
    params.attr = 0;                       // No special attributes
    params.level = 1;                      // Default volume

    // Open the audio port (not started yet)
    ret = audioPortOpen(&params, &portNum);
    printf("audioPortOpen: %d\n", ret);
    printf("  portNum: %d\n", portNum);

    // Get port configuration
    ret = audioGetPortConfig(portNum, &config);
    printf("audioGetPortConfig: %d\n", ret);
    printf("  readIndex: 0x%8X\n", config.readIndex);
    printf("  status: %d\n", config.status);
    printf("  channelCount: %ld\n", config.channelCount);
    printf("  numBlocks: %ld\n", config.numBlocks);
    printf("  portSize: %d\n", config.portSize);
    printf("  audioDataStart: 0x%8X\n", config.audioDataStart);

    // Start audio playback
    ret = audioPortStart(portNum);
    printf("audioPortStart: %d\n", ret);

    // Play 1000 blocks
    int i = 0;
    while(i < 1000)
    {
        if(playOneBlock((u64*)(u64)config.readIndex,
                       (float*)(u64)config.audioDataStart) != 0)
            i++;
    }

    // Shutdown in reverse order
    ret = audioPortStop(portNum);
    printf("audioPortStop: %d\n", ret);
    
    ret = audioPortClose(portNum);
    printf("audioPortClose: %d\n", ret);
    
    ret = audioQuit();
    printf("audioQuit: %d\n", ret);

    return 0;
}

Audio Playback Flow

1

Initialize audio system

audioInit();
2

Configure port parameters

AudioPortParam params;
params.numChannels = AUDIO_PORT_2CH;
params.numBlocks = AUDIO_BLOCK_8;
params.attr = 0;
params.level = 1;
3

Open audio port

u32 portNum;
audioPortOpen(&params, &portNum);
4

Get port configuration

AudioPortConfig config;
audioGetPortConfig(portNum, &config);
This gives you:
  • readIndex: Hardware read position
  • audioDataStart: Buffer address
  • channelCount, numBlocks: Confirmed settings
5

Start playback

audioPortStart(portNum);
6

Fill blocks continuously

In your main loop, keep filling blocks ahead of the hardware
7

Cleanup

audioPortStop(portNum);
audioPortClose(portNum);
audioQuit();

audiosync - Event-Based Audio

Location: samples/audio/audiosync/ Demonstrates event-based audio synchronization for precise timing.

What It Demonstrates

  • Audio event queue creation
  • Event-based synchronization
  • Playing pre-recorded audio samples
  • Precise timing control

Implementation

samples/audio/audiosync/source/main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <audio/audio.h>
#include "tada48_16_2_raw.h"

static u64 snd_key;
static sys_event_queue_t snd_queue;

void fillBuffer(f32 *buf)
{
    u32 i;
    static u32 pos = 0;

    // Convert 16-bit PCM to float and copy to audio buffer
    for(i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
        buf[i*2 + 0] = (f32)*((s16*)&tada48_16_2_raw[pos]) / 32768.0f;
        buf[i*2 + 1] = (f32)*((s16*)&tada48_16_2_raw[pos + 2]) / 32768.0f;

        pos += 4;
        if(pos >= tada48_16_2_raw_size) pos = 0;
    }
}

void playOneBlock(audioPortConfig *config)
{
    f32 *buf;
    s32 ret = 0;
    sys_event_t event;
    u64 current_block = *(u64*)((u64)config->readIndex);
    f32 *dataStart = (f32*)((u64)config->audioDataStart);
    u32 audio_block_index = (current_block + 1) % config->numBlocks;

    // Wait for audio event (hardware finished reading a block)
    ret = sysEventQueueReceive(snd_queue, &event, 20*1000);

    // Fill the next block
    buf = dataStart + config->channelCount * AUDIO_BLOCK_SAMPLES * audio_block_index;
    fillBuffer(buf);
}

int main(int argc, char *argv[])
{
    u32 portNum, i;
    audioPortParam params;
    audioPortConfig config;

    s32 ret = audioInit();
    printf("audioInit: %08x\n", ret);

    // Configure audio port
    params.numChannels = AUDIO_PORT_2CH;
    params.numBlocks = AUDIO_BLOCK_8;
    params.attrib = AUDIO_PORT_INITLEVEL;  // Initialize buffer level
    params.level = 1.0f;
    
    ret = audioPortOpen(&params, &portNum);
    printf("audioPortOpen: %08x\n", ret);
    printf("  portNum: %d\n", portNum);

    ret = audioGetPortConfig(portNum, &config);
    printf("audioGetPortConfig: %08x\n", ret);

    // Create event queue for audio synchronization
    ret = audioCreateNotifyEventQueue(&snd_queue, &snd_key);
    printf("audioCreateNotifyEventQueue: %08x\n", ret);
    printf("snd_queue: %16lx\n", (long unsigned int)snd_queue);
    printf("snd_key: %16lx\n", snd_key);

    // Register event queue with audio system
    ret = audioSetNotifyEventQueue(snd_key);
    printf("audioSetNotifyEventQueue: %08x\n", ret);

    // Drain any pending events
    ret = sysEventQueueDrain(snd_queue);
    printf("sysEventQueueDrain: %08x\n", ret);

    // Start playback
    ret = audioPortStart(portNum);
    printf("audioPortStart: %08x\n", ret);

    // Play using event-based synchronization
    i = 0;
    while(i < 1000) {
        playOneBlock(&config);
        i++;
    }

    // Cleanup
    ret = audioPortStop(portNum);
    printf("audioPortStop: %08x\n", ret);

    ret = audioRemoveNotifyEventQueue(snd_key);
    printf("audioRemoveNotifyEventQueue: %08x\n", ret);

    ret = audioPortClose(portNum);
    printf("audioPortClose: %08x\n", ret);

    ret = sysEventQueueDestroy(snd_queue, 0);
    printf("sysEventQueueDestroy: %08x\n", ret);

    ret = audioQuit();
    printf("audioQuit: %08x\n", ret);

    return 0;
}

Event-Based vs Polling

Pros:
  • Simpler code
  • No event queue overhead
  • Works well for always-playing audio
Cons:
  • Must actively check if buffer is ready
  • Can waste CPU cycles
while(audio_block_index == current_block) {
    // Busy wait
}
Pros:
  • Efficient - thread sleeps until event
  • Precise timing
  • Better for interactive audio
Cons:
  • More complex setup
  • Event queue overhead
sysEventQueueReceive(snd_queue, &event, timeout);

Building Audio Samples

Build All Audio Samples

cd samples/audio
make

Build Individual Sample

cd samples/audio/audiotest
make

Run Sample

ps3load audiotest.self

Audio Buffer Management

Buffer Calculation

// Total buffer size
size_t buffer_size = numChannels * AUDIO_BLOCK_SAMPLES * numBlocks * sizeof(float);

// Block offset
float *block_ptr = audioDataStart + (channelCount * AUDIO_BLOCK_SAMPLES * block_index);

// Sample offset within block
float *sample_ptr = block_ptr + (sample_index * channelCount);

Interleaved Format

Audio samples are interleaved by channel:
// Stereo (2 channels)
buf[0] = left_sample_0;
buf[1] = right_sample_0;
buf[2] = left_sample_1;
buf[3] = right_sample_1;
// ...

// Multi-channel (8 channels)
buf[0] = ch0_sample_0;
buf[1] = ch1_sample_0;
buf[2] = ch2_sample_0;
// ... up to ch7
buf[8] = ch0_sample_1;

Common Audio Patterns

float frequency = 440.0f;  // A4 note
float sample_rate = 48000.0f;
float phase = 0.0f;

for(int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
    float sample = sin(phase * 2.0f * M_PI);
    buf[i*2 + 0] = sample;  // Left
    buf[i*2 + 1] = sample;  // Right
    
    phase += frequency / sample_rate;
    if(phase >= 1.0f) phase -= 1.0f;
}
// 16-bit signed PCM to float
for(int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
    s16 pcm_left = pcm_data[i*2 + 0];
    s16 pcm_right = pcm_data[i*2 + 1];
    
    buf[i*2 + 0] = (float)pcm_left / 32768.0f;
    buf[i*2 + 1] = (float)pcm_right / 32768.0f;
}
float volume = 0.5f;  // 50% volume

for(int i = 0; i < AUDIO_BLOCK_SAMPLES * 2; i++) {
    buf[i] *= volume;
}
// Mix two audio sources
for(int i = 0; i < AUDIO_BLOCK_SAMPLES * 2; i++) {
    buf[i] = (source1[i] * 0.5f) + (source2[i] * 0.5f);
    
    // Clamp to valid range
    if(buf[i] > 1.0f) buf[i] = 1.0f;
    if(buf[i] < -1.0f) buf[i] = -1.0f;
}

Best Practices

Stay Ahead

Always keep several blocks filled ahead of the hardware read pointer

Avoid Underruns

If you don’t fill blocks fast enough, you’ll hear audio glitches

Use Events

Event-based synchronization is more efficient than polling

Mind the Format

Audio samples must be float in range [-1.0, 1.0]

Audio Constants

// Channel configurations
AUDIO_PORT_2CH  // Stereo
AUDIO_PORT_8CH  // 7.1 surround

// Block counts
AUDIO_BLOCK_8
AUDIO_BLOCK_16
AUDIO_BLOCK_32

// Samples per block
AUDIO_BLOCK_SAMPLES  // Typically 256

// Attributes
AUDIO_PORT_INITLEVEL  // Initialize buffer level

Audio API Reference

Complete audio system API documentation

Audio Programming Guide

In-depth audio programming concepts

Event Queue API

System event queue documentation

Threading

Multi-threading for audio processing

Build docs developers (and LLMs) love