Skip to content

Instantly share code, notes, and snippets.

@roxlu
Last active August 29, 2015 14:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roxlu/af1ffc1290b9dc37b5b9 to your computer and use it in GitHub Desktop.
Save roxlu/af1ffc1290b9dc37b5b9 to your computer and use it in GitHub Desktop.
Decoding vorbis flow (w/o cleanup yet).
/*
test_vorbis
------------
See online version: https://gist.github.com/roxlu/af1ffc1290b9dc37b5b9
Example code which decodes a .ogg file that contains vorbis and/or
some other stream, e.g. theora. We ignore all non-vorbis streams and
simply flush the packets.
The decoding flow is explained in detail here: https://xiph.org/vorbis/doc/libvorbis/overview.html
In short the whole ogg/theora/vorbis flow is simple and very
structured. First you read some data from a stream (e.g. file),
then ask ogg to extract a page. When you have a 'page' you feed
it into a stream before trying to retrieve a packet.
filling the buffer:
ogg_sync_buffer()
fread()
ogg_sync_wrote()
retrieving a page:
ogg_sync_pageout()
ogg_sync_serialno() - retrieve serial number of stream
find and/or initialize stream for serial
ogg_stream_init()
ogg_stream_pagein() - feed the page
ogg_stream_packetout() - read packet from stream
vorbis:
- feed the first 3 packets to vorbis_synthesis_headerin()
then, when 3 packets are fed, init using `vorbis_syntesis_init()`
and `vorbis_block_init()`
- then decoding works like:
- vorbis_synthesis()
- vorbis_syntheis_blockin()
- vorbis_synthesis_pcmout()
- vorbis_synthesis_read()
The decoded data (see pcm below) will hold the left channel
in pcm[0] and the right in pcm[1] for multi channel audio.
pmc[0][0] holds the first audio value for the left channel,
pcm[1][0] for the rigt channel. We write the audio into a pcm
file where we interleave the left and right channels for multi
channel audio.
You can convert the pcm file to wav, when you have 2 channels using:
./avconv -f f32le -ac 2 -ar 48000 -i out.pcm out.wav
Of course check the rate (-ar) and number of channels (-ac)
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vorbis/codec.h>
#define STREAM_TYPE_NONE 0
#define STREAM_TYPE_UNKNOWN 1
#define STREAM_TYPE_VORBIS 2
typedef struct app app;
typedef struct app_stream app_stream;
struct app_stream {
int serial;
int type;
ogg_stream_state state;
app_stream* next;
};
struct app {
/* ogg related */
ogg_sync_state sync_state;
ogg_stream_state stream_state;
ogg_page page;
ogg_packet packet;
int serial;
app_stream* streams;
/* vorbis related */
int vorbis_initialized;
vorbis_info info;
vorbis_comment comment;
vorbis_dsp_state dsp_state;
vorbis_block block;
int header_packets; /* how many packets passed into vorbis_synthesis_headerin(), we need 3 */
FILE* fp;
int size; /* file size */
char* buffer; /* points to sync state buffer */
int bytes; /* bytes read into the buffer */
/* output */
FILE* out_fp;
};
app tap;
static int app_init(app* a);
static int app_shutdown(app* a);
static app_stream* app_stream_alloc();
static app_stream* app_find_stream(app* a, int serial);
static app_stream* app_create_stream(app* a, int serial);
static int app_detect_stream_type(app* a, app_stream* s, ogg_page* page, ogg_packet* packet);
static int app_read_buffer(app* a);
static int app_read_page(app* a, ogg_page* page);
static int app_process_vorbis(app* a, app_stream* s, ogg_page* page, ogg_packet* packet);
int main() {
int r = 0;
int serial = 0;
printf("\n\ntest_vorbis\n\n");
if (app_init(&tap) < 0) {
printf("Error: cannot init app.\n");
exit(1);
}
while(1) {
ogg_page page;
ogg_packet packet;
/* I/O */
/* ------------------------------------------------ */
/* reads from the buffer until it can return an page */
if (app_read_page(&tap, &page) < 0) {
printf("Error: cannot read a page.\n");
break;
}
/* STREAM INIT + DETECTION */
/* ------------------------------------------------ */
/* find or create the stream */
serial = ogg_page_serialno(&page);
app_stream* stream = app_find_stream(&tap, serial);
if (!stream) {
stream = app_create_stream(&tap, serial);
if (!stream) {
printf("Error: cannot create stream.\n");
exit(1);
}
printf("- created stream: %d\n", stream->serial);
}
if (!stream) {
printf("Error: cannot find or create the stream.\n");
exit(1);
}
/* STREAM PROCESSING */
/* ------------------------------------------------ */
/* handle the stream with vorbis only and ignore others*/
if (stream->type == STREAM_TYPE_NONE) {
app_detect_stream_type(&tap, stream, &page, &packet);
}
else if (stream->type == STREAM_TYPE_UNKNOWN) {
/* we just read the packet, but don't do anything with it */
ogg_stream_packetout(&stream->state, &packet);
printf("- ignore stream: %d.\n", serial);
continue;
}
else if(stream->type == STREAM_TYPE_VORBIS) {
app_process_vorbis(&tap, stream, &page, &packet);
}
}
if (app_shutdown(&tap) < 0) {
exit(2);
}
}
static int app_process_vorbis(app* a, app_stream* s, ogg_page* page, ogg_packet* packet) {
int r = 0;
int samples = 0;
float** pcm;
if (!a) { return -1; }
if (!s) { return -2; }
if (!page) { return -3; }
if (!packet) { return -4; }
printf("-- vorbis: process %d\n", s->serial);
r = ogg_stream_pagein(&s->state, page);
if (r != 0) {
/* need more data */
printf("-- vorbis: feeding page failed: %d", r);
return 0;
}
r = ogg_stream_packetout(&s->state, packet);
if (r == 0) {
/* need more data */
printf("-- vorbis: packet needs more data: %d\n", r);
return 0;
}
else if(r < 0) {
printf("-- vorbis error: getting a packet: %d\n", r);
return r;
}
/* when we did not yet read enough header packets, needs to be 3, we feed
the current packet to vorbis_synthesis_headerin()
*/
if (a->header_packets < 3) {
r = vorbis_synthesis_headerin(&a->info, &a->comment, packet);
if (r != 0) {
printf("-- vorbis: error, vorbis_synthesis_headerin() failed: %d\n", r);
}
a->header_packets++;
if (a->header_packets == 3) {
char** ptr = a->comment.user_comments;
while(*ptr) {
printf("-- vorbis error: %s\n", *ptr);
++ptr;
}
/* initialize the vorbis structs */
if (vorbis_synthesis_init(&a->dsp_state, &a->info) != 0) {
printf("-- vorbis error: cannot initialize vorbis.\n");
return -6;
}
if (vorbis_block_init(&a->dsp_state, &a->block) != 0) {
printf("-- vorbis error: cannot initialize the vorbis block.\n");
return -7;
}
}
return 0;
}
printf("-- vorbis: packet.bytes: %ld\n", packet->bytes);
printf("-- vorbis: packet.granulepos: %lld\n", packet->granulepos);
printf("-- vorbis: packet.packetno: %lld\n", packet->packetno);
printf("-- vorbis: header_packets: %d\n", a->header_packets);
printf("-- vorbis: rate: %ld\n", a->info.rate);
r = vorbis_synthesis(&a->block, packet);
if (r != 0) {
printf("- vorbis error: vorbis_synthesis failed: %d\n", r);
return -5;
}
r = vorbis_synthesis_blockin(&a->dsp_state, &a->block);
if (r != 0) {
printf("-- vorbis error: vorbis_synthesis_blockin failed: %d\n", r);
return -6;
}
samples = vorbis_synthesis_pcmout(&a->dsp_state, &pcm);
if (samples <= 0) {
printf("-- vorbis: no samples: %d\n", samples);
return -7;
}
r = vorbis_synthesis_read(&a->dsp_state, samples);
if (r != 0) {
printf("-- vorbis error: vorbis_synthesis_read() failed: %d\n", r);
return -8;
}
printf("-- vorbis: read samples: %d \n", samples);
/* write to output as interleaved pcm */
if (a->out_fp) {
for (int i = 0; i < samples; ++i) {
for (int c = 0; c < a->info.channels; ++c) {
fwrite(&pcm[c][i], sizeof(float), 1, a->out_fp);
}
}
}
return 0;
}
static int app_read_buffer(app* a) {
if (!a) { return -1; }
a->buffer = ogg_sync_buffer(&a->sync_state, 4096);
int nread = fread(a->buffer, 1, 4096, a->fp);
ogg_sync_wrote(&a->sync_state, nread);
if (!nread) {
return -1;
}
return nread;
}
static int app_read_page(app* a, ogg_page* page) {
if (!a) { return -1; }
int r = 0;
do {
r = ogg_sync_pageout(&a->sync_state, page);
/* more data needed */
if (r == 0) {
if (app_read_buffer(a) < 0) {
printf("@todo, we've read the complete file. or error occured.\n");
exit(1);
}
}
} while(r != 1);
return 0;
}
static int app_init(app* a) {
if (!a) { return -1; }
a->serial = 0;
a->streams = NULL;
a->header_packets = 0;
/* init ogg + vorbis */
ogg_sync_init(&a->sync_state);
vorbis_info_init(&a->info);
vorbis_comment_init(&a->comment);
/* open the ogg file. */
a->fp = fopen("bunny.ogg", "rb");
if (!a->fp) {
printf("Error: cannot open the ogg file.\n");
return -1;
}
fseek(a->fp, 0, SEEK_END);
a->size = ftell(a->fp);
fseek(a->fp, 0, SEEK_SET);
if(a->size <= 0) {
printf("Error: file size is empty.\n");
fclose(a->fp);
a->fp = NULL;
return -4;
}
/* open file for output */
a->out_fp = fopen("out.pcm", "wb");
if (!a->out_fp) {
printf("Error: cannot create output file.\n");
return -5;
}
return 0;
}
static int app_shutdown(app* a) {
if (!a) { return -1; }
ogg_sync_clear(&a->sync_state);
if (a->fp) {
fclose(a->fp);
a->fp = NULL;
}
if (a->out_fp) {
printf("- closing output file.\n");
fclose(a->out_fp);
a->out_fp = NULL;
}
/* @todo cleanup info/comment/streams */
return 0;
}
static app_stream* app_stream_alloc() {
app_stream* s = (app_stream*) malloc(sizeof(app_stream));
if (!s) {
printf("Error: cannot allocate an app stream.\n");
return NULL;
}
s->type = STREAM_TYPE_NONE;
s->next = NULL;
return s;
}
/* allocates a new stream and appends it to the list of streams. */
static app_stream* app_create_stream(app* a, int serial) {
int r = 0;
if (!a) { return NULL; }
if (!serial) { return NULL; }
app_stream* s = app_stream_alloc();
if (!s) {
return NULL;
}
s->serial = serial;
r = ogg_stream_init(&s->state, serial);
if (r != 0) {
printf("Error: cannot initialize stream: %d, r: %d\n", serial, r);
return NULL;
}
if (!a->streams) {
/* first item */
a->streams = s;
}
else {
/* append to tail */
app_stream* tail = a->streams;
while (tail) {
if (!tail->next) {
break;
}
tail = tail->next;
}
tail->next = s;
}
printf("- appended stream: %d\n", s->serial);
return s;
}
static int app_detect_stream_type(app* a, app_stream* s, ogg_page* page, ogg_packet* packet) {
int r = 0;
if (!a) { return -1; }
if (!s) { return -2; }
printf("- detect stream type: %d\n", s->serial);
/* pass the extract page into the stream to which it belongs */
if (ogg_stream_pagein(&s->state, page) != 0) {
printf("Error: ogg_stream_pagein(), failed.\n");
return -3;
}
r = ogg_stream_packetout(&s->state, packet);
if (r < 0) {
printf("Error: cannot read a new packet for the current stream: %d\n", s->serial);
}
else if(r == 0) {
return r;
}
printf("- ok got enough data for this stream: %d\n", s->serial);
/* detect if the stream is vorbis */
r = vorbis_synthesis_headerin(&a->info, &a->comment, packet);
if (r < 0) {
if (r == OV_ENOTVORBIS) {
printf("- read packet is not vorbis: %d\n", s->serial);
s->type = STREAM_TYPE_UNKNOWN;
return 0;
}
printf("- warning: Unhandled vorbis error: %d, serial: %d\n", r, s->serial);
return -5;
}
s->type = STREAM_TYPE_VORBIS;
a->header_packets = 1;
printf("- VORBIS:\n");
printf("-- num channels: %d\n", a->info.channels);
printf("-- rate: %ld\n", a->info.rate);
return 0;
}
static app_stream* app_find_stream(app* a, int serial) {
if (!a) { return NULL; }
app_stream* s = a->streams;
while (s) {
if (s->serial == serial) {
return s;
}
s = s->next;
}
return NULL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment