Android 录音与播放

sancaiodm Android应用 2023-09-03 562 0

常见的录音和播放比较

image.png

音频的两种录制方式-AudioRecord,MediaRecorder的使用及播放


1、AudioRecord(基于字节流录音) 

主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩,

AudioRecorder 录音声音数据从音频硬件中被读出,编码格式为 PCM格式,但 PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,

所以必须先写代码实现数据编码以及压缩

示例:

使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右 

从AudioRecord读取的数据是PCM格式,可以用AudioTrack类播放。MediaPlayer也无法正常播放

使用AudioRecord类录制音频 并使用用AudioTrack类播放音频(链接)


2、MediaRecorder(基于文件录音) 

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp

优点:大部分以及集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

功能实现:MediaRecorder(这里需要注意,无论录制还是播放都是一个耗时操作,需要在非主线程中去操作)

示例:

使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K 

MediaRecorder播放音频文件 (链接)


3、音频格式比较

WAV格式:录音质量高,但是压缩率小,文件大

AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者Android SDK4.1.2(API 16)及以上版本支持播放

AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音

至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再转换成mp3格式


AudioRecord使用流程

[1]配置参数(各个参数都有默认值)

audioResource音频采集的来源:可以是麦克风声音、通话声音、系统内置声音。

audioSampleRate音频采样率

channelConfig声道:单声道、双声道等

audioFormat:音频采样精度,指定采样的数据的格式和每次采样的大小,只支持8位和16位。

buffer缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。获取最小的缓冲区大小,用于存放AudioRecord采集到的音频数据。

[2]初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小

byte data[] = new byte[recordBufSize];

[3]AudioRecord对象

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

[4]录制相关操作

开始录制startRecording()

停止录制stop()

释放资源release()

read()的使用

[5]建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中的数据导入数据流

[6]关闭数据流

[7]停止录制

-------------------------------------------

初始化 AudioRecord

首先来看一下 AudioRecord 的配置参数,AudioRecord 是通过构造函数来配置参数的,其函数原型如下:

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes)

上述参数所代表的函数及其在各种场景下应该传递的值的含义参考如下说明:

[1]audioSource: 该参数指的是音频采集的输入源,可选值以常量的形式定义在类 AudioSource (MediaRecorder 中的一个内部类)中,常用的值包括:

DEFAULT(默认)

VOICE_RECOGNITION (用于语音识别,等同于默认)

MIC (由手机麦克风输入)

VOICE_COMMUNICATION (用于 VOIP 应用场景)

[2]sampleRateInHz: 用于指定以多大的采样频率来采集音频,现在用的最多的兼容最好是 44100 (44.1KHZ)采样频率。

[3]channelConfig: 该参数用于指定录音器采集几个声道的声音,可选值以常量的形式定义在 AudioFormat 类中,常用的值包括:

CHANNEL_IN_MONO 单声道 (移动设备上目前推荐使用)

CHANNEL_IN_STEREO 立体声

[4]audioFormat: 采样格式,以常量的形式定义在 AudioFormat 类中,常用的值包括:

ENCODING_PCM_16BIT (16bit 兼容大部分 Android 手机)

ENCODING_PCM_8BIT (8bit)

[5]bufferSizeInBytes: 配置内部音频缓冲区的大小(配置的缓存值越小,延时就越低),而具体的大小,有可能在不同的手机上会有不同的值,

那么可以使用如下 API 进行确定缓冲大小:

AudioRecord.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);

[6]配置好之后,检查一下 AudioRecord 当前的状态是否可以进行录制,可以通过 AudioRecord##getState 来获取当前的状态:

STATE_UNINITIALIZED 还没有初始化,或者初始化失败了

STATE_INITIALIZED 已经初始化成功了。


private AudioRecord audioRecord = null;  // 声明 AudioRecord 对象
private int recordBufSize = 0; // 声明recoordBufffer的大小字段

recordBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, EncodingBitRate);  //audioRecord能接受的最小的buffer大小
  audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recordBufSize); //创建audiorecord对象
  
byte data[] = new byte[recordBufSize];      //初始化一个buffer

audioRecord.startRecording(); //开始录音

//创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流。
FileOutputStream os = null;

try {
   os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
   e.printStackTrace();
}
if (null != os) {    
	while (isRecording) {        
		read = audioRecord.read(data, 0, recordBufSize); // 如果读取音频数据没有出现错误,就将数据写入到文件   
		if (AudioRecord.ERROR_INVALID_OPERATION != read) {            
		try {                
		  os.write(data);            
		} catch (IOException e) {               
		   e.printStackTrace();          
		}        
	}   
}  
	try {        
	  os.close();    
	} catch (IOException e) {      
	  e.printStackTrace();    
	}
}


isRecording = false; //关闭数据流

if (null != audioRecord) { //停止录制
  audioRecord.stop();
  audioRecord.release();
  audioRecord = null;
  recordingThread = null;
}
 
 
    // 音频源:音频输入-麦克风
    private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
 
    // 采样率
    // 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
    private final static int AUDIO_SAMPLE_RATE = 16000;
       private  final  static  int  AUDIO_SAMPLE_RATE  =  44100; ////所有android系统都支持
       
    // 音频通道 单声道
    private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
 
    // 音频格式:PCM编码
    private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    
 //获取缓冲buffer,构建AudioRecord对象对象; 
 BufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_HERTZ,
                    CHANNEL_CONFIG, AUDIO_FORMAT);
  mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    SAMPLE_RATE_HERTZ, CHANNEL_CONFIG, AUDIO_FORMAT, BufferSize);
//配置AudioRecord
            int audioSource = MediaRecorder.AudioSource.MIC;
            //所有android系统都支持
            int sampleRate = 44100;
            //单声道输入
            int channelConfig = AudioFormat.CHANNEL_IN_MONO;
            //PCM_16是所有android系统都支持的
            int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
            //计算AudioRecord内部buffer最小
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, autioFormat);
            //buffer不能小于最低要求,也不能小于我们每次我们读取的大小。
            mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, autioFormat, Math.max(minBufferSize, BUFFER_SIZE));
            //开始录音
            mAudioRecord.startRecording();
            //循环读取数据,写入输出流中
            while (mIsRecording) {
                //只要还在录音就一直读取
                int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE);
                if(read<=0){
                    return false;
                }else {
                    mFileOutputStream.write(mBuffer, 0, read);
                }
            }
            //退出循环,停止录音,释放资源
            stopRecorder();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (mAudioRecord != null) {
                mAudioRecord.release();
            }
        }
        return true;
    }
转wav的工具文件
public class Pcm2WavUtil {
 
 
    /**
     * pcm文件转wav文件
     *
     * @param inFilename 源文件路径
     * @param outFilename 目标文件路径
     */
    public static void pcmToWav(long mSampleRate, int mChannel, int mBufferSize, String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
 
            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    /**
     * 加入wav文件头
     */
    private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
 
}


现有文章:

AudioRecord API详解

Android 录音实现(MediaRecorder)

Android 录音实现(AudioRecord)

评论