Last active
August 29, 2015 14:00
-
-
Save roxlu/af1ffc1290b9dc37b5b9 to your computer and use it in GitHub Desktop.
Decoding vorbis flow (w/o cleanup yet).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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