add opus audio support

This commit is contained in:
Zack Buhman 2026-03-25 19:36:19 -05:00
parent df0b7f5598
commit fe781beef4
13 changed files with 276 additions and 5 deletions

3
.gitignore vendored
View File

@ -22,4 +22,5 @@ minecraft/region*.dump
minecraft/gen/map.txt
test.pack
pack_main
.gdb_history
.gdb_history
*.pcm

View File

@ -22,8 +22,12 @@ CFLAGS += -DREAD_PACK_FILE
endif
CFLAGS += -I./include
CFLAGS += -I../SDL3-dist/include
CFLAGS += -I../opus-dist/include
LDFLAGS += -lm
ifeq ($(shell uname),Linux)
LDFLAGS += -Wl,-z noexecstack
endif
MINECRAFT_OBJS = \
minecraft/love2dworld/inthash.o \
@ -58,6 +62,7 @@ OBJS = \
src/lua_api.o \
src/pixel_line_art.o \
src/flame.o \
src/audio.o \
data/scenes/ship20/ship20.o \
data/scenes/noodle/noodle.o \
data/scenes/shadow_test/shadow_test.o \
@ -83,13 +88,10 @@ test.pack: pack_main $(PACK_FILENAMES)
test.pack.o: test.pack
$(OBJCOPY) -I binary -O $(OBJARCH) $< $@
test.so: $(OBJS)
$(CC) $(ARCH) $(OPT) -Wl,-z noexecstack -shared $(DEBUG) $^ -o $@ -lSDL3
test.dll: $(OBJS)
$(CXX) $(ARCH) $(OPT) -mthreads -static -mdll -static-libstdc++ -static-libgcc $(DEBUG) $^ -o $@ -L. -lSDL3 $(WINDOWS)
main: $(OBJS) src/main.o ../SDL3-dist/lib64/libSDL3.a
main: $(OBJS) src/main.o ../SDL3-dist/lib64/libSDL3.a ../opus-dist/lib/libopus.a
$(CC) $(ARCH) $(LDFLAGS) $(OPT) $(DEBUG) $^ -o $@
clean:

BIN
audio/Suite.opus.bin Normal file

Binary file not shown.

View File

@ -55,3 +55,4 @@ data/scenes/book/book.idx
shader/flame.vert
shader/flame.frag
minecraft/flame.data
audio/Suite.opus.bin

7
include/audio.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
namespace audio {
void init();
void load();
void update();
}

View File

@ -1,3 +1,9 @@
cmake ../SDL3-3.4.2/ -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../SDL3-dist
cmake --build . --config Debug
cmake --install . --config Debug
# opus
../opus-1.6.1/configure LDFLAGS="-static" CFLAGS="-march=x86-64-v3 -O2 -pipe" --prefix=../opus-dist
make
make install

123
src/audio.cpp Normal file
View File

@ -0,0 +1,123 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <opus/opus.h>
#include <SDL3/SDL.h>
#include "file.h"
#include "audio.h"
namespace audio {
int const frame_samples = 960; // 20 milliseconds @ 48kHz
int const sample_rate = 48000;
int const channels = 2;
int const sample_size = (sizeof (int16_t));
int const max_frame_size = 960 * 3; // 20ms at 48kHz
int const max_packet_size = 1275;
//
SDL_AudioStream * audio_stream;
SDL_AudioSpec audio_spec;
void const * audio_buf;
int audio_size;
int audio_samples_total;
int audio_offset;
int audio_samples_decoded;
OpusDecoder * opus_decoder;
void init()
{
audio_spec.channels = channels;
audio_spec.format = SDL_AUDIO_S16LE;
audio_spec.freq = sample_rate;
audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec, NULL, NULL);
assert(audio_stream);
SDL_ResumeAudioStreamDevice(audio_stream);
int err;
opus_decoder = opus_decoder_create(sample_rate, channels, &err);
if (err < 0) {
fprintf(stderr, "opus_decoder_create: %s\n", opus_strerror(err));
assert(!"opus_decoder_create");
}
}
void load()
{
audio_buf = file::read_file("audio/Suite.opus.bin", &audio_size);
assert(audio_buf != nullptr);
uint8_t const * buf = (uint8_t const *)audio_buf;
audio_samples_total
= (buf[3] << 24)
| (buf[2] << 16)
| (buf[1] << 8)
| (buf[0] << 0);
printf("audio samples total: %d\n", audio_samples_total);
audio_offset = 4;
audio_samples_decoded = 0;
}
inline static int min(int a, int b)
{
return (a < b) ? a : b;
}
void update()
{
int half_period_samples = audio_spec.freq / 2;
int half_period_size = half_period_samples * sample_size * audio_spec.channels;
if (SDL_GetAudioStreamQueued(audio_stream) < half_period_size) {
int put_samples = half_period_samples;
while (put_samples > 0) {
uint8_t const * buf = (uint8_t const *)audio_buf;
assert(audio_offset <= audio_size);
if (audio_offset == audio_size) {
assert(audio_samples_decoded == audio_samples_total);
audio_offset = 4;
audio_samples_decoded = 0;
int err = opus_decoder_ctl(opus_decoder, OPUS_RESET_STATE);
if (err < 0) {
fprintf(stderr, "opus_encoder_ctl(OPUS_RESET_STATE): %s\n", opus_strerror(err));
assert(!"opus_encoder_ctl");
}
}
uint16_t packet_size = (buf[audio_offset + 1] << 8) | (buf[audio_offset + 0] << 0);
audio_offset += 2;
int16_t decode_buf[max_frame_size * channels];
int frame_size = opus_decode(opus_decoder, &buf[audio_offset], packet_size, decode_buf, max_frame_size, 0);
if (frame_size < 0) {
fprintf(stderr, "opus_decode: %s\n", opus_strerror(frame_size));
assert(!"opus_decode\n");
}
audio_offset += packet_size;
int max_samples_this_frame = audio_samples_total - audio_samples_decoded;
assert(max_samples_this_frame > 0);
/*
if (frame_size > max_samples_this_frame) {
fprintf(stderr, "max samples %d %d\n", frame_size, max_samples_this_frame);
}
*/
frame_size = min(max_samples_this_frame, frame_size);
SDL_PutAudioStreamData(audio_stream,
(void *)decode_buf,
frame_size * channels * sample_size);
put_samples -= frame_size;
audio_samples_decoded += frame_size;
}
}
}
}

