Files
KirkOS/oldsrc/audio/pcm.c
T
2026-05-18 04:02:59 -04:00

173 lines
7.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "pcm.h"
#include "hda.h"
#include "mm/pmm.h"
#include "mm/vmm.h"
#include "mm/memory.h"
#include "libk/stdio.h"
#include "libk/debug.h"
/* ── WAV chunk scanner ──────────────────────────────────────────────────────── */
/*
* Scan a buffer for RIFF/WAVE format. If it is a valid PCM WAV file, fills
* in *fmt_out, *data_offset and *data_size and returns true.
*
* WAV chunks are word-padded (each chunk rounds up to an even byte count), so
* we skip through them by (8 + ALIGN_UP(chunk_size, 2)) bytes at a time.
*/
static bool parse_wav(const uint8_t *buf, uint32_t buf_size,
hda_format_t *fmt_out,
uint32_t *data_offset,
uint32_t *data_size)
{
if (buf_size < 12) return false;
/* Check RIFF header */
if (buf[0]!='R'||buf[1]!='I'||buf[2]!='F'||buf[3]!='F') return false;
if (buf[8]!='W'||buf[9]!='A'||buf[10]!='V'||buf[11]!='E') return false;
uint32_t pos = 12;
bool got_fmt = false;
bool got_data = false;
hda_format_t fmt = {0};
while (pos + 8 <= buf_size) {
/* Read chunk ID and size (little-endian) */
const char *id = (const char *)(buf + pos);
uint32_t chunk_size = *(const uint32_t *)(buf + pos + 4);
pos += 8;
if (pos + chunk_size > buf_size) break; /* truncated */
if (id[0]=='f' && id[1]=='m' && id[2]=='t' && id[3]==' ') {
if (chunk_size < 16) { pos += chunk_size; continue; }
uint16_t audio_fmt = *(uint16_t *)(buf + pos + 0);
if (audio_fmt != 1) {
kprintf("[PCM] WAV audio format %u not supported (only PCM=1)\n",
audio_fmt);
return false;
}
fmt.channels = (uint8_t)*(uint16_t *)(buf + pos + 2);
fmt.sample_rate = *(uint32_t *)(buf + pos + 4);
fmt.bits_per_sample = (uint8_t)*(uint16_t *)(buf + pos + 14);
got_fmt = true;
} else if (id[0]=='d' && id[1]=='a' && id[2]=='t' && id[3]=='a') {
*data_offset = pos;
*data_size = chunk_size;
got_data = true;
break; /* no need to scan further */
}
/* Advance past chunk (WAV chunks are word-aligned) */
pos += chunk_size;
if (chunk_size & 1) pos++; /* skip pad byte */
}
if (!got_fmt || !got_data) return false;
*fmt_out = fmt;
return true;
}
/* ── Shared internal helper ─────────────────────────────────────────────────── */
static bool play_from_ext2(const char *filename,
bool force_fmt,
hda_format_t forced)
{
/* ── 1. Resolve path and read inode ─────────────────────────────────────── */
uint32_t inum = ext2_resolve_path(filename);
if (!inum) {
kprintf("[PCM] file not found: %s\n", filename);
return false;
}
ext2_inode_t inode;
if (!ext2_read_inode(inum, &inode)) {
kprintf("[PCM] failed to read inode for %s\n", filename);
return false;
}
uint32_t file_size = inode.i_size;
if (!file_size) {
kprintf("[PCM] %s is empty\n", filename);
return false;
}
/* ── 2. Allocate physically-contiguous DMA buffer ────────────────────────── */
/*
* pmm_alloc() returns a contiguous run of physical pages, which is exactly
* what HDA's DMA engine needs. We access the data through HHDM.
*/
uint32_t pages = (file_size + PAGE_SIZE - 1) / PAGE_SIZE;
void *phys = pmm_alloc(pages);
if (!phys) {
kprintf("[PCM] OOM: cannot allocate %u pages for %s\n", pages, filename);
return false;
}
uint8_t *virt = (uint8_t *)((uintptr_t)phys + MEM_PHYS_OFFSET);
/* Zero any padding at the end so stale bytes don't become noise */
memset(virt + file_size, 0, (size_t)(pages * PAGE_SIZE - file_size));
/* ── 3. Read file data ───────────────────────────────────────────────────── */
if (!ext2_read_file(&inode, virt)) {
kprintf("[PCM] failed to read %s\n", filename);
pmm_free(phys, pages);
return false;
}
/* ── 4. Determine format and audio data region ───────────────────────────── */
hda_format_t fmt;
uintptr_t audio_phys = (uintptr_t)phys;
uint32_t audio_size = file_size;
if (force_fmt) {
fmt = forced;
/* Treat the whole file as raw audio samples */
} else {
uint32_t data_offset = 0, data_size = 0;
if (parse_wav(virt, file_size, &fmt, &data_offset, &data_size)) {
/* Adjust the physical pointer to skip the WAV headers */
audio_phys += data_offset;
audio_size = data_size;
kprintf("[PCM] WAV: %u Hz %u-bit %u ch %u bytes of audio\n",
fmt.sample_rate, fmt.bits_per_sample, fmt.channels, audio_size);
} else {
/* Not a WAV assume raw 48 kHz, 16-bit stereo */
fmt.sample_rate = 48000;
fmt.bits_per_sample = 16;
fmt.channels = 2;
kprintf("[PCM] Raw PCM assumed: 48000 Hz 16-bit stereo %u bytes\n",
audio_size);
}
}
/* ── 5. Play ─────────────────────────────────────────────────────────────── */
bool ok = hda_play_pcm(audio_phys, audio_size, fmt);
if (ok)
hda_wait_complete(); /* blocks until the stream finishes */
else
kprintf("[PCM] hda_play_pcm failed\n");
/* ── 6. Free the DMA buffer ─────────────────────────────────────────────── */
pmm_free(phys, pages);
return ok;
}
/* ══════════════════════════════════════════════════════════════════════════════
* Public API
* ══════════════════════════════════════════════════════════════════════════════ */
bool pcm_play_file(const char *filename)
{
hda_format_t dummy = {0};
return play_from_ext2(filename, false, dummy);
}
bool pcm_play_raw(const char *filename, hda_format_t fmt)
{
return play_from_ext2(filename, true, fmt);
}