支持的音频格式
AAC:高级音频编码(Advanced Audio Coding)编解码器(以及HEAAC(高效AAC)的两个配置文件),对应的是.m4a(audio/m4a)或.3gp(audio/3gpp)文件。AAC是一种流行的标准。Android支持这种在MPEG-4音频文件(MPEG-4就是.mp4文件)以及3GP文件(3GP就是.3gp文件,3GP基于MPEG-4格式)内部的音频格式,同时还支持新添加到AAC规范中的高效AAC(HEAAC)格式。
MP3:MPEG-1 Audio Layer 3(MPEG-1 音频层3),对应的是.mp3(audio/mp3)文件。Android支持MP3,这可能是使用最广泛的音频编解码器。允许Android利用在网上通过各种网站和音乐商店提供的大多数音频。
AMR:自适应多速率(Adaptive Multi-Rate)编解码器(包括AMR窄带AMR-NB和AMR宽带AMR-WB),对应的是.3gp(audio/3gpp)或.amr(audio/amr)文件。AMR是由3GPP(3rd Generation Partnership Project,第三代合作伙伴项目)使用的基本语音音频编解码器标准。AMR编解码器主要用于现代手机上的语音呼叫应用程序,对于语音编解码通常是有用的,但不利于处理更复杂类型的音频,如音乐。
Ogg:Ogg Vorbis,对应的是.ogg(application/ogg)文件。Ogg Vorbis是一种开放源代码的、无专利权的音频编解码器。
PCM:脉冲编码调制(Pulse Code Modulation)通常用于WAVE或WAV文件(Waveform Audio Format,波形音频格式),对应的是.wav(audio/x-wav)文件。PCM是用于计算机和其他数字音频设备上存储音频的技术。它通常是一个未压缩的音频文件,其数据表示一段音频随事件而变化的振幅。“采样率”表示存储振幅读数的频率。“位深“是表示一个单独采样所需的位数。采样率为16kHz、位深为32位的一段音频数据意味着它将以32位数据表示音频振幅,而且每秒钟包含16000个这样的数据。采样率和位深越大,则音频的数字化越准确。采样率和位深还决定了当给定长度时音频文件的大小。Android支持WAV文件中的PCM音频数据。WAV是PC上一个长期使用的标准音频格式。
总结:
AAC、MP3、AMR、Ogg这些都是表示的音频编解码器,相当于音频的编解码算法;针对不同的编解码算法可以输出不同的音频格式文件。
AAC编解码器输出.m4a或.3gp文件;MP3编解码器输出.mp3文件;AMR编解码器输出.3gp或.amr文件;PCM输出.wav文件。
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI // 存储音频文件的标准音频外部存储uri
音频常用相关列:
MediaStore.Audio.Media.DATA // 音频文件路径
MediaStore.Audio.Media._ID // 音频文件id
MediaStore.Audio.Media.TITLE // 音频文件标题
MediaStore.Audio.Media.DISPLAY_NAME // 音频文件名称
MediaStore.Audio.Media.MIME_TYPE // 音频文件格式类型,如audio/mp3等
MediaStore.Audio.Media.ARTIST // 艺人名称
MediaStore.Audio.Media.ALBUM // 艺人专辑
MediaStore.Audio.Media.IS_RINGTONE // 是否为警报音频文件类型
MediaStore.Audio.Media.IS_ALARM // 是否为闹铃音频文件类型
MediaStore.Audio.Media.IS_MUSIC // 是否为音乐音频文件类型
MediaStore.Audio.Media.IS_NOTIFICATION // 是否为通知音频文件类型
音频文件(特别是音乐文件)可以按照唱片集、艺术家和流派(Genre)来查找,也可以直接在MediaStore中查找。
唱片集:MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI(列:MediaStore.Audio.AlbumColumns.xxx)
艺术家:MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI(列:MediaStore.Audio.ArtistColumns.xxx)
流派:MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI(列:MediaStore.Audio.GenresColumns.xxx)
MediaStore.Audio.Albums、MediaStore.Audio.Artists、MediaStore.Audio.Genres三个类都实现了BaseColumns,以及各自实现对应的列接口AlbumColumns、ArtistsColumns、GenresColumns。
使用MediaRecorder捕获音频步骤如下(每步调用的执行顺序不同对结果影响非常大):
创建MediaRecorder实例
设置音频源(MediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC))
设置输出格式(MediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4))
设置音频编码器(MediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB))
设置输出文件(MediaRecorder.setOutputFile(filePath))
准备录制(MediaRecorder.prepare())
开始录制(MediaRecorder.start())
重置/停止(MediaRecorder.reset()/MediaRecorder.stop())
释放资源(MediaRecorder.release())
音频源:
MediaRecorder.AudioSource.MIC:是表示通过麦克风接收捕获音频。
MediaRecorder.AudioSource.VOICE_CALL、VOICE_DOWNLINK、VOICE_UPLINK:但是似乎没有任何Android手机或版本能够真正从电话中录制音频。
MediaRecorder.AudioSource.CAMCORDER、VOICE_RECOGNITION:如果设备有一个以上的麦克风,就可以使用它们。
输出格式:
MediaRecorder.OutputFormat.MPEG-4:这个常量指定输出的文件将是一个MPEG-4文件。它可能同时包含音频和视频轨。
MediaRecorder.OutputFormat.RAW_AMR:这个常量表示输出一个没有任何容器类型的原始文件。只有在捕获没有视频的音频且音频编码器是AMR_NB时才会使用这个常量。
MediaRecorder.OutputFormat.THREE_GPP:这个常量指定输出的文件将是一个3GPP(扩展名为.3gp)。它可能同时包含音频和视频轨。
音频编解码器:
MediaRecorder.AudioEncoder.DEFAULT:使用默认的音频编解码器。
MediaRecorder.AudioEncoder.AMR_NB:这是自适应多速率窄带编解码器。这种编解码器针对语音进行了优化,因此其不适用于语音之外的其他内容。默认情况下它的采样率为8kHz,码率在4.75~12.2kbps之间,这两个数据对于录制除语音之外的其他内容而言非常低。
输出和录制
getMaxAmplitude:允许请求由MediaPlayer录制的音频的最大振幅。每次调用此方法时都会重置该值,因此每次调用都将返回自从上一次调用以来的最大振幅。可通过定期调用该方法实现音量表。
setMaxDuration:允许以毫秒为单位指定最大录制持续事件。必须在setOutputFormat方法之后和prepare方法之前调用该方法。
setMaxFileSize:允许以字节为单位指定录制的最大文件大小。与setMaxDuration一样,必须在setOutputFormat方法之后和prepare方法之前调用该方法。
setAudioChannels:允许指定将录制的音频通道数。通常是一个通道(单声道)或两个通道(双声道)。必须在prepare方法之前调用该方法。
setAudioEncodingBitRate:允许指定当压缩音频时编码器所使用的每秒位数(bit/s)。必须在prepare方法之前调用该方法。
setAudioSamplingRate:允许指定捕获和编码的音频的采样率。硬件和使用的编解码器将会决定合适的采样率。必须在prepare方法之前调用该方法。
使用AudioRecorder捕获音频步骤如下:
创建AudioRecorder对象实例时指定音频源、录制的采样率、捕获音频通道的数量、音频格式和缓冲区大小
录制音频文件输出
开始录制(AudioRecorder.startRecording())
停止录制(AudioRecorder.stop())
音频源:
int audioSource = MediaRecorder.AudioSource.MIC;
录制的采样率:
录制的采样率应以Hz为单位指定。MediaRecorder采样的音频是8kHz或8000Hz,而CD质量的音频通常是44.1kHz或44100Hz。Hz是每秒的样本数量。不同的Android手机硬件将能够以不同的采样率进行采样。一个常用的采样率是11025Hz。
int sampleRateInHz = 11025;
获音频通道的数量:
AudioFormat.CHANNEL_CONFIGURATION_MONO:单声道
AudioFormat.CHANNEL_CONFIGURATION_STEREO:立体声道
AudioFormat.CHANNEL_CONFIGURATION_INVALID:不设置声道
AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:默认声道
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
音频格式:
AudioFormat.ENCODING_DEFAULT:默认音频格式
AudioFormat.ENCODING_INVALID:不设置音频格式
AudioFormat.ENCODING_PCM_16BIT:脉冲编码调制16位音频格式
AudioFormat.ENCODING_PCM_8BIT:脉冲编码调制8位音频格式
PCM实际上是原始的音频样本。可以设置每个样本的分辨率为16位或8位。16位将占用更多的空间和处理能力,但表示的音频更接近真实。
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
缓冲区大小:
实际上可以查询AudioRecorder类以获得最小的缓冲区大小,查询方式是调用getMinBufferSize静态方法,同时传入采样率、通道配置以及音频格式。
int bufferSizeInBytes = AudioRecorder.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
录制音频文件输出:
AudioRecorder类实际上不保存捕获的音频,因此需要手动保存捕获的音频。将音频录制到一个文件中。
开始录制
结束录制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_start_record_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始录制音频" />
<Button
android:id="@+id/btn_stop_record_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止录制音频" />
<Button
android:id="@+id/btn_play_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="播放录制音频" />
<Button
android:id="@+id/btn_stop_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止播放录制视频" />
<TextView
android:id="@+id/tv_max_amplitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp" />
</LinearLayout>
package com.example.media.audio;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.media.R;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MediaRecorderActivity extends AppCompatActivity implements MediaPlayer.OnCompletionListener {
private static final String TAG = MediaRecorderActivity.class.getSimpleName();
private Button mBtnStartRecordAudio;
private Button mBtnStopRecordAudio;
private Button mBtnStartPlayAudio;
private Button mBtnStopPlayAudio;
private MediaRecorder mMediaRecorder;
private MediaPlayer mMediaPlayer;
private String mAudioFilePath;
private TextView mTvMaxAmplitude; // 显示最大振幅
private RecordAmplitude mRecordAmplitude; // 异步显示最大振幅类
private boolean mIsRecording; // 控制振幅展示标志
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_recorder);
mBtnStartRecordAudio = findViewById(R.id.btn_start_record_audio);
mBtnStopRecordAudio = findViewById(R.id.btn_stop_record_audio);
mBtnStartPlayAudio = findViewById(R.id.btn_play_audio);
mBtnStopPlayAudio = findViewById(R.id.btn_stop_audio);
mTvMaxAmplitude = findViewById(R.id.tv_max_amplitude);
mTvMaxAmplitude.setText("0");
mBtnStartRecordAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MediaRecorderActivity.this, "开始录制音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(false);
mBtnStopRecordAudio.setEnabled(true);
// 在录制音频时显示振幅
mIsRecording = true;
mRecordAmplitude = new RecordAmplitude();
mRecordAmplitude.execute();
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
String outputFile = getOutputFile();
if (outputFile != null) {
mAudioFilePath = outputFile;
mMediaRecorder.setOutputFile(outputFile);
}
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mMediaRecorder.start();
}
});
mBtnStopRecordAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MediaRecorderActivity.this, "停止录制音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
// 停止振幅展示
mIsRecording = false;
mRecordAmplitude.cancel(true);
mTvMaxAmplitude.setText("0");
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
// 将录制的音频插入到数据库
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Audio.Media.TITLE, "this is my record audio");
contentValues.put(MediaStore.Audio.Media.DATA, mAudioFilePath);
contentValues.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis());
Uri recordAudioUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues);
Log.v(TAG, "record audio uri = " + recordAudioUri);
}
});
mBtnStartPlayAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MediaRecorderActivity.this, "开始播放音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(false);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(false);
mBtnStopPlayAudio.setEnabled(true);
if (mAudioFilePath != null) {
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(mAudioFilePath);
mMediaPlayer.setOnCompletionListener(MediaRecorderActivity.this);
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
mBtnStopPlayAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MediaRecorderActivity.this, "停止播放音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
mBtnStopPlayAudio.setEnabled(false);
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
});
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(false);
mBtnStopPlayAudio.setEnabled(false);
}
private String getOutputFile() {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return null;
}
String outputFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +
"/Android/data/com.example.media/audio/";
File filePath = new File(outputFilePath);
if (!filePath.exists()) {
if (filePath.mkdirs()) {
Log.v(TAG, "audio file path create = " + filePath.getAbsolutePath());
}
}
try {
File tempAudioFile = File.createTempFile("audio" +
new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA).format(new Date()),
".3gp", filePath);
return tempAudioFile.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public void onCompletion(MediaPlayer mp) {
if (new File(mAudioFilePath).delete()) {
int isDeleted = getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
MediaStore.Audio.Media.DATA + "=?", new String[]{mAudioFilePath});
Log.v(TAG, "audio file delete = " + isDeleted); // 1表示删除 0表示未删除
}
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(false);
mBtnStopPlayAudio.setEnabled(false);
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
@SuppressLint("StaticFieldLeak")
private class RecordAmplitude extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... voids) {
while (mIsRecording) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(mMediaRecorder.getMaxAmplitude()); // 定期获取最大振幅
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
String amplitude = values[0].toString();
mTvMaxAmplitude.setText(amplitude);
}
}
}
AudioTrack是Android中的一个类,它允许播放原始音频样本。利用该类能够播放使用AudioRecorder捕获的音频。而它们并不能使用MediaPlayer对象来播放。
使用AudioTrack播放音频步骤如下:
创建AudioTrack对象实例时指定流类型、播放的音频数据采样率、通道配置、音频格式、存储音频的缓冲区大小、模式。
读取音频数据
播放音频(AudioTrack.play())
流类型:
可能的值定义为AudioManager类中的常量。AudioManager.STREAM_MUSIC,用于正常播放音乐的音频流。
音频数据采样率:
以Hz为单位,采用11025Hz的采样率来捕获音频,因此在播放时需要指定相同的值。
通道配置:
AudioFormat.CHANNEL_CONFIGURATION_MONO:单声道
AudioFormat.CHANNEL_CONFIGURATION_STEREO:立体声道
AudioFormat.CHANNEL_CONFIGURATION_INVALID:不设置声道
AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:默认声道
音频格式:
AudioFormat.ENCODING_DEFAULT:默认音频格式
AudioFormat.ENCODING_INVALID:不设置音频格式
AudioFormat.ENCODING_PCM_16BIT:脉冲编码调制16位音频格式
AudioFormat.ENCODING_PCM_8BIT:脉冲编码调制8位音频格式
存储音频的缓冲区大小:
为了确定最小缓冲区大小,可以调用getMinBufferSize方法,同时传入采样率、通道配置和音频格式。
int frequency = 11025;
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = AudioTrack.getMinBufferSize(frequency, channelConfig, audioEncoding);
模式:
AudioTrack.MODE_STATIC:在播放发生之前将所有的音频数据转移到AudioTrack对象。
AudioTrack.MODE_STREAM:在播放的同时将音频数据持续地转移到AudioTrack对象。
读取音频数据
播放音频
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_start_record_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始录制音频" />
<Button
android:id="@+id/btn_stop_record_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止录制音频" />
<Button
android:id="@+id/btn_play_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="播放录制音频" />
<Button
android:id="@+id/btn_stop_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止播放录制视频" />
<TextView
android:id="@+id/tv_audio_record_process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp" />
</LinearLayout>
package com.example.media.audio;
import android.annotation.SuppressLint;
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.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.media.R;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class AudioRecorderActivity extends AppCompatActivity {
private static final String TAG = AudioRecorderActivity.class.getSimpleName();
private Button mBtnStartRecordAudio;
private Button mBtnStopRecordAudio;
private Button mBtnStartPlayAudio;
private Button mBtnStopPlayAudio;
private String mAudioFilePath;
private boolean mIsRecording; // 控制录制音频标志
private AudioRecordTask mAudioRecordTask; // 异步录制音频任务
private boolean mIsPlaying; // 控制播放音频标志
private AudioPlayTask mAudioPlayTask; // 异步播放音频任务
private TextView mTvRecordProcess;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_recorder);
mTvRecordProcess = findViewById(R.id.tv_audio_record_process);
mBtnStartRecordAudio = findViewById(R.id.btn_start_record_audio);
mBtnStopRecordAudio = findViewById(R.id.btn_stop_record_audio);
mBtnStartPlayAudio = findViewById(R.id.btn_play_audio);
mBtnStopPlayAudio = findViewById(R.id.btn_stop_audio);
mBtnStartRecordAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AudioRecorderActivity.this, "开始录制音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(false);
mBtnStopRecordAudio.setEnabled(true);
mAudioRecordTask = new AudioRecordTask();
mAudioRecordTask.execute();
}
});
mBtnStopRecordAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AudioRecorderActivity.this, "停止录制音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
mIsRecording = false;
}
});
mBtnStartPlayAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AudioRecorderActivity.this, "开始播放音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(false);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(false);
mBtnStopPlayAudio.setEnabled(true);
mAudioPlayTask = new AudioPlayTask();
mAudioPlayTask.execute();
}
});
mBtnStopPlayAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AudioRecorderActivity.this, "停止播放音频", Toast.LENGTH_SHORT).show();
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
mBtnStopPlayAudio.setEnabled(false);
mIsPlaying = false;
if (new File(mAudioFilePath).delete()) {
Log.v(TAG, "audio file delete"); // 1表示删除 0表示未删除
}
}
});
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(false);
mBtnStopPlayAudio.setEnabled(false);
}
private boolean createOutputFile() {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
}
String outputFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +
"/Android/data/com.example.media/audio/";
File filePath = new File(outputFilePath);
if (!filePath.exists()) {
if (filePath.mkdirs()) {
Log.v(TAG, "audio file path create = " + filePath.getAbsolutePath());
}
}
try {
File tempAudioFile = File.createTempFile("audio" +
new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA).format(new Date()),
".pcm", filePath);
mAudioFilePath = tempAudioFile.getAbsolutePath();
return tempAudioFile.exists();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
// 异步录制原始音频
@SuppressLint("StaticFieldLeak")
private class AudioRecordTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... voids) {
mIsRecording = true;
int sampleRateInHz = 11025;
int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig,
audioFormat, bufferSizeInBytes);
if (createOutputFile()) {
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mAudioFilePath)));
short[] buffer = new short[bufferSizeInBytes];
audioRecord.startRecording();
int r = 0;
while (mIsRecording) {
int bufferReadResult = audioRecord.read(buffer, 0, bufferSizeInBytes);
for (int i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
publishProgress(r);
r++;
}
audioRecord.stop();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeSilently(dos);
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
String process = values[0].toString();
mTvRecordProcess.setText(process);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
}
}
// 异步播放原始音频
@SuppressLint("StaticFieldLeak")
private class AudioPlayTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... voids) {
mIsPlaying = true;
int sampleRateInHz = 11025;
int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
short[] buffer = new short[bufferSize / 4];
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(mAudioFilePath)));
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig,
audioFormat, bufferSize, AudioTrack.MODE_STREAM);
audioTrack.play();
while (mIsPlaying && dis.available() > 0) {
int i = 0;
while (dis.available() > 0 && i < buffer.length) {
buffer[i] = dis.readShort();
i++;
}
audioTrack.write(buffer, 0, buffer.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeSilently(dis);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
mBtnStartRecordAudio.setEnabled(true);
mBtnStopRecordAudio.setEnabled(false);
mBtnStartPlayAudio.setEnabled(true);
mBtnStopPlayAudio.setEnabled(false);
}
}
private void closeSilently(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}