Skip to content

Commit

Permalink
Switch wav64 and opus over to a file descriptor (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
nopjne authored Sep 30, 2024
1 parent 61fc54f commit 2d339c7
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 66 deletions.
6 changes: 3 additions & 3 deletions include/wav64.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ typedef struct {
*/
waveform_t wave;

/** @brief Absolute ROM address of WAV64 */
uint32_t rom_addr;

/** @brief File descriptor to read WAV64 */
int current_fd; ///< File descriptor for the wav64 file
int base_offset; ///< Start of Wav64 data.
int format; ///< Internal format of the file
void *ext; ///< Pointer to extended data (internal use)
} wav64_t;
Expand Down
1 change: 1 addition & 0 deletions src/asset_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,6 @@ typedef struct {


FILE *must_fopen(const char *fn);
int must_open(const char *fn);

#endif
73 changes: 41 additions & 32 deletions src/audio/wav64.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
#include "debug.h"
#include "utils.h"
#include "rspq.h"
#include "asset_internal.h"
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdalign.h>
#include <fcntl.h>
#include <unistd.h>

/** @brief Set to 1 to use the reference C decode for VADPCM */
#define VADPCM_REFERENCE_DECODER 0
Expand Down Expand Up @@ -148,7 +151,16 @@ static inline void rsp_vadpcm_decompress(void *input, int16_t *output, bool ster

#endif /* VADPCM_REFERENCE_DECODER */

void raw_waveform_read(samplebuffer_t *sbuf, int base_rom_addr, int wpos, int wlen, int bps) {
void raw_waveform_read(samplebuffer_t *sbuf, int current_fd, int wpos, int wlen, int bps) {
uint8_t* ram_addr = (uint8_t*)samplebuffer_append(sbuf, wlen);
int bytes = wlen << bps;

uint32_t t0 = TICKS_READ();
read(current_fd, ram_addr, bytes);
__wav64_profile_dma += TICKS_READ() - t0;
}

void raw_waveform_read_address(samplebuffer_t *sbuf, int base_rom_addr, int wpos, int wlen, int bps) {
uint32_t rom_addr = base_rom_addr + (wpos << bps);
uint8_t* ram_addr = (uint8_t*)samplebuffer_append(sbuf, wlen);
int bytes = wlen << bps;
Expand All @@ -165,7 +177,10 @@ void raw_waveform_read(samplebuffer_t *sbuf, int base_rom_addr, int wpos, int wl
static void waveform_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
wav64_t *wav = (wav64_t*)ctx;
int bps = (wav->wave.bits == 8 ? 0 : 1) + (wav->wave.channels == 2 ? 1 : 0);
raw_waveform_read(sbuf, wav->rom_addr, wpos, wlen, bps);
if (seeking) {
lseek(wav->current_fd, wav->base_offset + (wpos << bps), SEEK_SET);
}
raw_waveform_read(sbuf, wav->current_fd, wpos, wlen, bps);
}

static void waveform_vadpcm_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
Expand All @@ -175,12 +190,12 @@ static void waveform_vadpcm_read(void *ctx, samplebuffer_t *sbuf, int wpos, int
if (seeking) {
if (wpos == 0) {
memset(&vhead->state, 0, sizeof(vhead->state));
vhead->current_rom_addr = wav->rom_addr;
lseek(wav->current_fd, wav->base_offset, SEEK_SET);
} else {
assertf(wpos == wav->wave.len - wav->wave.loop_len,
"wav64: seeking to %x not supported (%x %x)\n", wpos, wav->wave.len, wav->wave.loop_len);
memcpy(&vhead->state, &vhead->loop_state, sizeof(vhead->state));
vhead->current_rom_addr = wav->rom_addr + (wav->wave.len - wav->wave.loop_len) / 16 * 9;
lseek(wav->current_fd, (wav->wave.len - wav->wave.loop_len) / 16 * 9, SEEK_CUR);
}
}

Expand Down Expand Up @@ -208,8 +223,7 @@ static void waveform_vadpcm_read(void *ctx, samplebuffer_t *sbuf, int wpos, int
void *src = (void*)dest + ((nframes*16) << SAMPLES_BPS_SHIFT(sbuf)) - src_bytes;

// Fetch compressed data
dma_read(src, vhead->current_rom_addr, src_bytes);
vhead->current_rom_addr += src_bytes;
read(wav->current_fd, src, src_bytes);

#if VADPCM_REFERENCE_DECODER
if (wav->wave.channels == 1) {
Expand Down Expand Up @@ -252,38 +266,30 @@ static void waveform_vadpcm_read(void *ctx, samplebuffer_t *sbuf, int wpos, int
rspq_highpri_end();
}

void wav64_open(wav64_t *wav, const char *fn) {
void wav64_open(wav64_t *wav, const char *file_name) {
memset(wav, 0, sizeof(*wav));

// Currently, we only support streaming WAVs from DFS (ROMs). Any other
// filesystem is unsupported.
// For backward compatibility, we also silently accept a non-prefixed path.
if (strstr(fn, ":/")) {
assertf(strncmp(fn, "rom:/", 5) == 0, "Cannot open %s: wav64 only supports files in ROM (rom:/)", fn);
fn += 5;
}

int fh = dfs_open(fn);
assertf(fh >= 0, "error opening file %s: %s (%d)\n", fn, dfs_strerror(fh), fh);

// Open the input file.
int file_handle = must_open(file_name);
wav64_header_t head = {0};
dfs_read(&head, 1, sizeof(head), fh);
read(file_handle, &head, sizeof(head));
if (memcmp(head.id, WAV64_ID, 4) != 0) {
assertf(memcmp(head.id, WAV_RIFF_ID, 4) != 0 && memcmp(head.id, WAV_RIFX_ID, 4) != 0,
"wav64 %s: use audioconv64 to convert to wav64 format", fn);
"wav64 %s: use audioconv64 to convert to wav64 format", file_name);
assertf(0, "wav64 %s: invalid ID: %02x%02x%02x%02x\n",
fn, head.id[0], head.id[1], head.id[2], head.id[3]);
file_name, head.id[0], head.id[1], head.id[2], head.id[3]);
}
assertf(head.version == WAV64_FILE_VERSION, "wav64 %s: invalid version: %02x\n",
fn, head.version);
file_name, head.version);

wav->wave.name = fn;
wav->wave.name = file_name;
wav->wave.channels = head.channels;
wav->wave.bits = head.nbits;
wav->wave.frequency = head.freq;
wav->wave.len = head.len;
wav->wave.loop_len = head.loop_len;
wav->rom_addr = dfs_rom_addr(fn) + head.start_offset;
wav->current_fd = file_handle;
wav->base_offset = head.start_offset;
wav->format = head.format;

switch (head.format) {
Expand All @@ -294,37 +300,36 @@ void wav64_open(wav64_t *wav, const char *fn) {

case WAV64_FORMAT_VADPCM: {
wav64_header_vadpcm_t vhead = {0};
dfs_read(&vhead, 1, sizeof(vhead), fh);

read(file_handle, &vhead, sizeof(vhead));
int codebook_size = vhead.npredictors * vhead.order * head.channels * sizeof(wav64_vadpcm_vector_t);

void *ext = malloc_uncached(sizeof(vhead) + codebook_size);
memcpy(ext, &vhead, sizeof(vhead));
dfs_read(ext + sizeof(vhead), 1, codebook_size, fh);
read(file_handle, ext + sizeof(vhead), codebook_size);
wav->ext = ext;
wav->wave.read = waveform_vadpcm_read;
wav->wave.ctx = wav;
assertf(head.loop_len == 0 || head.loop_len % 16 == 0,
"wav64 %s: invalid loop length: %ld\n", fn, head.loop_len);
"wav64 %s: invalid loop length: %ld\n", file_name, head.loop_len);
} break;

case WAV64_FORMAT_OPUS: {
wav64_opus_init(wav, fh);
wav64_opus_init(wav, file_handle);
} break;

default:
assertf(0, "wav64 %s: invalid format: %02x\n", fn, head.format);
assertf(0, "wav64 %s: invalid format: %02x\n", file_name, head.format);
}

dfs_close(fh);
lseek(wav->current_fd, wav->base_offset, SEEK_SET);
}

void wav64_play(wav64_t *wav, int ch)
{
// Update the context pointer, so that we try to catch cases where the
// wav64_t instance was moved.
wav->wave.ctx = wav;
mixer_ch_play(ch, &wav->wave);
mixer_ch_play(ch, &wav->wave);
}

void wav64_set_loop(wav64_t *wav, bool loop) {
Expand Down Expand Up @@ -362,5 +367,9 @@ void wav64_close(wav64_t *wav)
break;
}
wav->ext = NULL;
if (wav->current_fd >= 0) {
close(wav->current_fd);
wav->current_fd = -1;
}
}
}
46 changes: 20 additions & 26 deletions src/audio/wav64_opus.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "n64sys.h"
#include "rspq.h"
#include "utils.h"
#include <unistd.h>

#include "libopus_internal.h"

Expand All @@ -54,35 +55,28 @@ typedef struct {
/// @brief Wav64 Opus state
typedef struct {
wav64_opus_header_ext xhead; ///< Opus header extension
uint32_t current_rom_addr; ///< Current ROM reading address
OpusCustomMode *mode; ///< Opus custom mode for this file
OpusCustomDecoder *dec; ///< Opus decoder for this file
} wav64_opus_state;

static uint16_t io_read16(uint32_t addr) {
uint32_t value = io_read(addr & ~3);
if (!(addr & 2))
value >>= 16;
return value;
}


static void waveform_opus_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
static int callcount = 0;
callcount += 1;
wav64_t *wav = (wav64_t*)ctx;
wav64_opus_state *st = (wav64_opus_state*)wav->ext;

if (seeking) {
if (wpos == 0) {
st->current_rom_addr = wav->rom_addr;
opus_custom_decoder_ctl(st->dec, OPUS_RESET_STATE);
lseek(wav->current_fd, wav->base_offset, SEEK_SET);
opus_custom_decoder_ctl(st->dec, OPUS_RESET_STATE);
} else {
assertf(0, "seeking not support in wav64 with opus compression");
assertf(0, "seeking not support in wav64 with opus compression");
}
}

// Allocate stack buffer for reading compressed data. Align it to cacheline
// to avoid any false sharing.
uint8_t alignas(16) buf[st->xhead.max_cmp_frame_size];
uint8_t alignas(16) buf[st->xhead.max_cmp_frame_size + 1];
int nframes = DIVIDE_CEIL(wlen, st->xhead.frame_size);

// Make space for the decoded samples. Call samplebuffer_append once as we
Expand All @@ -97,17 +91,18 @@ static void waveform_opus_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wl
memset(out, 0, st->xhead.frame_size * wav->wave.channels * sizeof(int16_t));
} else {
// Read frame size
int nb = io_read16(st->current_rom_addr);
assertf(nb <= st->xhead.max_cmp_frame_size, "opus frame size too large: %d (%ld)", nb, st->xhead.max_cmp_frame_size);
uint16_t nb = 0;
read(wav->current_fd, &nb, 2);
assertf(nb <= st->xhead.max_cmp_frame_size, "opus frame size too large: %08X (%ld) %d", nb, st->xhead.max_cmp_frame_size, callcount);

// Read frame
data_cache_hit_writeback_invalidate(buf, nb);
dma_read(buf, st->current_rom_addr + 2, nb);
unsigned long aligned_frame_size = nb;
if (aligned_frame_size & 1) {
aligned_frame_size += 1;
}

// Update ROM pointer after current frame
st->current_rom_addr += nb + 2;
if (st->current_rom_addr & 1)
st->current_rom_addr += 1;
// Read frame
data_cache_hit_writeback_invalidate(buf, aligned_frame_size);
read(wav->current_fd, buf, aligned_frame_size);

// Decode frame
int err = opus_custom_decode(st->dec, buf, nb, out, st->xhead.frame_size);
Expand All @@ -127,22 +122,21 @@ static void waveform_opus_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wl
}

void wav64_opus_init(wav64_t *wav, int fh) {
wav64_opus_header_ext xhead;
dfs_read(&xhead, sizeof(xhead), 1, fh);
wav64_opus_header_ext xhead;
read(fh, &xhead, sizeof(xhead));
debugf("opus header: frame_size=%ld, max_cmp_frame_size=%ld, bitrate_bps=%ld\n", xhead.frame_size, xhead.max_cmp_frame_size, xhead.bitrate_bps);
debugf("frequency: %f\n", wav->wave.frequency);

rsp_opus_init();

int err = OPUS_OK;
OpusCustomMode *custom_mode = opus_custom_mode_create(wav->wave.frequency, xhead.frame_size, &err);
assert(err == OPUS_OK);
assertf(err == OPUS_OK, "%i", err);
OpusCustomDecoder *dec = opus_custom_decoder_create(custom_mode, wav->wave.channels, &err);
assert(err == OPUS_OK);

// FIXME: try to avoid one allocation by allocating the decoder in the same malloc
wav64_opus_state *state = malloc(sizeof(wav64_opus_state));
state->current_rom_addr = 0;
state->mode = custom_mode;
state->dec = dec;
state->xhead = xhead;
Expand Down
1 change: 1 addition & 0 deletions src/audio/wav64_opus_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define LIBDRAGON_AUDIO_WAV64_OPUS_INTERNAL_H

#include <stdint.h>
#include <stdio.h>
#include "wav64.h"

/** @brief Initialize opus decompression on a wav64 file */
Expand Down
15 changes: 11 additions & 4 deletions src/audio/wav64internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ typedef struct __attribute__((aligned(8))) {
typedef struct __attribute__((packed, aligned(8))) {
int8_t npredictors; ///< Number of predictors
int8_t order; ///< Order of the predictors
int16_t padding; ///< Padding
uint32_t current_rom_addr; ///< Current address in ROM
uint16_t padding; ///< padding
uint32_t padding1; ///< padding1
wav64_vadpcm_vector_t loop_state[2];///< State at the loop point
wav64_vadpcm_vector_t state[2]; ///< Current decompression state
wav64_vadpcm_vector_t codebook[]; ///< Codebook of the predictors
Expand All @@ -43,8 +43,15 @@ typedef struct samplebuffer_s samplebuffer_t;
/**
* @brief Utility function to help implementing #WaveformRead for uncompressed (raw) samples.
*
* This function uses PI DMA to load samples from ROM into the sample buffer.
* This function uses a file descriptor to load samples from ROM into the sample buffer.
*/
void raw_waveform_read(samplebuffer_t *sbuf, int base_rom_addr, int wpos, int wlen, int bps);
void raw_waveform_read(samplebuffer_t *sbuf, int fd, int wpos, int wlen, int bps);

/**
* @brief Utility function to help implementing #WaveformRead for uncompressed (raw) samples.
*
* This function uses PI DMA to load samples from ROM into the sample buffer.
* Note: Tempory function should be removed when XM64 moves to using FILE*.
*/
void raw_waveform_read_address(samplebuffer_t *sbuf, int rom_addr, int wpos, int wlen, int bps);
#endif
2 changes: 1 addition & 1 deletion src/audio/xm64.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

static void wave_read(void *ctx, samplebuffer_t *sbuf, int wpos, int wlen, bool seeking) {
xm_sample_t *samp = (xm_sample_t*)ctx;
raw_waveform_read(sbuf, samp->data8_offset, wpos, wlen, samp->bits >> 4);
raw_waveform_read_address(sbuf, samp->data8_offset, wpos, wlen, samp->bits >> 4);
}

static int tick(void *arg) {
Expand Down

0 comments on commit 2d339c7

Please sign in to comment.