Android音频开发之使用AudioRecord录制
2017-01-02 10:14
453 查看
本文主要是记录Android端音频开发
本例记录使用AudioRecord 录制音频,播放使用AudioTrack,存储的文件为pcm
只是简单的测试用例,界面同上文
注意添加权限
--- 配置参数,初始化内部的音频缓冲区
--- 开始采集
--- 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。
--- 停止采集,释放资源
参数配置
该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。
--- sampleRateInHz
采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
--- channelConfig
通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)
--- audioFormat
这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。
--- bufferSizeInBytes
这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下:
int size = 采样率 x 位宽 x 采样时间 x 通道数
采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。
在Android开发中,AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:
强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。
当创建好了 AudioRecord 对象之后,就可以开始进行音频数据的采集了,通过下面两个函数控制采集的开始/停止:
AudioRecord.startRecording();
AudioRecord.stop();
调用的读取数据的接口是:
AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);
2 . AudioTrack 的工作流程:
--- 配置参数,初始化内部的音频播放缓冲区
--- 开始播放
--- 需要一个线程,不断地向 AudioTrack 的缓冲区“写入”音频数据
--- 停止播放,释放资源
参数设置
这个参数代表着当前应用使用的哪一种音频管理策略,当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果,该参数的可选的值以常量的形式定义在 AudioManager 类中,主要包括:
STREAM_VOCIE_CALL:电话声音
STREAM_SYSTEM:系统声音
STREAM_RING:铃声
STREAM_MUSCI:音乐声
STREAM_ALARM:警告声
STREAM_NOTIFICATION:通知声
--- sampleRateInHz
采样率,从AudioTrack源码的“audioParamCheck”函数可以看到,这个采样率的取值范围必须在 4000Hz~192000Hz 之间。
--- channelConfig
通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)
--- audioFormat
这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。
--- bufferSizeInBytes
配置的是 AudioTrack 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下:
int size = 采样率 x 位宽 x 采样时间 x 通道数
AudioTrack 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:
--- mode
AudioTrack 提供了两种播放模式,一种是 static 方式,一种是 streaming 方式,前者需要一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段; 后者则是按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景。
在 AudioTrack 类中,一个是 MODE_STATIC,另一个是 MODE_STREAM
本例记录使用AudioRecord 录制音频,播放使用AudioTrack,存储的文件为pcm
只是简单的测试用例,界面同上文
注意添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>1 . AudioRecord 的工作流程:
--- 配置参数,初始化内部的音频缓冲区
--- 开始采集
--- 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。
--- 停止采集,释放资源
参数配置
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) throws IllegalArgumentException--- audioSource
该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。
--- sampleRateInHz
采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
--- channelConfig
通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)
--- audioFormat
这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。
--- bufferSizeInBytes
这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下:
int size = 采样率 x 位宽 x 采样时间 x 通道数
采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。
在Android开发中,AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:
int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);
强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。
当创建好了 AudioRecord 对象之后,就可以开始进行音频数据的采集了,通过下面两个函数控制采集的开始/停止:
AudioRecord.startRecording();
AudioRecord.stop();
调用的读取数据的接口是:
AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);
2 . AudioTrack 的工作流程:
--- 配置参数,初始化内部的音频播放缓冲区
--- 开始播放
--- 需要一个线程,不断地向 AudioTrack 的缓冲区“写入”音频数据
--- 停止播放,释放资源
参数设置
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException--- streamType
这个参数代表着当前应用使用的哪一种音频管理策略,当系统有多个进程需要播放音频时,这个管理策略会决定最终的展现效果,该参数的可选的值以常量的形式定义在 AudioManager 类中,主要包括:
STREAM_VOCIE_CALL:电话声音
STREAM_SYSTEM:系统声音
STREAM_RING:铃声
STREAM_MUSCI:音乐声
STREAM_ALARM:警告声
STREAM_NOTIFICATION:通知声
--- sampleRateInHz
采样率,从AudioTrack源码的“audioParamCheck”函数可以看到,这个采样率的取值范围必须在 4000Hz~192000Hz 之间。
--- channelConfig
通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)
--- audioFormat
这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。
--- bufferSizeInBytes
配置的是 AudioTrack 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,一帧音频帧的大小计算如下:
int size = 采样率 x 位宽 x 采样时间 x 通道数
AudioTrack 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:
int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);
--- mode
AudioTrack 提供了两种播放模式,一种是 static 方式,一种是 streaming 方式,前者需要一次性将所有的数据都写入播放缓冲区,简单高效,通常用于播放铃声、系统提醒的音频片段; 后者则是按照一定的时间间隔不间断地写入音频数据,理论上它可用于任何音频播放的场景。
在 AudioTrack 类中,一个是 MODE_STATIC,另一个是 MODE_STREAM
import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaRecorder; import android.os.AsyncTask; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.cl.slack.mediarecorder.R; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; public class AudioRecorderActivity extends AppCompatActivity implements View.OnClickListener { private final int REQ_PERMISSION_AUDIO = 0x01; private Button mRecode, mPlay; private File mAudioFile = null; private Thread mCaptureThread = null; private boolean mIsRecording,mIsPlaying; private int mFrequence = 44100; private int mChannelConfig = AudioFormat.CHANNEL_IN_MONO; private int mPlayChannelConfig = AudioFormat.CHANNEL_IN_STEREO; private int mAudioEncoding = AudioFormat.ENCODING_PCM_16BIT; private PlayTask mPlayer; private RecordTask mRecorder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_media_recorder_audio); mRecode = (Button) findViewById(R.id.audio_recode); mPlay = (Button) findViewById(R.id.audio_paly); mRecode.setText("recode"); mPlay.setText("play"); mPlay.setEnabled(false); mRecode.setOnClickListener(this); mPlay.setOnClickListener(this); // mRecorder = new RecordTask(); // mPlayer = new PlayTask(); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.audio_recode: if (mRecode.getTag() == null) { startAudioRecode(); } else { stopAudioRecode(); } break; case R.id.audio_paly: if (mPlay.getTag() == null) { startAudioPlay(); } else { stopAudioPlay(); } break; } } private void startAudioRecode() { if (checkPermission()) { PackageManager packageManager = this.getPackageManager(); if (!packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) { showToast("This device doesn't have a mic!"); return; } mRecode.setTag(this); mRecode.setText("stop"); mPlay.setEnabled(false); File fpath = new File(Environment.getExternalStorageDirectory() .getAbsolutePath() + "/slack"); fpath.mkdirs();// 创建文件夹 try { // 创建临时文件,注意这里的格式为.pcm mAudioFile = File.createTempFile("recording", ".pcm", fpath); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } mRecorder = new RecordTask(); mRecorder.execute(); showToast("Recording started"); } else { requestPermission(); } } private void stopAudioRecode() { mIsRecording = false; mRecode.setTag(null); mRecode.setText("recode"); mPlay.setEnabled(true); showToast("Recording Completed"); } private void startAudioPlay() { mPlay.setTag(this); mPlay.setText("stop"); mPlayer = new PlayTask(); mPlayer.execute(); showToast("Recording Playing"); } private void stopAudioPlay() { mIsPlaying = false; mPlay.setTag(null); mPlay.setText("play"); } private boolean checkPermission() { int result = ContextCompat.checkSelfPermission(getApplicationContext(), WRITE_EXTERNAL_STORAGE); int result1 = ContextCompat.checkSelfPermission(getApplicationContext(), RECORD_AUDIO); return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED; } private void requestPermission() { ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE, RECORD_AUDIO}, REQ_PERMISSION_AUDIO); } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case REQ_PERMISSION_AUDIO: if (grantResults.length > 0) { boolean StoragePermission = grantResults[0] == PackageManager.PERMISSION_GRANTED; boolean RecordPermission = grantResults[1] == PackageManager.PERMISSION_GRANTED; if (StoragePermission && RecordPermission) { showToast("Permission Granted"); } else { showToast("Permission Denied"); } } break; } } private void showToast(String message) { Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } class RecordTask extends AsyncTask<Void,Integer,Void> { @Override protected Void doInBackground(Void... arg0) { mIsRecording = true; try { // 开通输出流到指定的文件 DataOutputStream dos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream(mAudioFile))); // 根据定义好的几个配置,来获取合适的缓冲大小 int bufferSize = AudioRecord.getMinBufferSize(mFrequence, mChannelConfig, mAudioEncoding); // 实例化AudioRecord AudioRecord record = new AudioRecord( MediaRecorder.AudioSource.MIC, mFrequence, mChannelConfig, mAudioEncoding, bufferSize); // 定义缓冲 short[] buffer = new short[bufferSize]; // 开始录制 record.startRecording(); int r = 0; // 存储录制进度 // 定义循环,根据isRecording的值来判断是否继续录制 while (mIsRecording) { // 从bufferSize中读取字节,返回读取的short个数 int bufferReadResult = record .read(buffer, 0, buffer.length); // 循环将buffer中的音频数据写入到OutputStream中 for (int i = 0; i < bufferReadResult; i++) { dos.writeShort(buffer[i]); } publishProgress(new Integer(r)); // 向UI线程报告当前进度 r++; // 自增进度值 } // 录制结束 record.stop(); Log.i("slack", "::" + mAudioFile.length()); dos.close(); } catch (Exception e) { // TODO: handle exception Log.e("slack", "::" + e.getMessage()); } return null; } // 当在上面方法中调用publishProgress时,该方法触发,该方法在UI线程中被执行 protected void onProgressUpdate(Integer... progress) { // } protected void onPostExecute(Void result) { } } /** * AudioTrack */ class PlayTask extends AsyncTask<Void,Void,Void> { @Override protected Void doInBackground(Void... arg0) { mIsPlaying = true; int bufferSize = AudioTrack.getMinBufferSize(mFrequence, mPlayChannelConfig, mAudioEncoding); short[] buffer = new short[bufferSize ]; try { // 定义输入流,将音频写入到AudioTrack类中,实现播放 DataInputStream dis = new DataInputStream( new BufferedInputStream(new FileInputStream(mAudioFile))); // 实例AudioTrack AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, mFrequence, mPlayChannelConfig, mAudioEncoding, bufferSize, AudioTrack.MODE_STREAM); // 开始播放 track.play(); // 由于AudioTrack播放的是流,所以,我们需要一边播放一边读取 while (mIsPlaying && dis.available() > 0) { int i = 0; while (dis.available() > 0 && i < buffer.length) { buffer[i] = dis.readShort(); i++; } // 然后将数据写入到AudioTrack中 track.write(buffer, 0, buffer.length); } // 播放结束 track.stop(); dis.close(); } catch (Exception e) { // TODO: handle exception Log.e("slack","error:" + e.getMessage()); } return null; } protected void onPostExecute(Void result) { } protected void onPreExecute() { } } }
相关文章推荐
- Android Native C++ 层中使用AudioRecord录制PCM音频
- Android项目开发五-《星星生活志》1.使用MediaRecorder录制音频
- Android音频开发之使用MediaRecorder录制
- Android项目开发五-《星星生活志》1.使用MediaRecorder录制音频
- Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件
- Android使用AudioRecord录制pcm音频原始数据以及使用AudioTrack播放
- Android音频录制--AudioRecord
- Android下音频录制以及网络传输的思路及开发方法
- Android开发 使用Lame把音频文件转换成mp3格式
- 【Android开发小记--13】录制音频
- 木瓜妮子多媒体开发教程---第五天---Android下音频录制和播放
- Android使用的开发MediaRecorder录制声音
- Android开发 使用Lame把音频文件转换成mp3格式
- 初学Android,多媒体之使用MediaRecorder录制音频(七十八)
- Android开发之使用MediaRecorder录制视频
- Android音频开发(6):使用 OpenSL ES API(上)
- 使用AudioRecord录制音频
- 26、从头学Android之多媒体--使用MediaRecorder录制音频
- android录音开发问题<记录1>:AudioRecord录制的音频文件如何用MediaPlayer类读取
- Android游戏开发8:使用MediaPlayer类和SoundPool类播放音频