diff --git a/.gitignore b/.gitignore index 8885f53..3949d47 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ minecraft/region*.dump minecraft/gen/map.txt test.pack pack_main -.gdb_history \ No newline at end of file +.gdb_history +*.pcm \ No newline at end of file diff --git a/Makefile b/Makefile index 56fb9de..a23a8cf 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/Suite.mp3 b/audio/Suite.mp3 similarity index 100% rename from Suite.mp3 rename to audio/Suite.mp3 diff --git a/audio/Suite.opus.bin b/audio/Suite.opus.bin new file mode 100644 index 0000000..08c8f23 Binary files /dev/null and b/audio/Suite.opus.bin differ diff --git a/filenames.txt b/filenames.txt index 8c4798f..c6d79ca 100644 --- a/filenames.txt +++ b/filenames.txt @@ -55,3 +55,4 @@ data/scenes/book/book.idx shader/flame.vert shader/flame.frag minecraft/flame.data +audio/Suite.opus.bin diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 0000000..9a79fbd --- /dev/null +++ b/include/audio.h @@ -0,0 +1,7 @@ +#pragma once + +namespace audio { + void init(); + void load(); + void update(); +} diff --git a/sdl3-build.sh b/sdl3-build.sh index 5993798..86ece82 100644 --- a/sdl3-build.sh +++ b/sdl3-build.sh @@ -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 diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..b1a8256 --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,123 @@ +#include +#include +#include + +#include +#include + +#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; + } + } + } +} diff --git a/src/main.cpp b/src/main.cpp index 46828fc..2676071 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); } diff --git a/tool/Makefile b/tool/Makefile new file mode 100644 index 0000000..88eee64 --- /dev/null +++ b/tool/Makefile @@ -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 $^ diff --git a/tool/a.out b/tool/a.out new file mode 100755 index 0000000..826bd21 Binary files /dev/null and b/tool/a.out differ diff --git a/tool/opus_encode b/tool/opus_encode new file mode 100755 index 0000000..5d7b014 Binary files /dev/null and b/tool/opus_encode differ diff --git a/tool/opus_encode.c b/tool/opus_encode.c new file mode 100644 index 0000000..ec0a3de --- /dev/null +++ b/tool/opus_encode.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include + +#include + +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; +}