[Android development experience] Implementation of "Sonic communication/Verification" for mobile devices-Introduction to the SinVoice open-source project (III) and androidsinvoice
Reprinted please indicate the source: http://blog.csdn.net/zhaokaiqiang1992
The first two articles introduce the principles of acoustic wave verification/communication and the implementation of sound playback. This one will be the most important and difficult to understand, this is how SinVoice encodes these numbers.
Because a large number of undistinguished callback functions are added to the source code, I have done some renaming and code sorting for ease of reading. Don't be surprised.
First, the project structure is given:
This article focuses on the Encoder, SinGenerator, and Buffer classes.
In the previous article, we learned that SinVoiceplayer is a class that we directly access and use, using SinVoicePlayer. the play (text) method can easily play the audio signal of the number we want to transmit, and then parse it. In SinVoicePlayer, the start () method of PcmPlayer is called to play the video. In pcmPlayer, AudioTrace is called to play the audio. By encapsulating AudioTrace layers, a simple call of SinVoicePlayer is implemented.
Since AudioTrace is the final class for audio playback, where does the data to be played come from?
The answer is that the data comes from the Encoder class, SinGenerator class, and Buffer class.
The following is the code of Encoder. The Code has been sorted out.
/** Copyright (C) 2013 gujicheng ** Licensed under the GPL License Version 2.0; * you may not use this file before t in compliance with the License. ** If you have any question, please contact me. **************************************** * ********************************** Author information ** **************************************** * ********************************** Email: gujicheng197@126.c Om ***** QQ: 29600731 ***** Weibo: *************************************** * ***********************************/package com. libra. sinvoice; import java. util. list; import com. libra. sinvoice. buffer. bufferData;/***** @ ClassName: com. libra. sinvoice. encoder * @ Description: Encoder * @ author zhaokaiqiang * @ date 1:32:17 **/public class Encoder implements SinGene Rator. sinGeneratorCallback {private final static String TAG = "Encoder"; private final static int STATE_ENCODING = 1; private final static int STATE_STOPED = 2; // index 0, 1, 2, 3, 4, 5, 6 // circleCount 31, 28, 25, 22, 19, 15, 10 private final static int [] CODE_FREQUENCY = {1422,157 5, 1764,200 4, 2321,2940, 4410}; private int mState; private SinGenerator mSinGenerator; private EncoderCallback encoder Callback; public static interface EncoderCallback {void freeEncodeBuffer (BufferData buffer); BufferData getEncodeBuffer ();} public Encoder (callback, int sampleRate, int bits, int bufferSize) {EncoderCallback = Callback; mState = STATE_STOPED; mSinGenerator = new SinGenerator (this, sampleRate, bits, bufferSize);} public final static int getMaxCodeCount () {return CODE_FREQUENCY.length;} publ Ic final boolean isStoped () {return (STATE_STOPED = mState);} // content of input from 0 to (CODE_FREQUENCY.length-1) public void encode (List <Integer> codes, int duration) {if (STATE_STOPED = mState) {mState = STATE_ENCODING; mSinGenerator. start (); for (int index: codes) {if (STATE_ENCODING = mState) {if (index> = 0 & index <CODE_FREQUENCY.length) {// use the sine generator to encode mSinGenerator. gen (CODE_FREQUENCY [in Dex], duration);} else {LogHelper. d (TAG, "code index error") ;}} else {LogHelper. d (TAG, "encode force stop"); break ;}} mSinGenerator. stop () ;}} public void stop () {if (STATE_ENCODING = mState) {mState = STATE_STOPED; mSinGenerator. stop () ;}@ Overridepublic BufferData getGenBuffer () {if (null! = EncoderCallback) {return encoderCallback. getEncodeBuffer ();} return null ;}@ Overridepublic void freeGenBuffer (BufferData buffer) {if (null! = EncoderCallback) {encoderCallback. freeEncodeBuffer (buffer );}}}
This class involves the following:
1. This class implements the SinGenerator. SinGeneratorCallback interface, which is actually in the SinGenerator class. This interface mainly completes Data Acquisition and release. In the following code, I will explain
2. the numbers in the array CODE_FREQUENCY represent the frequencies from 0 to 6, and different data will be encoded according to different frequencies, circleCount 31, 28, 25, 22, 19, 15, 10 refers to the number of samples of the sine wave of the corresponding frequency in a cycle during the coding process. The number of samples in each cycle x the total number of cycles = the total number of samples.
Do you still remember the previous DEFAULT_GEN_DURATION = 100? This variable refers to the audio duration corresponding to each number. 100 represents 100 milliseconds, that is, 0.1 seconds. We have said that the default sampling rate is 44.1 KHZ, Which is 44100 times of sampling within 1 s. If you need to play 100 milliseconds, you only need to sample 44100/10 = 4410 times, because
Private final static int [] CODE_FREQUENCY = {1422,157 5, 1764,200 4, 2324410 };
If we want to encode the number 0, we know that the vibration frequency of 0 is 1422 HZ, which is also one second. What if it is Ms? It is 142.2 HZ. We use 142.2x31 = 4408.2, which is close to 4410 times. Therefore, we can know the number of times to be sampled in each cycle based on the frequency of the audio to be generated, the sampling interval can be calculated. Because Encoder is just a packaging class, the real implementation of encoding is SinGenerator, In this tired, we can see a lot of encryption details.
The following is the code implementation of SinGenerator.
/** Copyright (C) 2013 gujicheng ** Licensed under the GPL License Version 2.0; * you may not use this file before t in compliance with the License. ** If you have any question, please contact me. **************************************** * ********************************** Author information ** **************************************** * ********************************** Email: gujicheng197@126.c Om ***** QQ: 29600731 ***** Weibo: *************************************** * ***********************************/package com. libra. sinvoice; import com. libra. sinvoice. buffer. bufferData;/***** @ ClassName: com. libra. sinvoice. sinGenerator * @ Description: Sine Wave Generator * @ author zhaokaiqiang * @ date 2:51:34 **/public class SinGenerator {private static final Strin G TAG = "SinGenerator"; private static final int STATE_START = 1; private static final int STATE_STOP = 2; // public static final int BITS_8 = 128 When 2 ^ 8; // default value: public static final int BITS_16 = 32768; // sampling rate: public static final int SAMPLE_RATE_8 = 8000; public static final int SAMPLE_RATE_11 = 11250; public static final int SAMPLE_RATE_16 = 16000; public static final int UNIT_ACCURACY_1 = 4; public Static final int UNIT_ACCURACY_2 = 8; private int mState; private int mSampleRate; private int mBits; private static final int DEFAULT_BITS = BITS_8; private static final int DEFAULT_SAMPLE_RATE = SAMPLE_RATE_8; private static final int DEFAULT_BUFFER_SIZE = 1024; private int mFilledSize; private int mBufferSize; private SinGeneratorCallback sinGeneratorCallback; public static interface SinGeneratorCallbac K {BufferData getGenBuffer (); void freeGenBuffer (BufferData buffer);} public SinGenerator (callback) {this (callback, callback, DEFAULT_BITS, callback);} public SinGenerator (SinGeneratorCallback callback, int sampleRate, int bits, int bufferSize) {sinGeneratorCallback = callback; mBufferSize = bufferSize; mSampleRate = sampleRate; mBits = bits; mFilledSize = 0; mSta Te = STATE_STOP;} public void stop () {if (STATE_START = mState) {mState = STATE_STOP;} public void start () {if (STATE_STOP = mState) {mState = STATE_START ;}}/*** encode the number ** @ param genRate * @ param duration */public void gen (int genRate, int duration) {if (STATE_START = mState) {// value 16384int n = mBits/2; int totalCount = (duration * mSampleRate)/1000; double per = (genRate/(double) mSample Rate) * 2 * Math. PI; double d = 0; LogHelper. d (TAG, "per:" + per + "___ genRate:" + genRate); if (null! = SinGeneratorCallback) {mFilledSize = 0; // obtain the data to be encoded BufferData bufferData = sinGeneratorCallback. getGenBuffer (); if (null! = BufferData) {for (int I = 0; I <totalCount; ++ I) {if (STATE_START = mState) {// calculate the sine value of the difference int out = (int) (Math. sin (d) * n) + 128; // if the number of fills exceeds the buffer size, reset mFilledSize and release bufferDataif (mFilledSize> = mBufferSize-1) {// free bufferbufferData. setFilledSize (mFilledSize); sinGeneratorCallback. freeGenBuffer (bufferData); mFilledSize = 0; bufferData = sinGeneratorCallback. getGenBuffer (); if (null = bufferDat A) {LogHelper. d (TAG, "get null buffer"); break ;}// transcode to the byte type and save it. & 0xff is used to prevent bufferData exceptions during negative conversion. byteData [mFilledSize ++] = (byte) (out & 0xff); if (BITS_16 = mBits) {bufferData. byteData [mFilledSize ++] = (byte) (out> 8) & 0xff);} d + = per;} else {LogHelper. d (TAG, "sin gen force stop"); break ;}} else {LogHelper. d (TAG, "get null buffer");} if (null! = BufferData) {bufferData. setFilledSize (mFilledSize); sinGeneratorCallback. freeGenBuffer (bufferData) ;}mfilledsize = 0 ;}}}}
The main method is gen (). We will analyze this method in detail.
1int n = mBits/2; the n defined here participates in the operation in the code below. n refers to the peak value of the sine function to be created, that is, the highest point value, the value of mBits is 2 ^ 16/2 = 32768. Here, we divide the peak value by two for recognition rate consideration, because when n is directly assigned to mBits, the sound is sharp, the recognition rate is much lower, so the most peak value of mBits/2 is selected here.
2. int totalCount = (duration * mSampleRate)/1000; this is the number of cycles to be calculated. Because duration = 100, the total number of samples is 4410, and the number of cycles is 4410.
3. double per = (genRate/(double) mSampleRate) * 2 * Math. PI; this per parameter is used to record the distance of each forward step in the loop process, which is related to the frequency. Let's take the number 5 as an example. From the Encoder class, we know that the frequency of 5 is 2940 HZ. If we want to play a sound for 100 ms, we need to shake it for 294 times, that is, 294 sine periods. And the 294 times, according to The 44.1KHZ frequency, that is, the 4410 times of sampling in 4410/294 ms, can be calculated in each cycle, the need to sample = 15, therefore, the number of samples in a cycle is 15 times, and the length of a sine period is 2 pi. Therefore, use 2PI/15 = 0.4186. This value is the per value here.
4. int out = (int) (Math. sin (d) * n) + 128; After calculating the per, the variable d is used in the loop to accumulate the per, and then the previous formula is used, the function value corresponding to the sample point can be calculated. After completing the following operations, the number encoding is realized.
// Transcode to byte type and save it. & 0xff is used to prevent negative conversion exceptions.
BufferData. byteData [mFilledSize ++] = (byte) (out & 0xff );
If (BITS_16 = mBits ){
BufferData. byteData [mFilledSize ++] = (byte) (out> 8) & 0xff );
}
We can see that the Buffre class is used for saving the encoding. This class stores the byte data and stores the encoded byte data, let's take a brief look at the code of this class.
/** Copyright (C) 2013 gujicheng ** Licensed under the GPL License Version 2.0; * you may not use this file before t in compliance with the License. ** If you have any question, please contact me. **************************************** * ********************************** Author information ** **************************************** * ********************************** Email: gujicheng197@126.c Om ***** QQ: 29600731 ***** Weibo: *************************************** * ***********************************/package com. libra. sinvoice; import java. util. concurrent. blockingQueue; import java. util. concurrent. linkedBlockingQueue;/***** @ ClassName: com. libra. sinvoice. buffer * @ Description: Buffer * @ author zhaokaiqiang * @ date 1:35:46 **/public class Buf Fer {private final static String TAG = "Buffer"; // producer queue private BlockingQueue <BufferData> mProducerQueue; // consumer queue private BlockingQueue <BufferData> mConsumeQueue; // Number of buffers private int mBufferCount; // Buffer volume private int mBufferSize; public Buffer () {this (Common. DEFAULT_BUFFER_COUNT, Common. DEFAULT_BUFFER_SIZE);} public Buffer (int bufferCount, int bufferSize) {mBufferSize = bufferSize; mBufferCount = buffer Count; mProducerQueue = new LinkedBlockingQueue <BufferData> (mBufferCount); // we want to put the end buffer, so need to add 1 mConsumeQueue = new LinkedBlockingQueue <BufferData> (mBufferCount + 1); // initialize the producer queue for (int I = 0; I <mBufferCount; ++ I) {try {mProducerQueue. put (new BufferData (mBufferSize);} catch (InterruptedException e) {e. printStackTrace () ;}} public void reset () {// remove the producer's short node int size = MProducerQueue. size (); for (int I = 0; I <size; ++ I) {BufferData data = mProducerQueue. peek (); if (null = data | null = data. byteData) {mProducerQueue. poll () ;}/// add non-empty data from the consumer to the producer. size = mConsumeQueue. size (); for (int I = 0; I <size; ++ I) {BufferData data = mConsumeQueue. poll (); if (null! = Data & null! = Data. byteData) {mProducerQueue. add (data) ;}} LogHelper. d (TAG, "reset ProducerQueue Size:" + mProducerQueue. size () + "ConsumeQueue Size:" + mConsumeQueue. size ();} final public int getEmptyCount () {return mProducerQueue. size ();} final public int getFullCount () {return mConsumeQueue. size () ;}// get the producer's header node, blocking public BufferData getEmpty () {return getImpl (mProducerQueue);} // Add it to the producer public boolean putEmpty (B UfferData data) {return putImpl (data, mProducerQueue);} // get the consumer's header node public BufferData getFull () {return getImpl (mConsumeQueue );} // Add public boolean putFull (BufferData data) {return putImpl (data, mConsumeQueue);} // obtain the queue's header node private BufferData getImpl (BlockingQueue <BufferData> queue) {if (null! = Queue) {try {return queue. take ();} catch (InterruptedException e) {e. printStackTrace () ;}} return null;} // Add data to the queue private boolean putImpl (BufferData data, BlockingQueue <BufferData> queue) {if (null! = Queue & null! = Data) {try {queue. put (data); return true;} catch (InterruptedException e) {e. printStackTrace () ;}} return false;} // when mData is null, means it is end of inputpublic static class BufferData {// data container public byte byteData []; // fill volume private int mFilledSize; // buffer maximum volume private int mMaxBufferSize; // static empty buffer private static BufferData sEmptyBuffer = new BufferData (0); public BufferData (int maxBufferSize) {mMaxBufferSize = maxBufferSize; mFilledSize = 0; if (maxBufferSize> 0) {byteData = new byte [mMaxBufferSize];} else {byteData = null ;}} /*** get an empty buffer ** @ return */public static BufferData getEmptyBuffer () {return sEmptyBuffer;} // reset the filling quantity final public void reset () {mFilledSize = 0;} final public int getMaxBufferSize () {return mMaxBufferSize;} // set the filling quantity final public void setFilledSize (int size) {mFilledSize = size ;} final public int getFilledSize () {return mFilledSize ;}}}
Buffer uses two queues to implement the producer and consumer model, so as to ensure a good compilation and playing one. In the SinVoicePlayer class, two threads are enabled to manage the data in the two queues respectively.
Demohttps: // github.com/ZhaoKaiQiang/SinVoiceDemo