📜 ⬆️ ⬇️

Streaming audio

Introduction


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

Constructor

To create an object you need to specify:
audioSourceWhere is the recording. In our case it is MediaRecorder.AudioSource.MIC
sampleRateInHzThe sampling rate in hertz. Documentation states that 44100Hz is supported by all devices.
channelConfigChannel configuration. May be CHANNEL_IN_MONO CHANNEL_IN_MONO or CHANNEL_IN_STEREO CHANNEL_IN_STEREO . Mono works everywhere.

Important: these constants do not coincide with the number of channels that they designate. This parameter cannot be passed 1 or 2.
audioFormatThe input format, better known as codec. May be ENCODING_PCM_16BIT ENCODING_PCM_16BIT or ENCODING_PCM_8BIT
bufferSizeInBytesThe size of that inner buffer. From it you can read the audio stream. The size of the read portion should not exceed this value. This parameter has a minimum value that can be obtained through getMinBufferSize() getMinBufferSize() .

When it is created, the object tries to obtain the necessary system resources. How well he did it, you can find out by calling the function getState() getState() . If she returns STATE_INITIALIZED STATE_INITIALIZED , then everything is fine if STATE_UNINITIALIZED STATE_UNINITIALIZED means an error has occurred.

There can be two reasons for the error: the buffer is too small and the format is invalid. First you need to avoid calling getMinBufferSize() . The second, in fact, it is.
')
getMinBufferSize ()

This static method yields the minimum internal buffer size at which an AudioRecord object can work. Parameters have the same meaning as for the constructor. It should be noted that using this value for recording is not the best idea. If the system is still busy with something, 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 the advice to take a size ten times larger.

Getting a list of formats

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

For example:
 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)) { //   } } } } 


Read data

To get data from the internal buffer, use the method read() read() . It exists in three versions:Their parameters are:
audioDataarray in which data will be written
audioBufferthe buffer to which the data will be written
offsetInBytes /
offsetInShorts
index from which to start recording
sizeInShortssize of the requested data block. In bytes for ByteBuffer and byte[] , in short integers for short[]

If everything is normal, then the method will return the number of bytes read, if this is an option with ByteBuffer or 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 considers the requested amount of data. If there are not enough of them in the internal buffer, then 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

In order for the program to receive data from the microphone, you need to specify the corresponding resolution in the AndroidManifest, xml file:
 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 

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

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

Important: these three methods, unlike those mentioned earlier, will be thrown away. IllegalStateException IllegalStateException , if called for an uninitialized (well, the word ... :) object or in the wrong order. Therefore, it is necessary to handle them “carefully”, i.e. through the try block.

Usage example

The class below does all of the above. In addition, he sends registered with him Handler Handler messages received data. This data will be processed in another stream, therefore, in order not to overwrite data that has not yet been processed with new data, a cyclic buffer is used.

The code uses the AudioFormatInfo class. It is a POJO with three fields describing the recording format: sampleRateInHz , channelConfig and 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<Handler> handlers; private AudioFormatInfo format; private AudioRecord mRecord; private final int BUFF_COUNT = 32; public AudioReciever(AudioFormatInfo format) { this.format = format; handlers = new ArrayList<Handler>(); 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)); } } } 

Source: https://habr.com/ru/post/137708/


All Articles