Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ps3dev/PSL1GHT/llms.txt
Use this file to discover all available pages before exploring further.
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(¶ms, &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
Configure port parameters
AudioPortParam params;
params.numChannels = AUDIO_PORT_2CH;
params.numBlocks = AUDIO_BLOCK_8;
params.attr = 0;
params.level = 1;
Open audio port
u32 portNum;
audioPortOpen(¶ms, &portNum);
Get port configuration
AudioPortConfig config;
audioGetPortConfig(portNum, &config);
This gives you:
readIndex: Hardware read position
audioDataStart: Buffer address
channelCount, numBlocks: Confirmed settings
Fill blocks continuously
In your main loop, keep filling blocks ahead of the hardware
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(¶ms, &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
Build Individual Sample
cd samples/audio/audiotest
make
Run Sample
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);
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