AtlatestRepositorysigil-audio
sigil-audio / tree / src / cogg-encode.c
1
/*2
* ogg-encode.c - OGG Vorbis encoding for sigil-audio3
*/5
#include "sigil/sigil.h"7
#include <stdio.h>8
#include <stdlib.h>9
#include <string.h>11
#include <vorbis/vorbisenc.h>12
#include <ogg/ogg.h>14
/* Monotonic serial counter for ogg streams (avoids polluting global RNG) */15
static int g_ogg_serial = 1;17
/*18
* (write-ogg path sample-buffer sample-rate channels quality) -> boolean19
*20
* Encode interleaved float32 samples to an OGG Vorbis file.21
*22
* path: Output file path (string)23
* sample-buffer: Bytevector of interleaved float32 samples24
* sample-rate: Sample rate in Hz (integer, e.g. 44100)25
* channels: Number of channels (integer, 1 or 2)26
* quality: VBR quality 0.0-1.0 (flonum, 0.4 ~ 128kbps)27
*28
* Returns #t on success, #f on failure.29
*/30
static Value native_write_ogg(SigilVM *vm, int argc, Value *args)31
{32
if (argc < 5) {33
sigil__vm_set_error(vm, SIGIL_ERR_ARITY,34
"write-ogg: requires path, sample-buffer, sample-rate, channels, quality");35
return SIGIL_FALSE;36
}38
if (!sigil_is_string(args[0])) {39
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "write-ogg: path must be a string");40
return SIGIL_FALSE;41
}43
if (!sigil_is_bytevector(args[1])) {44
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,45
"write-ogg: sample-buffer must be a bytevector");46
return SIGIL_FALSE;47
}49
if (!sigil_is_fixnum(args[2])) {50
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,51
"write-ogg: sample-rate must be an integer");52
return SIGIL_FALSE;53
}55
if (!sigil_is_fixnum(args[3])) {56
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,57
"write-ogg: channels must be an integer");58
return SIGIL_FALSE;59
}61
if (!sigil_is_flonum(args[4])) {62
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,63
"write-ogg: quality must be a flonum");64
return SIGIL_FALSE;65
}67
SigilString *path_str = (SigilString *)sigil_as_ptr(args[0]);68
const char *path = path_str->data;70
uint8_t *bv_data = sigil_bytevector_data(args[1]);71
size_t bv_len = sigil_bytevector_length(args[1]);73
int sample_rate = (int)sigil_as_fixnum(args[2]);74
int channels = (int)sigil_as_fixnum(args[3]);75
float quality = (float)sigil_as_flonum(args[4]);77
if (channels < 1 || channels > 2) {78
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,79
"write-ogg: channels must be 1 or 2");80
return SIGIL_FALSE;81
}83
if (sample_rate < 1 || sample_rate > 192000) {84
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,85
"write-ogg: sample-rate out of range");86
return SIGIL_FALSE;87
}89
if (quality < 0.0f) quality = 0.0f;90
if (quality > 1.0f) quality = 1.0f;92
size_t num_frames = (bv_len / sizeof(float)) / channels;93
float *samples = (float *)bv_data;95
if (num_frames == 0) {96
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,97
"write-ogg: sample-buffer is empty");98
return SIGIL_FALSE;99
}101
FILE *fp = fopen(path, "wb");102
if (!fp) {103
sigil__vm_set_error(vm, SIGIL_ERR_IO,104
"write-ogg: cannot open output file");105
return SIGIL_FALSE;106
}108
/* Initialize vorbis encoder */109
vorbis_info vi;110
vorbis_info_init(&vi);112
int ret = vorbis_encode_init_vbr(&vi, channels, sample_rate, quality);113
if (ret) {114
vorbis_info_clear(&vi);115
fclose(fp);116
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,117
"write-ogg: vorbis encoder init failed");118
return SIGIL_FALSE;119
}121
vorbis_comment vc;122
vorbis_comment_init(&vc);123
vorbis_comment_add_tag(&vc, "ENCODER", "sigil-audio");125
vorbis_dsp_state vd;126
vorbis_block vb;127
vorbis_analysis_init(&vd, &vi);128
vorbis_block_init(&vd, &vb);130
ogg_stream_state os;131
ogg_stream_init(&os, g_ogg_serial++);133
/* Write Vorbis headers */134
ogg_packet header;135
ogg_packet header_comm;136
ogg_packet header_code;138
vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);139
ogg_stream_packetin(&os, &header);140
ogg_stream_packetin(&os, &header_comm);141
ogg_stream_packetin(&os, &header_code);143
ogg_page og;144
while (ogg_stream_flush(&os, &og)) {145
fwrite(og.header, 1, og.header_len, fp);146
fwrite(og.body, 1, og.body_len, fp);147
}149
/* Encode samples in chunks */150
int eos = 0;151
int eos_signaled = 0;152
size_t frames_written = 0;153
const size_t CHUNK_FRAMES = 4096;155
while (!eos) {156
size_t frames_remaining = num_frames - frames_written;158
if (frames_remaining == 0) {159
if (!eos_signaled) {160
vorbis_analysis_wrote(&vd, 0);161
eos_signaled = 1;162
}163
} else {164
size_t chunk = frames_remaining < CHUNK_FRAMES ?165
frames_remaining : CHUNK_FRAMES;167
float **buffer = vorbis_analysis_buffer(&vd, (int)chunk);169
/* Deinterleave samples into vorbis channel buffers */170
if (channels == 1) {171
memcpy(buffer[0], samples + frames_written, chunk * sizeof(float));172
} else {173
float *left = buffer[0];174
float *right = buffer[1];175
const float *src = samples + frames_written * 2;176
for (size_t f = 0; f < chunk; f++) {177
left[f] = src[f * 2];178
right[f] = src[f * 2 + 1];179
}180
}182
vorbis_analysis_wrote(&vd, (int)chunk);183
frames_written += chunk;184
}186
/* Pull encoded blocks */187
while (vorbis_analysis_blockout(&vd, &vb) == 1) {188
vorbis_analysis(&vb, NULL);189
vorbis_bitrate_addblock(&vb);191
ogg_packet op;192
while (vorbis_bitrate_flushpacket(&vd, &op)) {193
ogg_stream_packetin(&os, &op);195
while (!eos) {196
if (!ogg_stream_pageout(&os, &og)) break;198
fwrite(og.header, 1, og.header_len, fp);199
fwrite(og.body, 1, og.body_len, fp);201
if (ogg_page_eos(&og)) eos = 1;202
}203
}204
}205
}207
/* Clean up */208
ogg_stream_clear(&os);209
vorbis_block_clear(&vb);210
vorbis_dsp_clear(&vd);211
vorbis_comment_clear(&vc);212
vorbis_info_clear(&vi);213
fclose(fp);215
return SIGIL_TRUE;216
}218
/*219
* (make-float-buffer vec) -> bytevector220
*221
* Convert a Scheme vector of numbers to a bytevector of float32 data.222
* Useful for preparing sample data for write-ogg.223
*/224
static Value native_make_float_buffer(SigilVM *vm, int argc, Value *args)225
{226
if (argc < 1 || !sigil_is_vector(args[0])) {227
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,228
"make-float-buffer: expected vector of numbers");229
return SIGIL_FALSE;230
}232
size_t len = sigil_vector_length(args[0]);233
Value bv = sigil_make_bytevector(vm, len * sizeof(float));234
float *data = (float *)sigil_bytevector_data(bv);236
for (size_t i = 0; i < len; i++) {237
Value elem = sigil_vector_ref(args[0], i);238
if (sigil_is_flonum(elem)) {239
data[i] = (float)sigil_as_flonum(elem);240
} else if (sigil_is_fixnum(elem)) {241
data[i] = (float)sigil_as_fixnum(elem);242
} else {243
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,244
"make-float-buffer: vector element is not a number");245
return SIGIL_FALSE;246
}247
}249
return bv;250
}252
/*253
* Register OGG encoding functions into the (sigil audio) module.254
* Called from sigil__init_sigil_audio_module in audio.c.255
*/256
void sigil__register_ogg_encode(SigilVM *vm)257
{258
sigil_module_register_native(vm, "%write-ogg", native_write_ogg,259
SIGIL_ARITY_EXACT(5),260
"Encode float samples to OGG Vorbis file (internal)");261
sigil_module_export(vm, "%write-ogg");263
sigil_module_register_native(vm, "make-float-buffer", native_make_float_buffer,264
SIGIL_ARITY_EXACT(1),265
"Convert vector of numbers to float32 bytevector");266
sigil_module_export(vm, "make-float-buffer");267
}