Work with streaming audio

    Introduction


    For receiving raw data from a microphone, the class is responsible android.media.AudioRecord. It writes data to the internal buffer, from which we must periodically collect it.

    Constructor

    To create an object you need to specify:
    audioSourceWhere is the recording from? In our case, thisMediaRecorder.AudioSource.MIC
    sampleRateInHz Hertz sample rate. The documentation claims that 44100Hz is supported by all devices
    channelconfig Channel Configuration Maybe CHANNEL_IN_MONOor CHANNEL_IN_STEREO. Mono works everywhere.

    Important: these constants do not match the number of channels that they denote. You cannot pass 1 or 2 to this parameter.
    audioFormatAn input data format, better known as a codec. Maybe ENCODING_PCM_16BITorENCODING_PCM_8BIT
    bufferSizeInBytesThe size of that same internal buffer. From it you can read the audio stream. The reading portion size should not exceed this value. This parameter has the minimum value that can be obtained through getMinBufferSize().

    During its creation, an object tries to get the system resources it needs. How well he succeeded in this, you can find out by calling the function getState(). If she returns STATE_INITIALIZED, then everything is fine, if STATE_UNINITIALIZED- then an error has occurred.

    There can be two causes of the error: a buffer that is too small and an invalid format. The first is to be avoided by calling getMinBufferSize(). The second, in fact, by him.

    getMinBufferSize ()

    This static method returns the minimum size of the internal buffer at which the object AudioRecordcan work. Parameters have the same meaning as for the constructor. It should be noted that using this particular value for recording is not a good idea. If the system is still busy with something, then the program may still not have time to read all the data in a row, and there will be holes in the record. I met advice to take a size ten times larger.

    Retrieving a list of formats

    The method getMinBufferSize()has a nice feature - swear at parameters that are invalid for this device , returning ERROR_BAD_VALUEor ERROR. This means that by going through all the possible combinations, you can find out what formats the device supports.

    For example, like this:
    int[] rates = {8000, 11025, 22050,44100, 48000, 96000 };
    int[] chans = {AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO};
    int[] encs  = {AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT};
    for(int enc : encs)
    {
        for(int ch : chans)
        {
            for(int rate : rates)
            {
                int t = AudioRecord.getMinBufferSize(rate, ch, enc);
                if((t != AudioRecord.ERROR) && (t != AudioRecord.ERROR_BAD_VALUE))
                {
                    // добавляем формат
                }
            }
        }
    }


    Data reading

    To get data from the internal buffer, use the method read(). It exists in three versions:
    • read(byte[] audioData, int offsetInBytes, int sizeInBytes)
    • read(short[] audioData, int offsetInShorts, int sizeInShorts)
    • read(ByteBuffer audioBuffer, int sizeInBytes)
    Their parameters:
    audioDataarray in which data will be written
    audioBufferbuffer to which data will be written
    offsetInBytes /
    offsetInShorts
    index at which recording will start
    sizeInShortssize of the requested data block. In bytes for ByteBufferand byte[], in short integers forshort[]

    If everything is fine, then the method will return the number of bytes read, if it is an option with ByteBufferor byte[], or read short integers for short[]. If at the time of the call the object was not correctly initialized, it will return ERROR_INVALID_OPERATION , and if something is wrong with the parameters - ERROR_BAD_VALUE

    Important: the method blocks the calling thread until it reads the requested amount of data. If there are not enough of them in the internal buffer, then he read()will wait until they come from the microphone. Therefore, the method should be called from a separate thread , otherwise the application will hang.

    Approach, departure, fixation

    So that the program can receive data from the microphone, you need to specify the appropriate resolution in the AndroidManifest, xml file:

    To start recording, you need to call the method startRecording(), and to finish - stop(). You can start and stop recording as many times as you like.

    After the work with the object is completed, you should call the method release(). It will release all system resources captured by the object. After that, the object cannot be used, and the variable referencing it should be set to null.

    Important: these three methods, unlike those mentioned earlier, will be thrown away IllegalStateExceptionif called for an uninitialized (well, a word ... :) object or in the wrong order. Therefore, they must be handled “carefully”, i.e. through the block try.

    Usage example

    The class below does everything that is mentioned above. In addition, he sends Handlermessages registered with him about received data. This data will be processed in another thread, therefore, in order not to overwrite the data that has not yet been processed with new ones, a circular buffer is used.

    The code used a class AudioFormatInfo. It is a POJO with three fields describing the recording format sampleRateInHz, channelConfigand audioFormat.
    package com.MyCompany;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import android.media.AudioFormat;
    import android.media.AudioRecord;
    import android.media.MediaRecorder;
    import android.os.Handler;
    import android.os.Process;
    //AudioFormatInfo - POJO с полями sampleRateInHz, channelConfig и audioFormat
    public class AudioReciever implements Runnable
    {
    	private boolean mIsRunning;
    	private List handlers;
    	private AudioFormatInfo format;
    	private AudioRecord mRecord;
    	private final int BUFF_COUNT = 32;
    	public AudioReciever(AudioFormatInfo format)
    	{
    		this.format = format;
    		handlers = new ArrayList();
    		mIsRunning = true;
    		mRecord = null;
    	}
    	public void addHandler(Handler handler)
    	{
    		handlers.add(handler);
    	}
    	public void stop()
    	{
    		mIsRunning = false;
    	}
    	@Override
    	public void run()
    	{
    		// приоритет для потока обработки аудио
    		Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
    		mIsRunning = true;
    		int buffSize = AudioRecord.getMinBufferSize(format.getSampleRateInHz(), 
    				format.getChannelConfig(), format.getAudioFormat());
    		if(buffSize == AudioRecord.ERROR)
    		{
    			System.err.println("getMinBufferSize returned ERROR");
    			return;
    		}
    		if(buffSize == AudioRecord.ERROR_BAD_VALUE)
    		{
    			System.err.println("getMinBufferSize returned ERROR_BAD_VALUE");
    			return;
    		}
    		// здесь работаем с short, поэтому требуем 16-bit
    		if(format.getAudioFormat() != AudioFormat.ENCODING_PCM_16BIT)
    		{
    			System.err.println("unknown format");
    			return;
    		}
    		// циклический буфер буферов. Чтобы не затереть данные,
    		// пока главный поток их обрабатывает
    		short[][] buffers = new short[BUFF_COUNT][buffSize >> 1];
    		mRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
    				format.getSampleRateInHz(), 
    				format.getChannelConfig(), format.getAudioFormat(),
    				buffSize * 10);
    		if(mRecord.getState() != AudioRecord.STATE_INITIALIZED)
    		{
    			System.err.println("getState() != STATE_INITIALIZED");
    			return;
    		}
    		try
    		{
    			mRecord.startRecording();
    		}
    		catch(IllegalStateException e)
    		{
    			e.printStackTrace();
    			return;
    		}
    		int count = 0;
    		while(mIsRunning)
    		{
    			int samplesRead = mRecord.read(buffers[count], 0, buffers[count].length);
    			if(samplesRead == AudioRecord.ERROR_INVALID_OPERATION)
    			{
    				System.err.println("read() returned ERROR_INVALID_OPERATION");
    				return;
    			}
    			if(samplesRead == AudioRecord.ERROR_BAD_VALUE)
    			{
    				System.err.println("read() returned ERROR_BAD_VALUE");
    				return;
    			}
    			// посылаем оповещение обработчикам
    			sendMsg(buffers[count]);
    			count = (count + 1) % BUFF_COUNT;
    		}
    		try
    		{
    			try
    			{
    				mRecord.stop();
    			}
    			catch(IllegalStateException e)
    			{
    				e.printStackTrace();
    				return;
    			}
    		}
    		finally
    		{
    			// освобождаем ресурсы
    			mRecord.release();
    			mRecord = null;
    		}
    	}
    	private void sendMsg(short[] data)
    	{
    		for(Handler handler : handlers)
    		{
    			handler.sendMessage(handler.obtainMessage(MSG_DATA, data));
    		}
    	}
    }

    Also popular now: