-Expanding demo application to allow quick customization, adding bitrate target option, and changing how exception handling works

master
Vincent Pizzo 2013-04-12 11:05:56 -05:00
parent 034b101c55
commit 5185b9500b
16 changed files with 803 additions and 409 deletions

View File

@ -22,25 +22,6 @@ void stopDecodeFeed(JNIEnv *env, jobject* vorbisDataFeed, jmethodID* stopMethodI
(*env)->CallVoidMethod(env, (*vorbisDataFeed), (*stopMethodId));
}
//Throws an exception to the java layer with th specified error code and stops the decode feed
void throwDecodeException(JNIEnv *env, const int code, jobject* vorbisDataFeed, jmethodID* stopMethodId) {
//Find the decode exception class and constructor
jclass decodeExceptionClass = (*env)->FindClass(env, "org/xiph/vorbis/decoder/DecodeException");
jmethodID constructor = (*env)->GetMethodID(env, decodeExceptionClass, "<init>", "(I)V");
//Create the decode exception object
jobject decodeException = (*env)->NewObject(env, vorbisDataFeed, constructor, code);
//Throw the exception
(*env)->Throw(env, decodeException);
//Free the exception object
(*env)->DeleteLocalRef(env, decodeException);
//Stop the decode feed
stopDecodeFeed(env, vorbisDataFeed, stopMethodId);
}
//Reads raw vorbis data from the jni callback
int readVorbisDataFromVorbisDataFeed(JNIEnv *env, jobject* vorbisDataFeed, jmethodID* readVorbisDataMethodId, char* buffer, jbyteArray* jByteArrayReadBuffer) {
//Call the read method
@ -174,7 +155,7 @@ JNIEXPORT int JNICALL Java_org_xiph_vorbis_decoder_VorbisDecoder_startDecoding
if(bytes<BUFFER_LENGTH)break;
/* error case. Must not be Vorbis data */
throwDecodeException(env, INVALID_OGG_BITSTREAM, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return INVALID_OGG_BITSTREAM;
}
@ -196,21 +177,21 @@ JNIEXPORT int JNICALL Java_org_xiph_vorbis_decoder_VorbisDecoder_startDecoding
vorbis_comment_init(&vc);
if(ogg_stream_pagein(&os,&og)<0){
/* error; stream version mismatch perhaps */
throwDecodeException(env, ERROR_READING_FIRST_PAGE, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return ERROR_READING_FIRST_PAGE;
}
if(ogg_stream_packetout(&os,&op)!=1){
/* no page? must not be vorbis */
throwDecodeException(env, ERROR_READING_INITIAL_HEADER_PACKET, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return ERROR_READING_INITIAL_HEADER_PACKET;
}
if(vorbis_synthesis_headerin(&vi,&vc,&op)<0){
/* error case; not a vorbis header */
throwDecodeException(env, NOT_VORBIS_HEADER, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return NOT_VORBIS_HEADER;
}
@ -242,12 +223,12 @@ JNIEXPORT int JNICALL Java_org_xiph_vorbis_decoder_VorbisDecoder_startDecoding
if(result<0){
/* Uh oh; data at some point was corrupted or missing!
We can't tolerate that in a header. Die. */
throwDecodeException(env, CORRUPT_SECONDARY_HEADER, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return CORRUPT_SECONDARY_HEADER;
}
result=vorbis_synthesis_headerin(&vi,&vc,&op);
if(result<0){
throwDecodeException(env, CORRUPT_SECONDARY_HEADER, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return CORRUPT_SECONDARY_HEADER;
}
i++;
@ -258,7 +239,7 @@ JNIEXPORT int JNICALL Java_org_xiph_vorbis_decoder_VorbisDecoder_startDecoding
buffer=ogg_sync_buffer(&oy,BUFFER_LENGTH);
bytes=readVorbisDataFromVorbisDataFeed(env, &vorbisDataFeed, &readVorbisDataMethodId, buffer, &jByteArrayReadBuffer);
if(bytes==0 && i<2){
throwDecodeException(env, PREMATURE_END_OF_FILE, &vorbisDataFeed, &stopMethodId);
stopDecodeFeed(env, &vorbisDataFeed, &stopMethodId);
return PREMATURE_END_OF_FILE;
}
ogg_sync_wrote(&oy,bytes);

View File

@ -19,9 +19,6 @@ JNIEXPORT int JNICALL Java_org_xiph_vorbis_decoder_VorbisDecoder_startDecoding
//Stops the vorbis data feed
void stopDecodeFeed(JNIEnv *env, jobject* vorbisDataFeed, jmethodID* stopMethodId);
//Throws an exception to the java layer with th specified error code and stops the decode feed
void throwDecodeException(JNIEnv *env, const int code, jobject* vorbisDataFeed, jmethodID* stopMethodId);
//Reads raw vorbis data from the jni callback
int readVorbisDataFromVorbisDataFeed(JNIEnv *env, jobject* vorbisDataFeed, jmethodID* readVorbisDataMethodId, char* buffer, jbyteArray* jByteArrayReadBuffer);

View File

@ -5,26 +5,13 @@
#define ERROR_INITIALIZING -44
#define SUCCESS 0
#define WITH_BITRATE 1
#define WITH_QUALITY 2
#define READ 1024
//Throws an exception to the java layer with th specified error code and stops the encode feed
void throwEncodeException(JNIEnv *env, const int code, jobject* vorbisDataFeed, jmethodID* stopMethodId) {
//Find the encode exception class and constructor
jclass encodeExceptionClass = (*env)->FindClass(env, "org/xiph/vorbis/encoder/EncodeException");
jmethodID constructor = (*env)->GetMethodID(env, encodeExceptionClass, "<init>", "(I)V");
//Create the encode exception object
jobject encodeException = (*env)->NewObject(env, vorbisDataFeed, constructor, code);
//Throw the exception
(*env)->Throw(env, encodeException);
//Free the exception object
(*env)->DeleteLocalRef(env, encodeException);
//Stop the encode feed
stopEncodeFeed(env, vorbisDataFeed, stopMethodId);
}
jfloat NO_QUALITY = -1;
jlong NO_BITRATE = -1;
//Starts the encode feed
void startEncodeFeed(JNIEnv *env, jobject *vorbisDataFeed, jmethodID* startMethodId) {
@ -78,213 +65,245 @@ int writeVorbisDataToEncoderDataFeed(JNIEnv *env, jobject* encoderDataFeed, jmet
return amountWritten;
}
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncoding
(JNIEnv *env, jclass cls, jlong sampleRate, jlong channels, jfloat quality, jobject encoderDataFeed) {
//Method to start encoding
int startEncoding(JNIEnv *env, jclass *cls_ptr, jlong *sampleRate_ptr, jlong *channels_ptr, jfloat *quality_ptr, jlong *bitrate_ptr, jobject *encoderDataFeed_ptr, int type) {
//Dereference our variables
jclass cls = (*cls_ptr);
jlong sampleRate = (*sampleRate_ptr);
jlong channels = (*channels_ptr);
jfloat quality = (*quality_ptr);
jlong bitrate = (*bitrate_ptr);
jobject encoderDataFeed = (*encoderDataFeed_ptr);
//Create our PCM data buffer
signed char readbuffer[READ*4+44];
//Create our PCM data buffer
signed char readbuffer[READ*4+44];
//Create a new java byte array to pass to the data feed method
jbyteArray jByteArrayBuffer = (*env)->NewByteArray(env, READ*4);
//Create a new java byte array to pass to the data feed method
jbyteArray jByteArrayBuffer = (*env)->NewByteArray(env, READ*4);
//Create a new java byte buffer to write to
jbyteArray jByteArrayWriteBuffer = (*env)->NewByteArray(env, READ*8);
//Create a new java byte buffer to write to
jbyteArray jByteArrayWriteBuffer = (*env)->NewByteArray(env, READ*8);
//Find our java classes we'll be calling
jclass encoderDataFeedClass = (*env)->FindClass(env, "org/xiph/vorbis/encoder/EncodeFeed");
//Find our java classes we'll be calling
jclass encoderDataFeedClass = (*env)->FindClass(env, "org/xiph/vorbis/encoder/EncodeFeed");
//Find our java method id's we'll be calling
jmethodID writeVorbisDataMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "writeVorbisData", "([BI)I");
jmethodID readPCMDataMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "readPCMData", "([BI)J");
jmethodID startMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "start", "()V");
jmethodID stopMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "stop", "()V");
//Find our java method id's we'll be calling
jmethodID writeVorbisDataMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "writeVorbisData", "([BI)I");
jmethodID readPCMDataMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "readPCMData", "([BI)J");
jmethodID startMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "start", "()V");
jmethodID stopMethodId = (*env)->GetMethodID(env, encoderDataFeedClass, "stop", "()V");
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
ogg_packet op; /* one raw packet of data for decode */
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
ogg_packet op; /* one raw packet of data for decode */
vorbis_info vi; /* struct that stores all the static vorbis bitstream
settings */
vorbis_comment vc; /* struct that stores all the user comments */
vorbis_info vi; /* struct that stores all the static vorbis bitstream
settings */
vorbis_comment vc; /* struct that stores all the user comments */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
int eos=0,ret;
int i, founddata;
int eos=0,ret;
int i, founddata;
/********** Encode setup ************/
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Setting up encoding");
vorbis_info_init(&vi);
/********** Encode setup ************/
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Setting up encoding");
vorbis_info_init(&vi);
/* choose an encoding mode. A few possibilities commented out, one
actually used: */
/* choose an encoding mode. A few possibilities commented out, one
actually used: */
/*********************************************************************
Encoding using a VBR quality mode. The usable range is -.1
(lowest quality, smallest file) to 1. (highest quality, largest file).
Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR
/*********************************************************************
Encoding using a VBR quality mode. The usable range is -.1
(lowest quality, smallest file) to 1. (highest quality, largest file).
Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR
ret = vorbis_encode_init_vbr(&vi,2,44100,.4);
ret = vorbis_encode_init_vbr(&vi,2,44100,.4);
---------------------------------------------------------------------
---------------------------------------------------------------------
Encoding using an average bitrate mode (ABR).
example: 44kHz stereo coupled, average 128kbps VBR
Encoding using an average bitrate mode (ABR).
example: 44kHz stereo coupled, average 128kbps VBR
ret = vorbis_encode_init(&vi,2,44100,-1,128000,-1);
ret = vorbis_encode_init(&vi,2,44100,-1,128000,-1);
---------------------------------------------------------------------
---------------------------------------------------------------------
Encode using a quality mode, but select that quality mode by asking for
an approximate bitrate. This is not ABR, it is true VBR, but selected
using the bitrate interface, and then turning bitrate management off:
Encode using a quality mode, but select that quality mode by asking for
an approximate bitrate. This is not ABR, it is true VBR, but selected
using the bitrate interface, and then turning bitrate management off:
ret = ( vorbis_encode_setup_managed(&vi,2,44100,-1,128000,-1) ||
vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE2_SET,NULL) ||
vorbis_encode_setup_init(&vi));
ret = ( vorbis_encode_setup_managed(&vi,2,44100,-1,128000,-1) ||
vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE2_SET,NULL) ||
vorbis_encode_setup_init(&vi));
*********************************************************************/
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Initializing with %d channels %dHz sample rate and %f quality", channels, sampleRate, quality);
ret=vorbis_encode_init_vbr(&vi,channels,sampleRate, quality);
*********************************************************************/
switch(type) {
case WITH_BITRATE:
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Initializing with %lld channels %lldHz sample rate and %lld bitrate", channels, sampleRate, bitrate);
ret=vorbis_encode_init(&vi, (long)channels, (long)sampleRate, (long)-1, (long)bitrate, (long)-1);
break;
case WITH_QUALITY:
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Initializing with %lld channels %lldHz sample rate and %f quality", channels, sampleRate, quality);
ret=vorbis_encode_init_vbr(&vi, (long)channels, (long)sampleRate, (float)quality);
break;
default:
__android_log_print(ANDROID_LOG_ERROR, "VorbisEncoder", "Failed to initialize");
stopEncodeFeed(env, &encoderDataFeed, &stopMethodId);
return ERROR_INITIALIZING;
}
/* do not continue if setup failed; this can happen if we ask for a
mode that libVorbis does not support (eg, too low a bitrate, etc,
will return 'OV_EIMPL') */
if(ret) {
__android_log_print(ANDROID_LOG_ERROR, "VorbisEncoder", "Failed to initialize");
throwEncodeException(env, ERROR_INITIALIZING, &encoderDataFeed, &stopMethodId);
return ERROR_INITIALIZING;
}
/* do not continue if setup failed; this can happen if we ask for a
mode that libVorbis does not support (eg, too low a bitrate, etc,
will return 'OV_EIMPL') */
startEncodeFeed(env, &encoderDataFeed, &startMethodId);
/* add a comment */
__android_log_print(ANDROID_LOG_DEBUG, "VorbisEncoder", "Adding comments");
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc,"ENCODER","JNIVorbisEncoder");
/* set up the analysis state and auxiliary encoding storage */
vorbis_analysis_init(&vd,&vi);
vorbis_block_init(&vd,&vb);
/* set up our packet->stream encoder */
/* pick a random serial number; that way we can more likely build
chained streams just by concatenation */
srand(time(NULL));
ogg_stream_init(&os,rand());
/* Vorbis streams begin with three headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
bitstream spec. The second header holds any comment fields. The
third header holds the bitstream codebook. We merely need to
make the headers, then pass them to libvorbis one at a time;
libvorbis handles the additional Ogg bitstream constraints */
{
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
ogg_stream_packetin(&os,&header); /* automatically placed in its own
page */
ogg_stream_packetin(&os,&header_comm);
ogg_stream_packetin(&os,&header_code);
/* This ensures the actual
* audio data will start on a new page, as per spec
*/
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Writting header");
while(!eos){
int result=ogg_stream_flush(&os,&og);
if(result==0)break;
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.header, og.header_len, &jByteArrayWriteBuffer);
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.body, og.body_len, &jByteArrayWriteBuffer);
if(ret) {
__android_log_print(ANDROID_LOG_ERROR, "VorbisEncoder", "Failed to initialize");
stopEncodeFeed(env, &encoderDataFeed, &stopMethodId);
return ERROR_INITIALIZING;
}
}
startEncodeFeed(env, &encoderDataFeed, &startMethodId);
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Starting to read from pcm callback");
while(!eos){
long i;
long bytes = readPCMDataFromEncoderDataFeed(env, &encoderDataFeed, &readPCMDataMethodId, readbuffer, READ*4, &jByteArrayBuffer);
/* add a comment */
__android_log_print(ANDROID_LOG_DEBUG, "VorbisEncoder", "Adding comments");
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc,"ENCODER","JNIVorbisEncoder");
if(bytes==0){
/* end of file. this can be done implicitly in the mainline,
but it's easier to see here in non-clever fashion.
Tell the library we're at end of stream so that it can handle
the last frame and mark end of stream in the output properly */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "End of file");
vorbis_analysis_wrote(&vd,0);
/* set up the analysis state and auxiliary encoding storage */
vorbis_analysis_init(&vd,&vi);
vorbis_block_init(&vd,&vb);
}else{
/* data to encode */
/* set up our packet->stream encoder */
/* pick a random serial number; that way we can more likely build
chained streams just by concatenation */
srand(time(NULL));
ogg_stream_init(&os,rand());
/* expose the buffer to submit data */
float **buffer=vorbis_analysis_buffer(&vd,READ);
/* Vorbis streams begin with three headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
bitstream spec. The second header holds any comment fields. The
third header holds the bitstream codebook. We merely need to
make the headers, then pass them to libvorbis one at a time;
libvorbis handles the additional Ogg bitstream constraints */
/* uninterleave samples */
int channel;
for(i=0;i<bytes/(2*channels);i++) {
for(channel = 0; channel < channels; channel++) {
buffer[channel][i]=((readbuffer[i*(2*channels)+(channel*2+1)]<<8)|
(0x00ff&(int)readbuffer[i*(2*channels)+(channel*2)]))/32768.f;
}
{
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
ogg_stream_packetin(&os,&header); /* automatically placed in its own
page */
ogg_stream_packetin(&os,&header_comm);
ogg_stream_packetin(&os,&header_code);
/* This ensures the actual
* audio data will start on a new page, as per spec
*/
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Writting header");
while(!eos){
int result=ogg_stream_flush(&os,&og);
if(result==0)break;
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.header, og.header_len, &jByteArrayWriteBuffer);
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.body, og.body_len, &jByteArrayWriteBuffer);
}
/* tell the library how much we actually submitted */
vorbis_analysis_wrote(&vd,i);
}
/* 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(&vd,&vb)==1){
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Starting to read from pcm callback");
while(!eos){
long i;
long bytes = readPCMDataFromEncoderDataFeed(env, &encoderDataFeed, &readPCMDataMethodId, readbuffer, READ*4, &jByteArrayBuffer);
/* analysis, assume we want to use bitrate management */
vorbis_analysis(&vb,NULL);
vorbis_bitrate_addblock(&vb);
if(bytes==0){
/* end of file. this can be done implicitly in the mainline,
but it's easier to see here in non-clever fashion.
Tell the library we're at end of stream so that it can handle
the last frame and mark end of stream in the output properly */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "End of file");
vorbis_analysis_wrote(&vd,0);
while(vorbis_bitrate_flushpacket(&vd,&op)){
}else{
/* data to encode */
/* weld the packet into the bitstream */
ogg_stream_packetin(&os,&op);
/* expose the buffer to submit data */
float **buffer=vorbis_analysis_buffer(&vd,READ);
/* write out pages (if any) */
while(!eos){
int result=ogg_stream_pageout(&os,&og);
if(result==0)break;
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.header, og.header_len, &jByteArrayWriteBuffer);
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.body, og.body_len, &jByteArrayWriteBuffer);
/* uninterleave samples */
int channel;
for(i=0;i<bytes/(2*channels);i++) {
for(channel = 0; channel < channels; channel++) {
buffer[channel][i]=((readbuffer[i*(2*channels)+(channel*2+1)]<<8)|
(0x00ff&(int)readbuffer[i*(2*channels)+(channel*2)]))/32768.f;
}
}
/* this could be set above, but for illustrative purposes, I do
it here (to show that vorbis does know where the stream ends) */
/* tell the library how much we actually submitted */
vorbis_analysis_wrote(&vd,i);
}
if(ogg_page_eos(&og))eos=1;
/* 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(&vd,&vb)==1){
/* analysis, assume we want to use bitrate management */
vorbis_analysis(&vb,NULL);
vorbis_bitrate_addblock(&vb);
while(vorbis_bitrate_flushpacket(&vd,&op)){
/* weld the packet into the bitstream */
ogg_stream_packetin(&os,&op);
/* write out pages (if any) */
while(!eos){
int result=ogg_stream_pageout(&os,&og);
if(result==0)break;
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.header, og.header_len, &jByteArrayWriteBuffer);
writeVorbisDataToEncoderDataFeed(env, &encoderDataFeed, &writeVorbisDataMethodId, og.body, og.body_len, &jByteArrayWriteBuffer);
/* 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(&og))eos=1;
}
}
}
}
}
/* clean up and exit. vorbis_info_clear() must be called last */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Cleaning up encoder");
ogg_stream_clear(&os);
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
/* clean up and exit. vorbis_info_clear() must be called last */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Cleaning up encoder");
ogg_stream_clear(&os);
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
/* ogg_page and ogg_packet structs always point to storage in
libvorbis. They're never freed or manipulated directly */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Completed encoding.");
stopEncodeFeed(env, &encoderDataFeed, &stopMethodId);
/* ogg_page and ogg_packet structs always point to storage in
libvorbis. They're never freed or manipulated directly */
__android_log_print(ANDROID_LOG_INFO, "VorbisEncoder", "Completed encoding.");
stopEncodeFeed(env, &encoderDataFeed, &stopMethodId);
//Clean up encode buffers
(*env)->DeleteLocalRef(env, jByteArrayBuffer);
(*env)->DeleteLocalRef(env, jByteArrayWriteBuffer);
//Clean up encode buffers
(*env)->DeleteLocalRef(env, jByteArrayBuffer);
(*env)->DeleteLocalRef(env, jByteArrayWriteBuffer);
return SUCCESS;
return SUCCESS;
}
//jni method for encoding with quality
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncodingWithQuality
(JNIEnv *env, jclass cls, jlong sampleRate, jlong channels, jfloat quality, jobject encoderDataFeed) {
startEncoding(env, &cls, &sampleRate, &channels, &quality, &NO_BITRATE, &encoderDataFeed, WITH_QUALITY);
}
//jni method for encoding with bitrate
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncodingWithBitrate
(JNIEnv *env, jclass cls, jlong sampleRate, jlong channels, jlong bitrate, jobject encoderDataFeed) {
startEncoding(env, &cls, &sampleRate, &channels, &NO_QUALITY, &bitrate, &encoderDataFeed, WITH_BITRATE);
}

View File

@ -13,9 +13,6 @@
extern "C" {
#endif
//Throws an exception to the java layer with th specified error code and stops the encode feed
void throwEncodeException(JNIEnv *env, const int code, jobject* vorbisDataFeed, jmethodID* stopMethodId);
//Starts the encode feed
void startEncodeFeed(JNIEnv *env, jobject *vorbisDataFeed, jmethodID* startMethodId);
@ -28,9 +25,16 @@ long readPCMDataFromEncoderDataFeed(JNIEnv *env, jobject* encoderDataFeed, jmeth
//Writes the vorbis data to the Java layer
int writeVorbisDataToEncoderDataFeed(JNIEnv *env, jobject* encoderDataFeed, jmethodID* writeVorbisDataMethodId, char* buffer, int bytes, jbyteArray* jByteArrayWriteBuffer);
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncoding
//Method to start encoding
int startEncoding(JNIEnv *env, jclass *cls_ptr, jlong *sampleRate_ptr, jlong *channels_ptr, jfloat *quality_ptr, jlong *bitrate_ptr, jobject *encoderDataFeed_ptr, int type);
//jni method for encoding with quality
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncodingWithQuality
(JNIEnv *env, jclass cls, jlong sampleRate, jlong channels, jfloat quality, jobject encoderDataFeed);
//jni method for encoding with bitrate
JNIEXPORT int JNICALL Java_org_xiph_vorbis_encoder_VorbisEncoder_startEncodingWithBitrate
(JNIEnv *env, jclass cls, jlong sampleRate, jlong channels, jlong bitrate, jobject encoderDataFeed);
#ifdef __cplusplus
}
#endif

Binary file not shown.

View File

@ -1,19 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Recording"
android:id="@+id/button" android:onClick="startRecording"/>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Stop Recording"
android:id="@+id/button1" android:onClick="stopRecording"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Playing"
android:id="@+id/button2" android:onClick="startPlaying"/>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Stop Playing"
android:id="@+id/button3" android:onClick="stopPlaying"/>
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sample Rate"
android:id="@+id/textView"/>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sample_rate_spinner"
android:entries="@array/available_sample_rates"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Channel Configuration"
android:id="@+id/textView2"/>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/channel_config_spinner"
android:entries="@array/available_channel_configurations"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Encoding Type" android:id="@+id/textView1"/>
<RadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:weightSum="2" android:orientation="horizontal"
android:id="@+id/encoding_type_radio_group">
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="With Quality"
android:id="@+id/with_quality" android:layout_weight="1" android:checked="true"/>
<RadioButton android:layout_width="0dp" android:layout_height="wrap_content"
android:text="With Bitrate" android:id="@+id/with_bitrate" android:layout_weight="1"/>
</RadioGroup>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:id="@+id/available_qualities_layout"
android:layout_height="fill_parent">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Quality Level" android:id="@+id/textView3"/>
<Spinner android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/quality_spinner"
android:entries="@array/available_qualities"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:id="@+id/available_bitrates_layout"
android:layout_height="fill_parent" android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Bitrate" android:id="@+id/textView4"/>
<Spinner android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/bitrate_spinner"
android:entries="@array/available_bitrates"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Start Recording"
android:id="@+id/start_recording_button" android:onClick="startRecording" android:layout_weight="1"/>
<Button android:layout_width="0dp" android:layout_height="wrap_content" android:text="Stop Recording"
android:id="@+id/stop_recording_button" android:onClick="stopRecording" android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Start Playing"
android:id="@+id/start_playing_button" android:onClick="startPlaying" android:layout_weight="1"/>
<Button android:layout_width="0dp" android:layout_height="wrap_content" android:text="Stop Playing"
android:id="@+id/stop_playing_button" android:onClick="stopPlaying" android:layout_weight="1"/>
</LinearLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical"
android:id="@+id/log_area"/>
</LinearLayout>

37
res/values/arrays.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="available_sample_rates">
<item>8000</item>
<item>11025</item>
<item>16000</item>
<item>22050</item>
<item>44100</item>
</string-array>
<string-array name="available_channel_configurations">
<item>Mono (1 Channel)</item>
<item>Stereo (2 Channels)</item>
</string-array>
<string-array name="available_qualities">
<item>-0.1</item>
<item>0</item>
<item>0.1</item>
<item>0.2</item>
<item>0.3</item>
<item>0.4</item>
<item>0.5</item>
<item>0.6</item>
<item>0.7</item>
<item>0.8</item>
<item>0.9</item>
<item>1</item>
</string-array>
<string-array name="available_bitrates">
<item>32000</item>
<item>96000</item>
<item>128000</item>
<item>160000</item>
<item>192000</item>
<item>256000</item>
<item>320000</item>
</string-array>
</resources>

View File

@ -3,8 +3,16 @@ package com.vorbisdemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.xiph.vorbis.player.VorbisPlayer;
import org.xiph.vorbis.recorder.VorbisRecorder;
@ -31,6 +39,56 @@ public class MainActivity extends Activity {
*/
private VorbisRecorder vorbisRecorder;
/**
* The sample rate selection spinner
*/
private Spinner sampleRateSpinner;
/**
* The channel config spinner
*/
private Spinner chanelConfigSpinner;
/**
* Qualities layout to hide when bitrate radio option is selected
*/
private LinearLayout availableQualitiesLayout;
/**
* The quality spinner when with quality radio option is selected
*/
private Spinner qualitySpinner;
/**
* Bitrate layout to hide when quality radio option is selected
*/
private LinearLayout availableBitratesLayout;
/**
* The bitrate spinner when bitrate radio option is selected
*/
private Spinner bitrateSpinner;
/**
* Radio group for encoding types
*/
private RadioGroup encodingTypeRadioGroup;
/**
* Recording handler for callbacks
*/
private Handler recordingHandler;
/**
* Playback handler for callbacks
*/
private Handler playbackHandler;
/**
* Text view to show logged messages
*/
private TextView logArea;
/**
* Creates and sets our activities layout
*
@ -40,6 +98,114 @@ public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sampleRateSpinner = (Spinner) findViewById(R.id.sample_rate_spinner);
chanelConfigSpinner = (Spinner) findViewById(R.id.channel_config_spinner);
encodingTypeRadioGroup = (RadioGroup) findViewById(R.id.encoding_type_radio_group);
availableQualitiesLayout = (LinearLayout) findViewById(R.id.available_qualities_layout);
qualitySpinner = (Spinner) findViewById(R.id.quality_spinner);
availableBitratesLayout = (LinearLayout) findViewById(R.id.available_bitrates_layout);
bitrateSpinner = (Spinner) findViewById(R.id.bitrate_spinner);
logArea = (TextView) findViewById(R.id.log_area);
//Set log area to scroll automatically
logArea.setMovementMethod(new ScrollingMovementMethod());
setLoggingHandlers();
setEncodingTypeCheckListener();
setDefaultValues();
}
/**
* Sets the recording and playback handlers for message logging
*/
private void setLoggingHandlers() {
recordingHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case VorbisRecorder.START_ENCODING:
logMessage("Starting to encode");
break;
case VorbisRecorder.STOP_ENCODING:
logMessage("Stopping the encoder");
break;
case VorbisRecorder.UNSUPPORTED_AUDIO_TRACK_RECORD_PARAMETERS:
logMessage("You're device does not support this configuration");
break;
case VorbisRecorder.ERROR_INITIALIZING:
logMessage("There was an error initializing. Try changing the recording configuration");
break;
case VorbisRecorder.FAILED_FOR_UNKNOWN_REASON:
logMessage("The encoder failed for an unknown reason!");
break;
case VorbisRecorder.FINISHED_SUCCESSFULLY:
logMessage("The encoder has finished successfully");
break;
}
}
};
playbackHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case VorbisPlayer.PLAYING_FAILED:
logMessage("The decoder failed to playback the file, check logs for more details");
break;
case VorbisPlayer.PLAYING_FINISHED:
logMessage("The decoder finished successfully");
break;
case VorbisPlayer.PLAYING_STARTED:
logMessage("Starting to decode");
break;
}
}
};
}
/**
* Sets the default values for the input configuration spinners
*/
private void setDefaultValues() {
//Set sample rate to '44100'
sampleRateSpinner.setSelection(4);
//Set channel configuration to 'Stereo (2 Channels)'
chanelConfigSpinner.setSelection(1);
//Set quality to value '1'
qualitySpinner.setSelection(11);
//Set bitrate to '128000'
bitrateSpinner.setSelection(2);
}
/**
* Sets the encoding type check listener to show/hide the quality and bitrate spinners
*/
private void setEncodingTypeCheckListener() {
encodingTypeRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
switch (checkedId) {
case R.id.with_bitrate:
availableBitratesLayout.setVisibility(View.VISIBLE);
availableQualitiesLayout.setVisibility(View.GONE);
break;
case R.id.with_quality:
availableBitratesLayout.setVisibility(View.GONE);
availableQualitiesLayout.setVisibility(View.VISIBLE);
break;
}
}
});
}
/**
@ -55,11 +221,23 @@ public class MainActivity extends Activity {
//Create our recorder if necessary
if (vorbisRecorder == null) {
vorbisRecorder = new VorbisRecorder(fileToSaveTo);
vorbisRecorder = new VorbisRecorder(fileToSaveTo, recordingHandler);
}
//Start recording with 44KHz stereo with 0.2 quality
vorbisRecorder.start(44100, 2, 0.2f);
Long sampleRate = Long.parseLong(sampleRateSpinner.getSelectedItem().toString());
Long channels = (long) (chanelConfigSpinner.getSelectedItemPosition() + 1);
//Start recording with selected encoding options
switch (encodingTypeRadioGroup.getCheckedRadioButtonId()) {
case R.id.with_bitrate:
Long bitrate = Long.parseLong(bitrateSpinner.getSelectedItem().toString());
vorbisRecorder.start(sampleRate, channels, bitrate);
break;
case R.id.with_quality:
Float quality = Float.parseFloat(qualitySpinner.getSelectedItem().toString());
vorbisRecorder.start(sampleRate, channels, quality);
break;
}
}
}
@ -88,7 +266,7 @@ public class MainActivity extends Activity {
//Create the vorbis player if necessary
if (vorbisPlayer == null) {
try {
vorbisPlayer = new VorbisPlayer(fileToPlay, null);
vorbisPlayer = new VorbisPlayer(fileToPlay, playbackHandler);
} catch (FileNotFoundException e) {
Log.e(TAG, "Failed to find saveTo.ogg", e);
Toast.makeText(this, "Failed to find file to play!", 2000).show();
@ -110,4 +288,24 @@ public class MainActivity extends Activity {
vorbisPlayer.stop();
}
}
/**
* Logs a message to the text area and auto scrolls down
*
* @param msg the message to log
*/
private void logMessage(String msg) {
// append the new string
logArea.append(msg + "\n");
// find the amount we need to scroll. This works by
// asking the TextView's internal layout for the position
// of the final line and then subtracting the TextView's height
final int scrollAmount = logArea.getLayout().getLineTop(logArea.getLineCount())
- logArea.getHeight();
// if there is no need to scroll, scrollAmount will be <=0
if (scrollAmount > 0)
logArea.scrollTo(0, scrollAmount);
else
logArea.scrollTo(0, 0);
}
}

View File

@ -1,64 +0,0 @@
package org.xiph.vorbis.decoder;
/**
* An exception class that is thrown by the native {@link VorbisDecoder} when there is a problem decoding the bitstream
* User: vincent
* Date: 4/1/13
* Time: 8:27 AM
*/
public class DecodeException extends Exception {
/**
* The bitstream is not ogg
*/
public static final int INVALID_OGG_BITSTREAM = -21;
/**
* Failed to read first page
*/
public static final int ERROR_READING_FIRST_PAGE = -22;
/**
* Failed reading the initial header packet
*/
public static final int ERROR_READING_INITIAL_HEADER_PACKET = -23;
/**
* The data is not a vorbis header
*/
public static final int NOT_VORBIS_HEADER = -24;
/**
* The secondary header is corrupt
*/
public static final int CORRUPT_SECONDARY_HEADER = -25;
/**
* Reached a premature end of file
*/
public static final int PREMATURE_END_OF_FILE = -26;
/**
* The thrown error code type
*/
private final int errorCode;
/**
* Decode exception constructor thrown by the native {@link VorbisDecoder}
*
* @param errorCode the message error code
*/
public DecodeException(int errorCode) {
this.errorCode = errorCode;
}
/**
* Returns the error code that should match an above constant
*
* @return the error code thrown by the native decoder
*/
public int getErrorCode() {
return errorCode;
}
}

View File

@ -12,6 +12,36 @@ public interface DecodeFeed {
*/
public static final int SUCCESS = 0;
/**
* The bitstream is not ogg
*/
public static final int INVALID_OGG_BITSTREAM = -21;
/**
* Failed to read first page
*/
public static final int ERROR_READING_FIRST_PAGE = -22;
/**
* Failed reading the initial header packet
*/
public static final int ERROR_READING_INITIAL_HEADER_PACKET = -23;
/**
* The data is not a vorbis header
*/
public static final int NOT_VORBIS_HEADER = -24;
/**
* The secondary header is corrupt
*/
public static final int CORRUPT_SECONDARY_HEADER = -25;
/**
* Reached a premature end of file
*/
public static final int PREMATURE_END_OF_FILE = -26;
/**
* Triggered from the native {@link VorbisDecoder} that is requesting to read the next bit of vorbis data
*

View File

@ -22,7 +22,6 @@ public class VorbisDecoder {
*
* @param decodeFeed the custom decode feed
* @return the result code
* @throws DecodeException thrown when there is a problem decoding the bitstream
*/
public static native int startDecoding(DecodeFeed decodeFeed) throws DecodeException;
public static native int startDecoding(DecodeFeed decodeFeed);
}

View File

@ -1,37 +0,0 @@
package org.xiph.vorbis.encoder;
/**
* An exception class that is thrown by the native {@link VorbisEncoder} when there is a problem encoding
* User: vincent
* Date: 4/1/13
* Time: 8:27 AM
*/
public class EncodeException extends Exception {
/**
* If there was an error initializing the encoder
*/
public static final int ERROR_INITIALIZING = -44;
/**
* The thrown error code type
*/
private final int errorCode;
/**
* Encode exception constructor thrown by the native {@link VorbisEncoder}
*
* @param errorCode the message error code
*/
public EncodeException(int errorCode) {
this.errorCode = errorCode;
}
/**
* Returns the error code that should match an above constant
*
* @return the error code thrown by the native encoder
*/
public int getErrorCode() {
return errorCode;
}
}

