-Expanding demo application to allow quick customization, adding bitrate target option, and changing how exception handling works
parent
034b101c55
commit
5185b9500b
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue