/* * wav2ogg.c - codifica um WAV PCM 16 bits estereo em Ogg Vorbis, sempre com os mesmos bytes. * * Escrito para o fredygames.com (render-music.sh do Crimson Fields). O oggenc sorteia o numero de * serie do stream; aqui ele e fixo, entao a mesma entrada da sempre o mesmo arquivo. * Copyright (C) 2026 FredyGames. Mesma licenca do Crimson Fields: GNU GPL versao 2 ou posterior. * * wav2ogg entrada.wav saida.ogg qualidade(-0.1 a 1.0) */ #include #include #include #include static unsigned le32(const unsigned char *p) { return p[0] | p[1] << 8 | p[2] << 16 | (unsigned)p[3] << 24; } static unsigned le16(const unsigned char *p) { return p[0] | p[1] << 8; } static void write_page(FILE *out, ogg_page *og) { fwrite(og->header, 1, og->header_len, out); fwrite(og->body, 1, og->body_len, out); } int main(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "uso: wav2ogg entrada.wav saida.ogg qualidade\n"); return 2; } FILE *in = fopen(argv[1], "rb"); if (!in) { perror(argv[1]); return 1; } fseek(in, 0, SEEK_END); long size = ftell(in); fseek(in, 0, SEEK_SET); unsigned char *wav = malloc(size); if (!wav || fread(wav, 1, size, in) != (size_t)size) { fprintf(stderr, "erro de leitura\n"); return 1; } fclose(in); if (size < 12 || memcmp(wav, "RIFF", 4) || memcmp(wav + 8, "WAVE", 4)) { fprintf(stderr, "nao e WAV\n"); return 1; } unsigned channels = 0, rate = 0, bits = 0; const unsigned char *pcm = NULL; unsigned long pcm_len = 0; for (long pos = 12; pos + 8 <= size;) { unsigned len = le32(wav + pos + 4); if (!memcmp(wav + pos, "fmt ", 4)) { if (le16(wav + pos + 8) != 1) { fprintf(stderr, "so PCM\n"); return 1; } channels = le16(wav + pos + 10); rate = le32(wav + pos + 12); bits = le16(wav + pos + 22); } else if (!memcmp(wav + pos, "data", 4)) { pcm = wav + pos + 8; pcm_len = len; } pos += 8 + len + (len & 1); } if (!pcm || channels != 2 || bits != 16) { fprintf(stderr, "precisa de PCM 16 bits estereo\n"); return 1; } vorbis_info vi; vorbis_info_init(&vi); if (vorbis_encode_init_vbr(&vi, 2, rate, (float)atof(argv[3]))) { fprintf(stderr, "qualidade invalida\n"); return 1; } vorbis_comment vc; vorbis_comment_init(&vc); vorbis_dsp_state vd; vorbis_analysis_init(&vd, &vi); vorbis_block vb; vorbis_block_init(&vd, &vb); ogg_stream_state os; ogg_stream_init(&os, 0x43464d55); /* "CFMU", fixo */ FILE *out = fopen(argv[2], "wb"); if (!out) { perror(argv[2]); return 1; } ogg_packet header, comm, code; vorbis_analysis_headerout(&vd, &vc, &header, &comm, &code); ogg_stream_packetin(&os, &header); ogg_stream_packetin(&os, &comm); ogg_stream_packetin(&os, &code); ogg_page og; while (ogg_stream_flush(&os, &og)) write_page(out, &og); unsigned long frames = pcm_len / 4, done = 0; int eos = 0; while (!eos) { unsigned long n = frames - done < 1024 ? frames - done : 1024; if (n == 0) { vorbis_analysis_wrote(&vd, 0); } else { float **buf = vorbis_analysis_buffer(&vd, (int)n); const unsigned char *p = pcm + done * 4; for (unsigned long i = 0; i < n; i++) { buf[0][i] = (short)le16(p + i * 4) / 32768.f; buf[1][i] = (short)le16(p + i * 4 + 2) / 32768.f; } vorbis_analysis_wrote(&vd, (int)n); done += n; } while (vorbis_analysis_blockout(&vd, &vb) == 1) { vorbis_analysis(&vb, NULL); vorbis_bitrate_addblock(&vb); ogg_packet op; while (vorbis_bitrate_flushpacket(&vd, &op)) { ogg_stream_packetin(&os, &op); while (!eos && ogg_stream_pageout(&os, &og)) { write_page(out, &og); if (ogg_page_eos(&og)) eos = 1; } } } } fclose(out); ogg_stream_clear(&os); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_comment_clear(&vc); vorbis_info_clear(&vi); free(wav); return 0; }