#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); }