b28a6bcf29
Signed-off-by: kaguya3311 <kaguya3311@national.shitposting.agency>
173 lines
7.0 KiB
C
173 lines
7.0 KiB
C
#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);
|
||
} |