View File

@ -8,6 +8,7 @@
#include "test.h"
#include "window.h"
#include "view.h"
#include "audio.h"
static int const max_gamepads = 16;
static SDL_Gamepad * gamepads[max_gamepads];
@ -125,6 +126,8 @@ int main()
}
load(".");
audio::init();
audio::load();
update_window(1024, 1024);
@ -153,6 +156,8 @@ int main()
update();
draw();
audio::update();
SDL_GL_SwapWindow(window);
}

5
tool/Makefile Normal file
View File

@ -0,0 +1,5 @@
OPT = -O2
CFLAGS = -I../../opus-dist/include
opus_encode: opus_encode.c ../../opus-dist/lib/libopus.a
gcc -o $@ $(OPT) $(CFLAGS) -lm $^

BIN
tool/a.out Executable file

Binary file not shown.

BIN
tool/opus_encode Executable file

Binary file not shown.

121
tool/opus_encode.c Normal file
View File

@ -0,0 +1,121 @@
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <opus/opus.h>
const int frame_size = 960; // 20ms at 48kHz
const int sample_rate = 48000;
const int channels = 2;
const int bitrate = 128000;
const int max_packet_size = 3 * 1275;
int main(int argc, char *argv[])
{
assert(argc == 3);
int err;
OpusEncoder * encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_AUDIO, &err);
if (err < 0) {
fprintf(stderr, "opus_encoder_create: %s\n", opus_strerror(err));
return 1;
}
err = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate));
if (err < 0) {
fprintf(stderr, "opus_encoder_ctl(OPUS_SET_BITRATE): %s\n", opus_strerror(err));
return 1;
}
char const * input_filename = argv[1];
FILE * input_file = fopen(input_filename, "rb");
if (input_file == NULL) {
fprintf(stderr, "fopen(%s): %s\n", input_filename, strerror(errno));
return 1;
}
char const * output_filename = argv[2];
FILE * output_file = fopen(output_filename, "wb");
if (input_file == NULL) {
fprintf(stderr, "fopen(%s): %s\n", output_filename, strerror(errno));
return 1;
}
int16_t input[frame_size * channels];
uint8_t input_buf[frame_size * channels * (sizeof (int16_t))];
uint8_t encode_buf[max_packet_size];
int total_samples = 0;
size_t header_write_1 = fwrite(&total_samples, 1, (sizeof (int)), output_file);
if (header_write_1 != (sizeof (int))) {
fprintf(stderr, "fwrite: %s\n", strerror(errno));
return 1;
}
bool more_data = true;
while (more_data) {
size_t samples = fread(input_buf, (sizeof (int16_t)) * channels, frame_size, input_file);
if (samples == 0)
break;
total_samples += samples;
if (samples != frame_size) {
printf("padding short frame %ld\n", samples);
more_data = false;
for (int i = samples * channels; i < frame_size * channels; i++) {
input_buf[i * 2 + 0] = 0;
input_buf[i * 2 + 1] = 0;
}
}
samples = frame_size;
for (int i = 0; i < channels * frame_size; i++) {
// load little endian
input[i] = (input_buf[2 * i + 1] << 8) | (input_buf[2 * i + 0] << 0);
}
int bytes = opus_encode(encoder, input, frame_size, encode_buf, max_packet_size);
if (bytes < 0) {
fprintf(stderr, "opus_encode: %s\n", opus_strerror(bytes));
return 1;
}
assert(bytes <= 1275);
int16_t encode_bytes = bytes;
size_t bytes_write = fwrite(&encode_bytes, 1, (sizeof (encode_bytes)), output_file);
if (bytes_write != (sizeof (encode_bytes))) {
fprintf(stderr, "fwrite: %s\n", strerror(errno));
return 1;
}
size_t buf_write = fwrite(encode_buf, 1, bytes, output_file);
if (buf_write != bytes) {
fprintf(stderr, "fwrite: %s\n", strerror(errno));
return 1;
}
}
fflush(output_file);
int ret = fseek(output_file, SEEK_SET, 0);
if (ret != 0) {
fprintf(stderr, "fseek: %s\n", strerror(errno));
return 1;
}
size_t header_write_2 = fwrite(&total_samples, 1, (sizeof (int)), output_file);
if (header_write_2 != (sizeof (int))) {
fprintf(stderr, "fwrite: %s\n", strerror(errno));
return 1;
}
printf("total_samples: %d\n", total_samples);
fclose(output_file);
fclose(input_file);
return 0;
}