ogg stream
parent
c13c69b558
commit
3a8daa69a5
|
@ -6,6 +6,7 @@ include $(addprefix $(LOCAL_PATH)/, $(addsuffix /Android.mk, \
|
|||
libogg \
|
||||
libvorbis \
|
||||
libvorbis-jni \
|
||||
libvorbis-stream \
|
||||
))
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := vorbis-stream
|
||||
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
|
||||
ifeq ($(TARGET_ARCH),arm)
|
||||
LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
|
||||
endif
|
||||
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := libogg libvorbis
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
vorbis-fileoutputstream.c \
|
||||
vorbis-fileinputstream.c \
|
||||
jni-util.c
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
|
@ -0,0 +1,28 @@
|
|||
/* Programmer: Nicholas Wertzberger
|
||||
* Email: wertnick@gmail.com
|
||||
*
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stream/util.h>
|
||||
|
||||
/*
|
||||
* http://java.sun.com/docs/books/jni/html/exceptions.html
|
||||
*/
|
||||
void
|
||||
JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg, const int code)
|
||||
{
|
||||
char buf [45];
|
||||
|
||||
sprintf(buf, "%35s: %d", msg, code);
|
||||
|
||||
jclass cls = (*env)->FindClass(env, name);
|
||||
/* if cls is NULL, an exception has already been thrown */
|
||||
if (cls != NULL) {
|
||||
(*env)->ThrowNew(env, cls, buf);
|
||||
}
|
||||
/* free the local ref */
|
||||
(*env)->DeleteLocalRef(env, cls);
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* A java interface (a la inputstream) to read ogg vorbis files.
|
||||
* http://svn.xiph.org/trunk/vorbis/examples/vorbisfile_example.c
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#include <stream/util.h>
|
||||
|
||||
/* This is arbitrary, If you don't like it, change it */
|
||||
#define MAX_INPUTSTREAMS 8
|
||||
|
||||
struct input_stream {
|
||||
FILE * fh;
|
||||
OggVorbis_File vf;
|
||||
int section;
|
||||
int length;
|
||||
};
|
||||
static struct input_stream input_streams[MAX_INPUTSTREAMS];
|
||||
|
||||
jint Java_org_xiph_vorbis_stream_VorbisFileInputStream_create(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jstring path,
|
||||
jobject info
|
||||
)
|
||||
{
|
||||
int ret; /* Debugging variable */
|
||||
jfieldID channels_field, sample_rate_field, length_field; /* JNI field ID */
|
||||
jclass cls = (*env)->GetObjectClass(env, info);
|
||||
int stream_idx;
|
||||
struct input_stream * iptr;
|
||||
vorbis_info * vi;
|
||||
|
||||
/* Find an unused input_stream */
|
||||
for (stream_idx = 0; stream_idx < MAX_INPUTSTREAMS; stream_idx++) {
|
||||
if (input_streams[stream_idx].fh == NULL) {
|
||||
const jbyte * pchars = (*env)->GetStringUTFChars(env, path, NULL);
|
||||
if (pchars == NULL) {
|
||||
/* Exception Already thrown */
|
||||
return;
|
||||
}
|
||||
/* We found one! */
|
||||
iptr = &input_streams[stream_idx];
|
||||
iptr->fh = fopen(pchars, "r");
|
||||
if (iptr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Error Creating File Handle", 0);
|
||||
return;
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, path, pchars);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_idx == MAX_INPUTSTREAMS) {
|
||||
JNU_ThrowByName(env, "java/io/IOException",
|
||||
"Too Many Vorbis InputStreams", stream_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Open the stream */
|
||||
ret = ov_open(iptr->fh, &iptr->vf, NULL, 0);
|
||||
if (ret < 0) {
|
||||
JNU_ThrowByName(env, "java/io/IOException",
|
||||
"Vorbis File Corrupt", ret);
|
||||
fclose(iptr->fh);
|
||||
iptr->fh = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
channels_field = (*env)->GetFieldID(env, cls, "channels", "I");
|
||||
sample_rate_field = (*env)->GetFieldID(env, cls, "sampleRate", "I");
|
||||
length_field = (*env)->GetFieldID(env, cls, "length", "J");
|
||||
if (channels_field == NULL || sample_rate_field == NULL) {
|
||||
JNU_ThrowByName(env, "java/lang/Exception",
|
||||
"Native Field Misnamed", 0);
|
||||
ov_clear(&iptr->vf);
|
||||
fclose(iptr->fh);
|
||||
iptr->fh = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
vi = ov_info(&iptr->vf, -1);
|
||||
|
||||
iptr->section = 0;
|
||||
iptr->length = ov_pcm_total(&iptr->vf, -1);
|
||||
|
||||
/* Populate basic stream info into the VorbisInfo object. */
|
||||
(*env)->SetIntField(env, info, channels_field, vi->channels);
|
||||
(*env)->SetIntField(env, info, sample_rate_field, vi->rate);
|
||||
(*env)->SetLongField(env, info, length_field, iptr->length);
|
||||
|
||||
|
||||
return stream_idx;
|
||||
}
|
||||
|
||||
jint Java_org_xiph_vorbis_stream_VorbisFileInputStream_readStreamIdx(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jint sidx,
|
||||
jshortArray pcm,
|
||||
jint offset,
|
||||
jint length
|
||||
)
|
||||
{
|
||||
long ret;
|
||||
struct input_stream * iptr = &input_streams[sidx];
|
||||
|
||||
jshort * pcmShorts = (*env)->GetShortArrayElements(env, pcm, NULL);
|
||||
int maxLength = (*env)->GetArrayLength(env,pcm);
|
||||
|
||||
/* Do the battery of validation checks. */
|
||||
if (offset + length > maxLength) {
|
||||
JNU_ThrowByName(env, "java/lang/ArrayIndexOutOfBoundsException",
|
||||
"No data was written to the buffer",
|
||||
offset + length - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sidx >= MAX_INPUTSTREAMS || sidx < 0 || iptr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Invalid Stream Index", sidx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
ret = ov_read(&iptr->vf, (char *)(pcmShorts + offset), length, 0, 2, 1, &iptr->section);
|
||||
/* -1 is EOF */
|
||||
if (ret == 0) {
|
||||
ret = -1;
|
||||
}
|
||||
else if (ret < 0) {
|
||||
if (ret == OV_EBADLINK) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Corrupt bitstream section!", iptr->section);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = 0;
|
||||
}
|
||||
/* Apparently sample rates can change inside the stream... We may need to account for that. */
|
||||
|
||||
(*env)->ReleaseShortArrayElements(env, pcm, pcmShorts, 0);
|
||||
return ret >> 1;
|
||||
}
|
||||
|
||||
jlong Java_org_xiph_vorbis_stream_VorbisFileInputStream_skipStreamIdx(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jint sidx,
|
||||
jlong offset
|
||||
)
|
||||
{
|
||||
struct input_stream * iptr = &input_streams[sidx];
|
||||
long ret;
|
||||
if (sidx >= MAX_INPUTSTREAMS || sidx < 0 || iptr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Invalid Stream Index", sidx);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ov_pcm_seek_lap(&iptr->vf, offset);
|
||||
|
||||
if (ret == OV_EREAD) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Read ERROR", ret);
|
||||
return;
|
||||
}
|
||||
else if (ret != 0){
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Vorbis Seek Error code: ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void Java_org_xiph_vorbis_stream_VorbisFileInputStream_closeStreamIdx(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jint sidx
|
||||
)
|
||||
{
|
||||
struct input_stream * iptr = &input_streams[sidx];
|
||||
if (sidx >= MAX_INPUTSTREAMS || sidx < 0 || iptr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Invalid Stream Index", sidx);
|
||||
return;
|
||||
}
|
||||
ov_clear(&iptr->vf);
|
||||
fclose(iptr->fh);
|
||||
iptr->fh = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
/* Programmer: Nicholas Wertzberger
|
||||
*
|
||||
* The Java interface (a la outputstream) for vorbis encoding. This acts
|
||||
* roughly the way I would expect a Java OutputStream to act. I didn't bother
|
||||
* trying to SWIG anythign around between the native world and java. In fact,
|
||||
* I jsut have a statically allocate array to store data in here.
|
||||
*
|
||||
* http://svn.xiph.org/trunk/vorbis/examples/encoder_example.c
|
||||
*/
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <vorbis/vorbisenc.h>
|
||||
#include <stream/util.h>
|
||||
|
||||
/* I really don't want to figure out what vorbis is storing in their structs.
|
||||
* Let's just store it all in this here array and call it good.
|
||||
*/
|
||||
|
||||
/* This is arbitrary, If you don't like it, change it */
|
||||
#define MAX_OUTPUTSTREAMS 4
|
||||
#define MAX_VORBIS_CHUNKSIZE 1024
|
||||
|
||||
struct output_stream {
|
||||
FILE * fh;
|
||||
vorbis_info vi;
|
||||
vorbis_comment vc;
|
||||
vorbis_dsp_state vd;
|
||||
vorbis_block vb;
|
||||
ogg_stream_state os;
|
||||
ogg_page og;
|
||||
ogg_packet op;
|
||||
int channels;
|
||||
};
|
||||
static struct output_stream output_streams[MAX_OUTPUTSTREAMS];
|
||||
|
||||
/* Based on code from:
|
||||
* http://svn.xiph.org/trunk/vorbis/examples/encoder_example.c
|
||||
* Returns a pointer to the stream struct related to that current vorbis file.
|
||||
*/
|
||||
jint Java_org_xiph_vorbis_stream_VorbisFileOutputStream_create(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jstring path,
|
||||
jobject info
|
||||
)
|
||||
{
|
||||
/* Configuration structs */
|
||||
struct output_stream * optr = NULL;
|
||||
|
||||
/* JNI field ID's */
|
||||
jfieldID channels_field, sample_rate_field, quality_field;
|
||||
jclass cls = (*env)->GetObjectClass(env, info);
|
||||
|
||||
/* packet stream structs */
|
||||
ogg_packet header;
|
||||
ogg_packet header_comm;
|
||||
ogg_packet header_code;
|
||||
|
||||
int ret; /* Return code storage for function calls */
|
||||
int eos = 0; /* End of Stream */
|
||||
int stream_idx;
|
||||
int sample_rate;
|
||||
float quality;
|
||||
|
||||
/* Find an unused output_stream */
|
||||
for (stream_idx = 0; stream_idx < MAX_OUTPUTSTREAMS; stream_idx++) {
|
||||
if (output_streams[stream_idx].fh == NULL) {
|
||||
const jbyte * pchars = (*env)->GetStringUTFChars(env, path, NULL);
|
||||
if (pchars == NULL) {
|
||||
/* Exception Already thrown */
|
||||
return;
|
||||
}
|
||||
/* We found one! */
|
||||
optr = &output_streams[stream_idx];
|
||||
optr->fh = fopen(pchars, "w");
|
||||
if (optr->fh == NULL) {
|
||||
char * message = "Error Creating File Handle. ";
|
||||
JNU_ThrowByName(env, "java/io/IOException", message, errno);
|
||||
return;
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, path, pchars);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stream_idx == MAX_OUTPUTSTREAMS) {
|
||||
JNU_ThrowByName(env, "java/io/IOException",
|
||||
"Too Many Vorbis OutputStreams", stream_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Step 1. According to documented workflow.
|
||||
* http://xiph.org/vorbis/doc/libvorbis/overview.html
|
||||
*/
|
||||
vorbis_info_init(&optr->vi);
|
||||
|
||||
/* TODO: make these options passed in. We definitely don't need stereo
|
||||
* most of the time.
|
||||
*/
|
||||
channels_field = (*env)->GetFieldID(env, cls, "channels", "I");
|
||||
sample_rate_field = (*env)->GetFieldID(env, cls, "sampleRate", "I");
|
||||
quality_field = (*env)->GetFieldID(env, cls, "quality", "F");
|
||||
|
||||
optr->channels = (*env)->GetIntField(env, info, channels_field);
|
||||
sample_rate = (*env)->GetIntField(env, info, sample_rate_field);
|
||||
quality = (*env)->GetFloatField(env, info, quality_field);
|
||||
|
||||
/* TODO: Optimize this for speed more? */
|
||||
ret = vorbis_encode_init_vbr(&optr->vi,optr->channels,sample_rate,quality);
|
||||
|
||||
if (ret) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Bad Encoding options", ret);
|
||||
fclose(optr->fh);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Step 2. */
|
||||
vorbis_analysis_init(&optr->vd, &optr->vi);
|
||||
vorbis_block_init(&optr->vd, &optr->vb);
|
||||
|
||||
/* Step 3. */
|
||||
vorbis_comment_init(&optr->vc);
|
||||
|
||||
/* A 0 means all is well. */
|
||||
srand(time(NULL));
|
||||
ogg_stream_init(&optr->os, rand());
|
||||
|
||||
ret = vorbis_analysis_headerout(&optr->vd, &optr->vc, &header, &header_comm,
|
||||
&header_code);
|
||||
|
||||
if (ret) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "header init error", ret);
|
||||
ogg_stream_clear(&optr->os);
|
||||
vorbis_block_clear(&optr->vb);
|
||||
vorbis_dsp_clear(&optr->vd);
|
||||
vorbis_comment_clear(&optr->vc);
|
||||
vorbis_info_clear(&optr->vi);
|
||||
fclose(optr->fh);
|
||||
optr->fh = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
ogg_stream_packetin(&optr->os, &header); /* placed in its own page */
|
||||
ogg_stream_packetin(&optr->os, &header_comm);
|
||||
ogg_stream_packetin(&optr->os, &header_code);
|
||||
|
||||
/* This ensures the actual
|
||||
* audio data will start on a new page, as per spec
|
||||
*/
|
||||
while (1) {
|
||||
int result = ogg_stream_flush(&optr->os, &optr->og);
|
||||
if (result == 0)
|
||||
break;
|
||||
/* TODO: Tie this into the file handle passed in... Or whatever */
|
||||
fwrite(optr->og.header, 1, optr->og.header_len, optr->fh);
|
||||
fwrite(optr->og.body, 1, optr->og.body_len, optr->fh);
|
||||
}
|
||||
return stream_idx;
|
||||
}
|
||||
|
||||
/* Write out to the file handle
|
||||
*
|
||||
*/
|
||||
jint Java_org_xiph_vorbis_stream_VorbisFileOutputStream_writeStreamIdx(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jint sidx,
|
||||
jshortArray pcm,
|
||||
jint offset,
|
||||
jint length
|
||||
)
|
||||
{
|
||||
|
||||
jshort * pcmShorts = (*env)->GetShortArrayElements(env, pcm, NULL);
|
||||
int maxLength = (*env)->GetArrayLength(env,pcm);
|
||||
struct output_stream * optr = &output_streams[sidx];
|
||||
int channels;
|
||||
int i,j;
|
||||
int eos = 0;
|
||||
|
||||
if (offset + length > maxLength) {
|
||||
JNU_ThrowByName(env, "java/lang/ArrayIndexOutOfBoundsException",
|
||||
"No data was read from the buffer",
|
||||
offset + length - 1);
|
||||
return;
|
||||
}
|
||||
if (sidx >= MAX_OUTPUTSTREAMS || sidx < 0 || optr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Invalid Stream Index",
|
||||
sidx);
|
||||
return;
|
||||
}
|
||||
|
||||
channels = optr->channels;
|
||||
|
||||
while (length > 0) {
|
||||
/* Data to encode:
|
||||
* According to this: http://xiph.org/vorbis/doc/libvorbis/vorbis_analysis_buffer.html
|
||||
*
|
||||
* A "reasonable" chunk size is 1024. Due to some sampling issues, we
|
||||
* are going to force this to be the max size.
|
||||
*/
|
||||
int chunksize = length;
|
||||
if (chunksize > MAX_VORBIS_CHUNKSIZE) chunksize = MAX_VORBIS_CHUNKSIZE;
|
||||
|
||||
/* expose the buffer to submit data */
|
||||
float ** buffer = vorbis_analysis_buffer(&optr->vd, chunksize);
|
||||
|
||||
/* uninterleave samples */
|
||||
for (i = 0; i < chunksize / channels; i++) {
|
||||
for (j = 0; j < channels; j++) {
|
||||
buffer[j][i] = pcmShorts[i*channels + j + offset] / 32768.f;
|
||||
}
|
||||
}
|
||||
|
||||
/* tell the library how much we actually submitted */
|
||||
vorbis_analysis_wrote(&optr->vd, i);
|
||||
|
||||
length -= i*channels;
|
||||
offset += i*channels;
|
||||
|
||||
/* vorbis does some data preanalysis, then divvies up blocks for
|
||||
more involved (potentially parallel) processing. Get a single
|
||||
block for encoding now */
|
||||
while (vorbis_analysis_blockout(&optr->vd, &optr->vb) == 1) {
|
||||
|
||||
/* analysis, assume we want to use bitrate management */
|
||||
vorbis_analysis(&optr->vb, NULL);
|
||||
vorbis_bitrate_addblock(&optr->vb);
|
||||
|
||||
while (vorbis_bitrate_flushpacket(&optr->vd, &optr->op)) {
|
||||
|
||||
/* weld the packet into the bitstream */
|
||||
ogg_stream_packetin(&optr->os, &optr->op);
|
||||
|
||||
/* write out pages (if any) */
|
||||
while (!eos) {
|
||||
int result = ogg_stream_pageout(&optr->os, &optr->og);
|
||||
if (result == 0)
|
||||
break;
|
||||
fwrite(optr->og.header, 1, optr->og.header_len, optr->fh);
|
||||
fwrite(optr->og.body, 1, optr->og.body_len, optr->fh);
|
||||
|
||||
/* this could be set above, but for illustrative purposes, I do
|
||||
it here (to show that vorbis does know where the stream ends) */
|
||||
|
||||
if (ogg_page_eos(&optr->og))
|
||||
eos = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(*env)->ReleaseShortArrayElements(env, pcm, pcmShorts, JNI_ABORT);
|
||||
}
|
||||
/*
|
||||
* Clean up stream info.
|
||||
*/
|
||||
void Java_org_xiph_vorbis_stream_VorbisFileOutputStream_closeStreamIdx(
|
||||
JNIEnv* env,
|
||||
jobject this,
|
||||
jint sidx
|
||||
)
|
||||
{
|
||||
struct output_stream * optr = &output_streams[sidx];
|
||||
if (sidx >= MAX_OUTPUTSTREAMS || sidx < 0 || optr->fh == NULL) {
|
||||
JNU_ThrowByName(env, "java/io/IOException", "Invalid Stream Index", sidx);
|
||||
return;
|
||||
}
|
||||
vorbis_analysis_wrote(&optr->vd, 0);
|
||||
|
||||
while (vorbis_analysis_blockout(&optr->vd, &optr->vb) == 1) {
|
||||
vorbis_analysis(&optr->vb, NULL);
|
||||
vorbis_bitrate_addblock(&optr->vb);
|
||||
while (vorbis_bitrate_flushpacket(&optr->vd, &optr->op)) {
|
||||
ogg_stream_packetin(&optr->os, &optr->op);
|
||||
}
|
||||
}
|
||||
|
||||
while (ogg_stream_pageout(&optr->os, &optr->og) > 0) {
|
||||
fwrite(optr->og.header, 1, optr->og.header_len, optr->fh);
|
||||
fwrite(optr->og.body, 1, optr->og.body_len, optr->fh);
|
||||
}
|
||||
|
||||
ogg_stream_clear(&optr->os);
|
||||
vorbis_block_clear(&optr->vb);
|
||||
vorbis_dsp_clear(&optr->vd);
|
||||
vorbis_comment_clear(&optr->vc);
|
||||
vorbis_info_clear(&optr->vi);
|
||||
fclose(optr->fh);
|
||||
optr->fh = NULL;
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libvorbis
|
||||
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
|
||||
ifeq ($(TARGET_ARCH),arm)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,23 @@
|
|||
package org.xiph.vorbis.stream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class AudioInputStream extends InputStream implements Closeable {
|
||||
public abstract void close() throws IOException;
|
||||
|
||||
public int read(short[] pcmBuffer) throws IOException {
|
||||
return this.read(pcmBuffer, 0, pcmBuffer.length);
|
||||
}
|
||||
|
||||
public abstract int read(short[] pcmBuffer, int offset, int length) throws IOException;
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
short buf[] = new short[1];
|
||||
if ( this.read(buf, 0, 1) == -1) return -1;
|
||||
else return buf[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.xiph.vorbis.stream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class AudioOutputStream extends OutputStream implements Closeable {
|
||||
|
||||
@Override
|
||||
public void write(final int buf) throws IOException {
|
||||
short[] buffer = new short[1];
|
||||
buffer[0] = (short) buf;
|
||||
this.write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public void write(final short[] buffer) throws IOException {
|
||||
this.write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public abstract void write(final short[] buffer, int offset, int length)
|
||||
throws IOException;
|
||||
|
||||
public abstract int getSampleRate();
|
||||
|
||||
@Override
|
||||
public abstract void close() throws IOException;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.xiph.vorbis.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class VorbisFileInputStream extends AudioInputStream {
|
||||
|
||||
private final VorbisInfo info;
|
||||
|
||||
public VorbisInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
private final int oggStreamIdx;
|
||||
|
||||
static {
|
||||
System.loadLibrary("ogg");
|
||||
System.loadLibrary("vorbis");
|
||||
System.loadLibrary("vorbis-stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file for reading and parses any comments out of the file header.
|
||||
*/
|
||||
public VorbisFileInputStream(String fname) throws IOException {
|
||||
info = new VorbisInfo();
|
||||
oggStreamIdx = this.create(fname, info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.closeStreamIdx(oggStreamIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns interleaved PCM data from the vorbis stream.
|
||||
*
|
||||
* @param pcmBuffer
|
||||
* @param offset
|
||||
* @param length
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int read(short[] pcmBuffer, int offset, int length) throws IOException {
|
||||
return this.readStreamIdx(oggStreamIdx, pcmBuffer, offset, length);
|
||||
}
|
||||
|
||||
private native int create(String fname, VorbisInfo info) throws IOException;
|
||||
|
||||
private native void closeStreamIdx(int sidx) throws IOException;
|
||||
|
||||
/**
|
||||
* This just returns all the channels interleaved together. I assume this is how android wants it.
|
||||
*
|
||||
* @param pcm
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private native int readStreamIdx(int sidx, short[] pcm, int offset, int size) throws IOException;
|
||||
|
||||
/**
|
||||
* Skips over the number of samples specified. This skip doesn't account for channels.
|
||||
*
|
||||
* @param samples
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private native long skipStreamIdx(int sidx, long samples) throws IOException;
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.xiph.vorbis.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Converts incoming PCM Audio Data into OGG data into a file. This will be implemented using the open source BSD-licensed stuff from Xiph.org.
|
||||
*
|
||||
* NOTE: This implementation has a limitation of MAX_STREAMS concurrent output streams. When i wrote this, it was set to 8. Check in
|
||||
* vorbis-fileoutputstream.c to see what it is set to.
|
||||
*
|
||||
*/
|
||||
public class VorbisFileOutputStream extends AudioOutputStream {
|
||||
// The index into native memory where the ogg stream info is stored.
|
||||
private final int oggStreamIdx;
|
||||
private VorbisInfo info;
|
||||
private static final int VORBIS_BLOCK_SIZE = 1024;
|
||||
|
||||
static {
|
||||
System.loadLibrary("ogg");
|
||||
System.loadLibrary("vorbis");
|
||||
System.loadLibrary("vorbis-stream");
|
||||
}
|
||||
|
||||
public VorbisFileOutputStream(String fname, VorbisInfo s) throws IOException {
|
||||
info = s;
|
||||
oggStreamIdx = this.create(fname, s);
|
||||
}
|
||||
|
||||
public VorbisFileOutputStream(String fname) throws IOException {
|
||||
oggStreamIdx = this.create(fname, new VorbisInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.closeStreamIdx(this.oggStreamIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write PCM data to ogg. This assumes that you pass your streams in interleaved.
|
||||
*
|
||||
* @param buffer
|
||||
* @param offset
|
||||
* @param length
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void write(final short[] buffer, int offset, int length) throws IOException {
|
||||
this.writeStreamIdx(this.oggStreamIdx, buffer, offset, length);
|
||||
}
|
||||
|
||||
private native int writeStreamIdx(int idx, short[] pcmdata, int offset, int size) throws IOException;
|
||||
|
||||
private native void closeStreamIdx(int idx) throws IOException;
|
||||
|
||||
private native int create(String path, VorbisInfo s) throws IOException;
|
||||
|
||||
@Override
|
||||
public int getSampleRate() {
|
||||
return info.sampleRate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.xiph.vorbis.stream;
|
||||
|
||||
/**
|
||||
* A class used to pass vorbis file info to the encoder and find out what kind of file was received by the decoder.
|
||||
*
|
||||
* Currently, the vorbis file encoder/decoder only supports 16-bit samples. There are no plans by me to ever add 8-bit. Defaults are set to whatever I
|
||||
* wanted to use for the project i wrote this for.
|
||||
*
|
||||
*/
|
||||
public class VorbisInfo {
|
||||
/**
|
||||
* The number of channels to be encoded. For your sake, here are the official channel positions for the first five according to Xiph.org. one
|
||||
* channel - the stream is monophonic two channels - the stream is stereo. channel order: left, right three channels - the stream is a 1d-surround
|
||||
* encoding. channel order: left, center, right four channels - the stream is quadraphonic surround. channel order: front left, front right, rear
|
||||
* left, rear right five channels - the stream is five-channel surround. channel order: front left, center, front right, rear left, rear right
|
||||
*/
|
||||
public int channels = 1;
|
||||
|
||||
/**
|
||||
* The number of samples per second of pcm data.
|
||||
*/
|
||||
public int sampleRate = 44100;
|
||||
|
||||
/**
|
||||
* The recording quality of the encoding. This is not currently set for the decoder. The range goes from -.1 (worst) to 1 (best)
|
||||
*/
|
||||
public float quality = 0.4f;
|
||||
|
||||
/**
|
||||
* the total number of samples from the recording. This field means nothing to the encoder.
|
||||
*/
|
||||
public long length;
|
||||
|
||||
}
|
Loading…
Reference in New Issue