Skip to content

Commit 2ee3c7a

Browse files
audio mixer
1 parent 4ba746b commit 2ee3c7a

File tree

5 files changed

+499
-40
lines changed

5 files changed

+499
-40
lines changed

include/audio.h

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ typedef uint32_t (*audio_length_t)(void);
5454
/** @brief Maximum length (including NUL) for an audio device’s display name. */
5555
#define MAX_AUDIO_DEVICE_NAME 32
5656

57+
/** @brief Opaque channel handle. */
58+
typedef struct mixer_stream mixer_stream_t;
59+
5760
/**
5861
* @brief Registered audio device descriptor.
5962
*
@@ -95,23 +98,93 @@ typedef struct audio_device_t {
9598
* @brief Register a new audio device.
9699
*
97100
* Inserts @p newdev into the global device list. The caller retains ownership
98-
* of the storage; the struct must remain valid for the lifetime of the device.
101+
* of the storage; the struct must remain valid for the lifetime of the device
99102
*
100-
* @param newdev Pointer to the device descriptor to register (must not be NULL).
103+
* @param newdev Pointer to the device descriptor to register (must not be NULL)
101104
* @return true on success, false on failure.
102105
*/
103106
bool register_audio_device(audio_device_t *newdev);
104107

105108
/**
106-
* @brief Find a registered audio device by name.
109+
* @brief Find a registered audio device by name
107110
*
108-
* @param name NUL-terminated device name to search for.
109-
* @return Pointer to the matching device, or NULL if not found.
111+
* @param name NUL-terminated device name to search for
112+
* @return Pointer to the matching device, or NULL if not found
110113
*/
111114
audio_device_t *find_audio_device(const char *name);
112115

113116
/**
114-
* @brief Get the first registered audio device, if any.
115-
* @return Pointer to the first device in the list, or NULL if none.
117+
* @brief Get the first registered audio device, if any
118+
* @return Pointer to the first device in the list, or NULL if none
116119
*/
117120
audio_device_t *find_first_audio_device(void);
121+
122+
/**
123+
* @brief Initialise the mixer and register the idle tick
124+
*
125+
* Consumes queued frames from all active channels, applies gain/mute,
126+
* mixes them with 32-bit accumulation using SSE2, clips to 16-bit,
127+
* and pushes the result to the first audio device until the desired
128+
* latency is met. This is the sole hot path of the software mixer.
129+
*
130+
* @param dev Audio device to attach the mixer to
131+
* @param target_latency_ms Desired steady-state output latency in ms
132+
* @param idle_period_ms Foreground idle period in ms (mix cadence)
133+
* @param max_channels Maximum number of concurrently open channels
134+
* @return true on success, false if no audio device or allocation failed
135+
*/
136+
bool mixer_init(audio_device_t* dev, uint32_t target_latency_ms, uint32_t idle_period_ms, uint32_t max_channels);
137+
138+
/**
139+
* @brief Open a channel slot for a producer
140+
*
141+
* Single producer per channel, the mixer is the sole consumer
142+
* @return Channel handle or NULL if none available
143+
*/
144+
mixer_stream_t *mixer_create_stream(void);
145+
146+
/**
147+
* @brief Close a channel (drains remaining data then frees the slot)
148+
*/
149+
void mixer_free_stream(mixer_stream_t *ch);
150+
151+
/**
152+
* @brief Submit interleaved S16LE stereo frames to a channel (non-blocking)
153+
*
154+
* Identical signature and semantics to audio_push_t; returns frames accepted
155+
*/
156+
size_t mixer_push(mixer_stream_t *ch, const int16_t *frames, size_t total_frames);
157+
158+
/**
159+
* @brief Set per-channel gain (Q8.8; 256 == 1.0).
160+
*/
161+
void mixer_set_gain(mixer_stream_t *ch, uint16_t q8_8_gain);
162+
163+
/**
164+
* @brief Mute/unmute a channel; muted channels do not contribute to the mix.
165+
*/
166+
void mixer_set_mute(mixer_stream_t *ch, bool mute);
167+
168+
/**
169+
* @brief Query frames currently queued in a channel ring.
170+
*/
171+
uint32_t mixer_stream_queue_length(mixer_stream_t *ch);
172+
173+
/**
174+
* @brief Return the audio device used by the mixer (first device) or NULL.
175+
*/
176+
audio_device_t *mixer_device(void);
177+
178+
/**
179+
* @brief Foreground idle callback to drive the mixer.
180+
*
181+
* Called periodically by the process idle scheduler to top up the
182+
* output device with freshly mixed audio. Consumes queued frames
183+
* from all active mixer channels, applies gain/mute, and pushes
184+
* the mixed stream to the first audio device until the desired
185+
* latency is reached.
186+
*
187+
* This function is not intended to be called directly; it is
188+
* registered with @ref proc_register_idle by @ref mixer_init.
189+
*/
190+
void mixer_idle(void);