View File

@ -13,6 +13,11 @@ public interface EncodeFeed {
*/
public static final int SUCCESS = 0;
/**
* If there was an error initializing the encoder
*/
public static final int ERROR_INITIALIZING = -44;
/**
* Triggered by the native {@link VorbisEncoder} when it needs to read raw pcm data
*
@ -32,10 +37,15 @@ public interface EncodeFeed {
public int writeVorbisData(byte[] vorbisData, int amountToRead);
/**
* To be called to stop the encoder
* To be called by the native encoder notifying the encode feed is complete
*/
public void stop();
/**
* To be called to stop the encoder
*/
public void stopEncoding();
/**
* To be called when the encoding has started
*/

View File

@ -23,7 +23,16 @@ public class VorbisEncoder {
* @param numberOfChannels the number of channels
* @param quality the quality to encode the output vorbis data
* @param encodeFeed the custom encoder feed
* @throws EncodeException thrown if there was a problem encoding the incoming data
*/
public static native int startEncoding(long sampleRate, long numberOfChannels, float quality, EncodeFeed encodeFeed) throws EncodeException;
public static native int startEncodingWithQuality(long sampleRate, long numberOfChannels, float quality, EncodeFeed encodeFeed);
/**
* The native JNI method call to the encoder to start encoding raw pcm data to encoded vorbis data
*
* @param sampleRate the sample rate which the incoming pcm data will arrive
* @param numberOfChannels the number of channels
* @param bitrate the bitrate of the output vorbis data
* @param encodeFeed the custom encoder feed
*/
public static native int startEncodingWithBitrate(long sampleRate, long numberOfChannels, long bitrate, EncodeFeed encodeFeed);
}

View File

@ -6,7 +6,6 @@ import android.media.AudioTrack;
import android.os.*;
import android.os.Process;
import android.util.Log;
import org.xiph.vorbis.decoder.DecodeException;
import org.xiph.vorbis.decoder.DecodeFeed;
import org.xiph.vorbis.decoder.DecodeStreamInfo;
import org.xiph.vorbis.decoder.VorbisDecoder;
@ -133,8 +132,6 @@ public class VorbisPlayer implements Runnable {
@Override
public void stop() {
if (isPlaying() || isReadingHeader()) {
sendMessageThroughHandler(PLAYING_FINISHED);
//Closes the file input stream
if (inputStream != null) {
try {
@ -182,7 +179,7 @@ public class VorbisPlayer implements Runnable {
@Override
public void startReadingHeader() {
if (inputStream == null && isStopped()) {
sendMessageThroughHandler(PLAYING_STARTED);
handler.sendEmptyMessage(PLAYING_STARTED);
try {
inputStream = new BufferedInputStream(new FileInputStream(fileToDecode));
currentState.set(PlayerState.READING_HEADER);
@ -269,8 +266,6 @@ public class VorbisPlayer implements Runnable {
@Override
public void stop() {
if (!isStopped()) {
sendMessageThroughHandler(PLAYING_FINISHED);
//If we were in a state of buffering before we actually started playing, start playing and write some silence to the track
if (currentState.get() == PlayerState.BUFFERING) {
audioTrack.play();
@ -324,7 +319,7 @@ public class VorbisPlayer implements Runnable {
@Override
public void startReadingHeader() {
if (isStopped()) {
sendMessageThroughHandler(PLAYING_STARTED);
handler.sendEmptyMessage(PLAYING_STARTED);
currentState.set(PlayerState.READING_HEADER);
}
}
@ -342,6 +337,9 @@ public class VorbisPlayer implements Runnable {
if (fileToPlay == null) {
throw new IllegalArgumentException("File to play must not be null.");
}
if (handler == null) {
throw new IllegalArgumentException("Handler must not be null.");
}
this.decodeFeed = new FileDecodeFeed(fileToPlay);
this.handler = handler;
}
@ -356,6 +354,9 @@ public class VorbisPlayer implements Runnable {
if (audioDataStream == null) {
throw new IllegalArgumentException("Input stream must not be null.");
}
if (handler == null) {
throw new IllegalArgumentException("Handler must not be null.");
}
this.decodeFeed = new BufferedDecodeFeed(audioDataStream, 24000);
this.handler = handler;
}
@ -370,6 +371,9 @@ public class VorbisPlayer implements Runnable {
if (decodeFeed == null) {
throw new IllegalArgumentException("Decode feed must not be null.");
}
if (handler == null) {
throw new IllegalArgumentException("Handler must not be null.");
}
this.decodeFeed = decodeFeed;
this.handler = handler;
}
@ -395,33 +399,36 @@ public class VorbisPlayer implements Runnable {
public void run() {
//Start the native decoder
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
try {
int result = VorbisDecoder.startDecoding(decodeFeed);
if (result == DecodeFeed.SUCCESS) {
int result = VorbisDecoder.startDecoding(decodeFeed);
switch (result) {
case DecodeFeed.SUCCESS:
Log.d(TAG, "Successfully finished decoding");
}
} catch (DecodeException e) {
sendMessageThroughHandler(PLAYING_FAILED);
switch (e.getErrorCode()) {
case DecodeException.INVALID_OGG_BITSTREAM:
Log.e(TAG, "Invalid ogg bitstream error received");
break;
case DecodeException.ERROR_READING_FIRST_PAGE:
Log.e(TAG, "Error reading first page error received");
break;
case DecodeException.ERROR_READING_INITIAL_HEADER_PACKET:
Log.e(TAG, "Error reading initial header packet error received");
break;
case DecodeException.NOT_VORBIS_HEADER:
Log.e(TAG, "Not a vorbis header error received");
break;
case DecodeException.CORRUPT_SECONDARY_HEADER:
Log.e(TAG, "Corrupt secondary header error received");
break;
case DecodeException.PREMATURE_END_OF_FILE:
Log.e(TAG, "Premature end of file error received");
break;
}
handler.sendEmptyMessage(PLAYING_FINISHED);
break;
case DecodeFeed.INVALID_OGG_BITSTREAM:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Invalid ogg bitstream error received");
break;
case DecodeFeed.ERROR_READING_FIRST_PAGE:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Error reading first page error received");
break;
case DecodeFeed.ERROR_READING_INITIAL_HEADER_PACKET:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Error reading initial header packet error received");
break;
case DecodeFeed.NOT_VORBIS_HEADER:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Not a vorbis header error received");
break;
case DecodeFeed.CORRUPT_SECONDARY_HEADER:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Corrupt secondary header error received");
break;
case DecodeFeed.PREMATURE_END_OF_FILE:
handler.sendEmptyMessage(PLAYING_FAILED);
Log.e(TAG, "Premature end of file error received");
break;
}
}
@ -460,15 +467,4 @@ public class VorbisPlayer implements Runnable {
public synchronized boolean isBuffering() {
return currentState.get() == PlayerState.BUFFERING;
}
/**
* Sends a message code through the handler if one is provided
*
* @param code the code to send via the handler
*/
private void sendMessageThroughHandler(int code) {
if (handler != null) {
handler.sendEmptyMessage(code);
}
}
}

View File

@ -1,11 +1,11 @@
package org.xiph.vorbis.recorder;
import android.os.Handler;
import android.os.Process;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import org.xiph.vorbis.encoder.EncodeException;
import org.xiph.vorbis.encoder.EncodeFeed;
import org.xiph.vorbis.encoder.VorbisEncoder;
@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicReference;
/**
* The VorbisRecorder is responsible for receiving raw pcm data from the {@link AudioRecord} and feeding that data
* to the naitive {@link VorbisEncoder}
* to the native {@link VorbisEncoder}
* <p/>
* This class is primarily intended as a demonstration of how to work with the JNI java interface {@link VorbisEncoder}
* <p/>
@ -28,6 +28,48 @@ import java.util.concurrent.atomic.AtomicReference;
* Time: 12:47 PM
*/
public class VorbisRecorder {
/**
* Vorbis recorder status flag to notify handler to start encoding
*/
public static final int START_ENCODING = 1;
/**
* Vorbis recorder status flag to notify handler to that it has stopped encoding
*/
public static final int STOP_ENCODING = 2;
/**
* Vorbis recorder status flag to notify handler that the recorder has finished successfully
*/
public static final int FINISHED_SUCCESSFULLY = 0;
/**
* Vorbis recorder status flag to notify handler that the encoder has failed for an unknown reason
*/
public static final int FAILED_FOR_UNKNOWN_REASON = -2;
/**
* Vorbis recorder status flag to notify handler that the encoder couldn't initialize an {@link AudioRecord}
*/
public static final int UNSUPPORTED_AUDIO_TRACK_RECORD_PARAMETERS = -3;
/**
* Vorbis recorder status flag to notify handler that the encoder has failed to initialize properly
*/
public static final int ERROR_INITIALIZING = -1;
/**
* Whether the recording will encode with a quality percent or average bitrate
*/
private static enum RecordingType {
WITH_QUALITY, WITH_BITRATE
}
/**
* The record handler to post status updates to
*/
private final Handler recordHandler;
/**
* The sample rate of the recorder
*/
@ -43,11 +85,21 @@ public class VorbisRecorder {
*/
private float quality;
/**
* The target encoding bitrate
*/
private long bitrate;
/**
* Whether the recording will encode with a quality percent or average bitrate
*/
private RecordingType recordingType;
/**
* The state of the recorder
*/
private static enum RecorderState {
RECORDING, STOPPED
RECORDING, STOPPED, STOPPING
}
/**
@ -100,7 +152,7 @@ public class VorbisRecorder {
@Override
public long readPCMData(byte[] pcmDataBuffer, int amountToRead) {
//If we are no longer recording, return 0 to let the native encoder know
if (isStopped()) {
if (isStopped() || isStopping()) {
return 0;
}
@ -124,7 +176,7 @@ public class VorbisRecorder {
@Override
public int writeVorbisData(byte[] vorbisData, int amountToWrite) {
//If we have data to write and we are recording, write the data
if (vorbisData != null && amountToWrite > 0 && outputStream != null && isRecording()) {
if (vorbisData != null && amountToWrite > 0 && outputStream != null && !isStopped()) {
try {
//Write the data to the output stream
outputStream.write(vorbisData, 0, amountToWrite);
@ -141,13 +193,16 @@ public class VorbisRecorder {
@Override
public void stop() {
if (isRecording()) {
recordHandler.sendEmptyMessage(STOP_ENCODING);
if (isRecording() || isStopping()) {
//Set our state to stopped
currentState.set(RecorderState.STOPPED);
//Close the output stream
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close output stream", e);
@ -163,12 +218,27 @@ public class VorbisRecorder {
}
}
@Override
public void stopEncoding() {
if (isRecording()) {
//Set our state to stopped
currentState.set(RecorderState.STOPPING);
}
}
@Override
public void start() {
if (isStopped()) {
recordHandler.sendEmptyMessage(START_ENCODING);
//Creates the audio recorder
int channelConfiguration = numberOfChannels == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
int bufferSize = AudioRecord.getMinBufferSize((int) sampleRate, channelConfiguration, AudioFormat.ENCODING_PCM_16BIT);
if(bufferSize < 0) {
recordHandler.sendEmptyMessage(UNSUPPORTED_AUDIO_TRACK_RECORD_PARAMETERS);
}
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, (int) sampleRate, channelConfiguration, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
//Start recording
@ -217,7 +287,7 @@ public class VorbisRecorder {
@Override
public long readPCMData(byte[] pcmDataBuffer, int amountToRead) {
//If we are no longer recording, return 0 to let the native encoder know
if (isStopped()) {
if (isStopped() || isStopping()) {
return 0;
}
@ -241,7 +311,7 @@ public class VorbisRecorder {
@Override
public int writeVorbisData(byte[] vorbisData, int amountToWrite) {
//If we have data to write and we are recording, write the data
if (vorbisData != null && amountToWrite > 0 && outputStream != null && isRecording()) {
if (vorbisData != null && amountToWrite > 0 && outputStream != null && !isStopped()) {
try {
//Write the data to the output stream
outputStream.write(vorbisData, 0, amountToWrite);
@ -258,7 +328,9 @@ public class VorbisRecorder {
@Override
public void stop() {
if (isRecording()) {
recordHandler.sendEmptyMessage(STOP_ENCODING);
if (isRecording() || isStopping()) {
//Set our state to stopped
currentState.set(RecorderState.STOPPED);
@ -281,9 +353,19 @@ public class VorbisRecorder {
}
}
@Override
public void stopEncoding() {
if (isRecording()) {
//Set our state to stopped
currentState.set(RecorderState.STOPPING);
}
}
@Override
public void start() {
if (isStopped()) {
recordHandler.sendEmptyMessage(START_ENCODING);
//Creates the audio recorder
int channelConfiguration = numberOfChannels == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
int bufferSize = AudioRecord.getMinBufferSize((int) sampleRate, channelConfiguration, AudioFormat.ENCODING_PCM_16BIT);
@ -299,9 +381,10 @@ public class VorbisRecorder {
/**
* Constructs a recorder that will record an ogg file
*
* @param fileToSaveTo the file to save to
* @param fileToSaveTo the file to save to
* @param recordHandler the handler for receiving status updates about the recording process
*/
public VorbisRecorder(File fileToSaveTo) {
public VorbisRecorder(File fileToSaveTo, Handler recordHandler) {
if (fileToSaveTo == null) {
throw new IllegalArgumentException("File to play must not be null.");
}
@ -312,32 +395,37 @@ public class VorbisRecorder {
}
this.encodeFeed = new FileEncodeFeed(fileToSaveTo);
this.recordHandler = recordHandler;
}
/**
* Constructs a recorder that will record an ogg output stream
*
* @param streamToWriteTo the output stream to write the encoded information to
* @param recordHandler the handler for receiving status updates about the recording process
*/
public VorbisRecorder(OutputStream streamToWriteTo) {
public VorbisRecorder(OutputStream streamToWriteTo, Handler recordHandler) {
if (streamToWriteTo == null) {
throw new IllegalArgumentException("File to play must not be null.");
}
this.encodeFeed = new OutputStreamEncodeFeed(streamToWriteTo);
this.recordHandler = recordHandler;
}
/**
* Constructs a vorbis recorder with a custom {@link EncodeFeed}
*
* @param encodeFeed the custom {@link EncodeFeed}
* @param encodeFeed the custom {@link EncodeFeed}
* @param recordHandler the handler for receiving status updates about the recording process
*/
public VorbisRecorder(EncodeFeed encodeFeed) {
public VorbisRecorder(EncodeFeed encodeFeed, Handler recordHandler) {
if (encodeFeed == null) {
throw new IllegalArgumentException("Encode feed must not be null.");
}
this.encodeFeed = encodeFeed;
this.recordHandler = recordHandler;
}
/**
@ -363,6 +451,37 @@ public class VorbisRecorder {
this.sampleRate = sampleRate;
this.numberOfChannels = numberOfChannels;
this.quality = quality;
this.recordingType = RecordingType.WITH_QUALITY;
//Starts the recording process
new Thread(new AsyncEncoding()).start();
}
}
/**
* Starts the recording/encoding process
*
* @param sampleRate the rate to sample the audio at, should be greater than <code>0</code>
* @param numberOfChannels the nubmer of channels, must only be <code>1/code> or <code>2</code>
* @param bitrate the bitrate at which to encode, must be greater than <code>-0</code>
*/
@SuppressWarnings("all")
public synchronized void start(long sampleRate, long numberOfChannels, long bitrate) {
if (isStopped()) {
if (numberOfChannels != 1 && numberOfChannels != 2) {
throw new IllegalArgumentException("Channels can only be one or two");
}
if (sampleRate <= 0) {
throw new IllegalArgumentException("Invalid sample rate, must be above 0");
}
if (bitrate <= 0) {
throw new IllegalArgumentException("Target bitrate must be greater than 0");
}
this.sampleRate = sampleRate;
this.numberOfChannels = numberOfChannels;
this.bitrate = bitrate;
this.recordingType = RecordingType.WITH_BITRATE;
//Starts the recording process
new Thread(new AsyncEncoding()).start();
@ -373,7 +492,7 @@ public class VorbisRecorder {
* Stops the audio recorder and notifies the {@link EncodeFeed}
*/
public synchronized void stop() {
encodeFeed.stop();
encodeFeed.stopEncoding();
}
/**
@ -383,18 +502,29 @@ public class VorbisRecorder {
@Override
public void run() {
//Start the native encoder
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
int result = VorbisEncoder.startEncoding(sampleRate, numberOfChannels, quality, encodeFeed);
if (result == EncodeFeed.SUCCESS) {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
int result = 0;
switch (recordingType) {
case WITH_BITRATE:
result = VorbisEncoder.startEncodingWithBitrate(sampleRate, numberOfChannels, bitrate, encodeFeed);
break;
case WITH_QUALITY:
result = VorbisEncoder.startEncodingWithQuality(sampleRate, numberOfChannels, quality, encodeFeed);
break;
}
switch (result) {
case EncodeFeed.SUCCESS:
Log.d(TAG, "Encoder successfully finished");
}
} catch (EncodeException e) {
switch (e.getErrorCode()) {
case EncodeException.ERROR_INITIALIZING:
Log.e(TAG, "There was an error initializing the native encoder");
break;
}
recordHandler.sendEmptyMessage(FINISHED_SUCCESSFULLY);
break;
case EncodeFeed.ERROR_INITIALIZING:
recordHandler.sendEmptyMessage(ERROR_INITIALIZING);
Log.e(TAG, "There was an error initializing the native encoder");
break;
default:
recordHandler.sendEmptyMessage(FAILED_FOR_UNKNOWN_REASON);
Log.e(TAG, "Encoder returned an unknown result code");
break;
}
}
}
@ -416,4 +546,13 @@ public class VorbisRecorder {
public synchronized boolean isStopped() {
return currentState.get() == RecorderState.STOPPED;
}
/**
* Checks whether the recording is currently stopping (not recording)
*
* @return <code>true</code> if stopping, <code>false</code> otherwise
*/
public synchronized boolean isStopping() {
return currentState.get() == RecorderState.STOPPING;
}
}