Skip to main content
Proper audio-video synchronization is crucial for multimedia applications. The PSL1GHT audio system provides event-based mechanisms to ensure precise timing between audio buffer fills and video frame rendering.

Event-Based Synchronization

The recommended approach for synchronization uses audio event queues to trigger buffer fills at exactly the right time.

Event Queue Setup

1

Create an audio event queue

The event queue receives notifications when audio blocks are ready to be filled.
samples/audio/audiosync/source/main.c
#include <audio/audio.h>
#include <sys/event_queue.h>

sys_event_queue_t snd_queue;
sys_ipc_key_t snd_key;

s32 ret = audioCreateNotifyEventQueue(&snd_queue, &snd_key);
if (ret != 0) {
    printf("Failed to create event queue: %08x\n", ret);
    return -1;
}
2

Associate queue with audio system

Set the created event queue as the active notification queue.
samples/audio/audiosync/source/main.c
ret = audioSetNotifyEventQueue(snd_key);
if (ret != 0) {
    printf("Failed to set event queue: %08x\n", ret);
    return -1;
}
3

Drain initial events

Clear any pending events before starting playback.
samples/audio/audiosync/source/main.c
ret = sysEventQueueDrain(snd_queue);
if (ret != 0) {
    printf("Failed to drain queue: %08x\n", ret);
}

Synchronized Playback Loop

With event queues configured, your application can synchronize audio fills with video frames:
samples/audio/audiosync/source/main.c
void fillBuffer(f32 *buf) {
    static u32 pos = 0;
    
    for (u32 i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
        // Convert 16-bit PCM to float
        buf[i*2 + 0] = (f32)*((s16*)&audio_data[pos]) / 32768.0f;
        buf[i*2 + 1] = (f32)*((s16*)&audio_data[pos + 2]) / 32768.0f;
        
        pos += 4;
        if (pos >= audio_data_size)
            pos = 0;
    }
}

void playOneBlock(audioPortConfig *config) {
    sys_event_t event;
    
    // Block until next audio buffer is ready
    s32 ret = sysEventQueueReceive(snd_queue, &event, 20*1000);
    if (ret != 0) {
        printf("Event queue timeout\n");
        return;
    }
    
    // Get current hardware read position
    u64 current_block = *(u64*)((u64)config->readIndex);
    f32 *dataStart = (f32*)((u64)config->audioDataStart);
    
    // Calculate next block to fill (stay ahead of read position)
    u32 audio_block_index = (current_block + 1) % config->numBlocks;
    
    // Fill the audio buffer
    f32 *buf = dataStart + config->channelCount * AUDIO_BLOCK_SAMPLES * audio_block_index;
    fillBuffer(buf);
}

// Main loop
audioPortStart(portNum);

u32 frame_count = 0;
while (running) {
    // Audio notification drives the timing
    playOneBlock(&config);
    
    // Render video frame synchronized with audio
    renderFrame(frame_count++);
    
    // Present frame
    flip();
}
The audio system generates events at a consistent rate (approximately every 5.3ms for 48kHz audio with 256-sample blocks), providing a reliable timing source.

Timing Calculations

Understanding audio timing helps maintain synchronization:

Block Duration

// Audio is typically 48kHz on PS3
#define SAMPLE_RATE 48000

// Duration of one audio block in milliseconds
float block_duration_ms = (AUDIO_BLOCK_SAMPLES * 1000.0f) / SAMPLE_RATE;
// Result: ~5.33ms per block

// Duration in microseconds (for precise timing)
u64 block_duration_us = (AUDIO_BLOCK_SAMPLES * 1000000ULL) / SAMPLE_RATE;
// Result: ~5333μs per block

Buffer Latency

// Total buffered audio time
float total_latency_ms = block_duration_ms * config.numBlocks;

// Example with 8 blocks:
// ~42.67ms of audio buffered

Advanced Synchronization

Combining Audio and Video Events

For applications with complex timing requirements, you can use multiple event sources:
#include <sys/event.h>

// Create event port for multiple sources
sys_event_port_t event_port;
sysEventPortCreate(&event_port, SYS_EVENT_PORT_LOCAL, SYS_EVENT_PORT_NO_NAME);

// Connect audio queue to port
sysEventPortConnectLocal(event_port, snd_queue);

// Connect other timing sources as needed
// (e.g., video vsync, timers, etc.)

// Receive events from any source
sys_event_t event;
while (running) {
    ret = sysEventQueueReceive(snd_queue, &event, SYS_NO_TIMEOUT);
    
    switch (event.source) {
        case /* audio event */:
            playOneBlock(&config);
            break;
        case /* video event */:
            renderFrame();
            break;
    }
}

Handling Underruns

If your application can’t fill buffers fast enough:
// Monitor for gaps in block filling
void checkUnderrun(audioPortConfig *config) {
    u64 current_block = *(u64*)((u64)config->readIndex);
    
    if (current_block == last_filled_block) {
        printf("Audio underrun detected!\n");
        // Hardware is starving for audio data
    }
}

Cleanup

Always clean up event queues when stopping audio:
samples/audio/audiosync/source/main.c
// Stop audio first
audioPortStop(portNum);

// Disconnect event queue
audioRemoveNotifyEventQueue(snd_key);

// Destroy the queue
sysEventQueueDestroy(snd_queue, 0);

// Close port and quit
audioPortClose(portNum);
audioQuit();
Always call audioRemoveNotifyEventQueue() before destroying the event queue, or you may leak system resources.

Practical Tips

Sample Rate Conversion

If your audio data isn’t at 48kHz, you’ll need to resample:
// Simple linear interpolation (low quality but fast)
float resample(float *input, u32 input_samples, u32 input_rate, 
               float *output, u32 output_samples, u32 output_rate) {
    float ratio = (float)input_rate / output_rate;
    
    for (u32 i = 0; i < output_samples; i++) {
        float src_pos = i * ratio;
        u32 src_idx = (u32)src_pos;
        float frac = src_pos - src_idx;
        
        // Linear interpolation
        output[i] = input[src_idx] * (1.0f - frac) + 
                    input[src_idx + 1] * frac;
    }
}

Converting PCM Formats

The audio system requires 32-bit float samples. Convert from common formats:
samples/audio/audiosync/source/main.c
// 16-bit signed PCM to float
float sample_float = (float)sample_int16 / 32768.0f;

// 8-bit unsigned PCM to float
float sample_float = ((float)sample_uint8 - 128.0f) / 128.0f;

// 24-bit PCM to float
float sample_float = (float)sample_int24 / 8388608.0f;

API Reference

Key functions from audio/audio.h:
  • audioCreateNotifyEventQueue() - Create event queue for audio notifications
  • audioSetNotifyEventQueue() - Set active event queue
  • audioRemoveNotifyEventQueue() - Disconnect event queue
  • sysEventQueueReceive() - Wait for audio events (from sys/event_queue.h)
  • sysEventQueueDrain() - Clear pending events

Example

See samples/audio/audiosync/ for a complete example of audio-video synchronization using event queues.

Build docs developers (and LLMs) love