modules/ac97/ac97.c

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,18 @@ static bool ac97_start(pci_dev_t dev) {
7272
return true;
7373
}
7474

75-
static bool init_ac97(void) {
75+
static audio_device_t* init_ac97(void) {
7676
pci_dev_t dev = pci_get_device(0, 0, 0x0401);
7777
if (!dev.bits) {
7878
dprintf("ac97: no devices (class 0x0401)\n");
79-
return false;
79+
return NULL;
8080
}
8181
if (ac97_start(dev)) {
8282
kprintf("ac97: started\n");
8383
ac97_stream_prepare(4096, 44100); /* 44.1khz stereo LE 16 bit */
8484
} else {
8585
dprintf("ac97: start failed\n");
86-
return false;
86+
return NULL;
8787
}
8888
proc_register_idle(ac97_idle, IDLE_FOREGROUND, 1);
8989

@@ -97,9 +97,8 @@ static bool init_ac97(void) {
9797
device->resume = ac97_resume;
9898
device->stop = ac97_stop_clear;
9999
device->queue_length = ac97_buffered_ms;
100-
register_audio_device(device);
101100

102-
return true;
101+
return register_audio_device(device) ? device : NULL;
103102
}
104103

105104
static inline uint8_t ac97_po_civ(void) {
@@ -407,23 +406,15 @@ uint32_t ac97_get_hz() {
407406
return rate;
408407
}
409408

410-
void ac97_test_melody(void) {
411-
if (!ac97.bdl || !ac97.buf || ac97.bdl_n != 32 || ac97.frag_frames == 0 || ac97.frag_bytes == 0) {
412-
dprintf("ac97: test_melody: stream not prepared\n");
413-
return;
414-
}
415-
416-
fs_directory_entry_t* entry = fs_get_file_info("/system/webserver/test.raw");
417-
int16_t* data = kmalloc(entry->size);
418-
fs_read_file(entry, 0, entry->size, (unsigned char*)data);
419-
push_all_s16le(data, entry->size / sizeof(int16_t) / 2);
420-
kfree(data);
421-
}
422-
423409
bool EXPORTED MOD_INIT_SYM(KMOD_ABI)(void) {
424410
dprintf("ac97: module loaded\n");
425-
if (init_ac97()) {
426-
ac97_test_melody();
411+
audio_device_t* dev;
412+
if (!(dev = init_ac97())) {
413+
return false;
414+
}
415+
if (!mixer_init(dev, 50, 25, 64)) {
416+
dprintf("ac97: mixer init failed\n");
417+
return false;
427418
}
428419
return true;
429420
}

modules/ac97/ac97.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,6 @@ uint32_t ac97_buffered_ms(void);
145145
*/
146146
uint32_t ac97_get_hz(void);
147147

148-
/**
149-
* @brief Test helper: load a RAW S16LE stereo file and enqueue it.
150-
*
151-
* Loads "/system/webserver/test.raw" (header-less, S16LE, stereo) into
152-
* memory and enqueues the entire file into the software FIFO using
153-
* push_all_s16le(). Playback proceeds asynchronously via ac97_idle().
154-
*
155-
* @note The stream must already be prepared; the file’s sample rate must
156-
* match the active DAC rate or it will play at the wrong speed.
157-
* @warning Reads the whole file into memory in one go (no streaming).
158-
*/
159-
void ac97_test_melody(void);
160-
161148
/**
162149
* @brief Prepare AC’97 PCM Out stream buffers and descriptors.
163150
*

0 commit comments

Comments
 (0)