add opus audio support
This commit is contained in:
parent
df0b7f5598
commit
fe781beef4
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,3 +23,4 @@ minecraft/gen/map.txt
|
||||
test.pack
|
||||
pack_main
|
||||
.gdb_history
|
||||
*.pcm
|
||||
10
Makefile
10
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:
|
||||
|
||||
BIN
audio/Suite.opus.bin
Normal file
BIN
audio/Suite.opus.bin
Normal file
Binary file not shown.
@ -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
7
include/audio.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace audio {
|
||||
void init();
|
||||
void load();
|
||||
void update();
|
||||
}
|
||||
@ -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
123
src/audio.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
5
tool/Makefile
Normal 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
BIN
tool/a.out
Executable file
Binary file not shown.
BIN
tool/opus_encode
Executable file
BIN
tool/opus_encode
Executable file
Binary file not shown.
121
tool/opus_encode.c
Normal file
121
tool/opus_encode.c
Normal 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user