使用AudioRecord和AudioTrack来录制和播放音频

简介: 使用AudioRecord和AudioTrack来录制和播放音频

1.使用AudioRecord录制原始音频

除了通过意图启动录音机和使用MediaRecorder之外,Android还提供了第三种方法来捕获音频:使用成为AudioRecord的类。AudioRecord是三种方法里最灵活的(因为允许访问原始音频流),但是它拥有的内置功能也是最少的,如不会自动压缩音频。


使用AudioRecord的基础知识非常简单。我们只需要构造一个AudioRecord类型的对象,并传入各种不同的配置参数。


需要指定的第一个值是音频源。下面使用的值与用于MediaRecorder的值相同,其在MediaRecorder.AudioSource中定义。实际上,这意味着可以使用MediaRecorder.AudioSource.MIC。

int audioSource=MediaRecorder.AudioSource.MIC;

需要指定的第二个值是 录制的采样率。以赫兹(Hz)为单位指定它。MediaRecorder的采样音频是8kHz,而CD质量的音频通常是44.1Hz。不同的安卓手机硬件能够以不同的采样率进行采样。而本示例则以11025Hz的采样率进行采样,这是另外一个常用的采样率。

int frequency = 11025;

接下来,需要指定的第三个值是 捕获的音频通道的数量。在AudioFormat类中指定了用于此参数的常量,而且可根据名称理解它们。

 AudioFormat.CHANNEL_CONFIGURATION_MONO;//channelConfiguration
 AudioFormat.CHANNEL_CONFIGURATION_STEREO;
 AudioFormat.CHANNEL_CONFIGURATION_INVALID;
 AudioFormat.CHANNEL_CONFIGURATION_DEFAULT;

PCM代表脉冲编码调制,它实际上是原始的音频样本。16位将占用更多的空间和处理能力,但是表示的音频将更接近真实。

最后,需要指定的是第五个值是缓冲区大小。实际上可以查询AudioRecord类以获得最小缓冲区大小,查询方式是调用getMinBufferSize静态方法,同时传入采样率、通道配置以及音频格式。

int bufferSize = AudioTrack.getMinBufferSize(frequency,channelConfiguration, audioEncoding);

完成了以上步骤,我们就可以构造实际的AudioRecord对象。

AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
            channelConfiguration, audioEncoding, bufferSize);

但值得注意的是,AudioRecord类实际上 并不保存捕获的音频,因此需要手动保存捕获的音频。可以首先利用以下方法来创建一个文件。

File path = new File(
  Environment.getExternalStorageDirectory().getAbsolutePath()
    + "/AudioRecorder/files/");
path.mkdirs();
try
{
  recordingFile = File.createTempFile("recording", ".pcm", path);
} catch (IOException e)
{
  throw new RuntimeException("Couldn't create file on SD card", e);
}

接下来再创建该文件对应的OutputStream输出流,尤其是出于 性能和便利的原因,可以将它包装在BufferedOutputStream和DataOutputStream中。

DataOutputStream dos = new DataOutputStream(
  new BufferedOutputStream(new FileOutputStream(
    recordingFile)));

现在就可以启动捕获,同时将音频样本写入到文件中。可以使用short数组来保存从AudioRecord对象读取而来的音频。同时, 将采用比AudioRecord对象的缓冲区更小的数组,从而在确保将音频读出来之前缓冲区没有被填满。

为了确保此数组小于缓冲区大小,需要将缓冲区大小除以4.因为缓冲区的大小以字节为单位,而每个short类型的数据占用2个字节,所以除以2并不足够。所以除以4就刚好使得该数组是AudioRecord对象内部缓冲区大小的一半了。

short[] audiodata = new short[bufferSize / 4];

至此,只需要调用AudioRecord对象上的startRecording方法即可。

录制开始之后,可以构造一个循环,不断从AudioRecorder对象读取音频并放入short数组中,同时写入对应文件的DataOutputStream

while (isRecording)
{
  int bufferReadResult = audioRecord.read(buffer, 0,bufferSize);
  for (int i = 0; i < bufferReadResult; i++)
  {
    dos.writeShort(buffer[i]);
  }
}

当想结束录音的时候,调用AudioRecord对象上的stop方法和DataOutputStream上的close方法。


2.使用AudioTrack来播放原始音频

AudioTrack允许播放AudioRecord捕获的原始音频类,而它们并不能使用MediaPlayer对象播放。


为了构造一个AudioTrack对象,需要传入以下一系列配置变量来描述待播放的音频


第一个参数是流类型。可能的值定义为AudioManager类的常量。例如

AudioManager.STREAM_MUSIC//正常播放音乐的音频流

第二个参数是播放音频数据的采样率,这里的参数需要和AudioRecord指定的参数一致。

第三个参数是通道配置。可能的值与构造AudioRecord的值相同。


第四个参数是音频格式。可能的值与构造AudioRecord的值相同。


第五个参数是将在对象中用于存储音频的缓冲区大小。为了确定使用最小的缓冲区大小,可以调用getMinBufferSize方法,同时传入采样率、通道配置和音频格式。


最后一个参数是模式。可能的值定义为AudioTrack类中的常量。

AudioTrack.MODE_STATIC://在播放发生之前将所有的音频数据转移到AudioTrack对象
AudioTrack.MODE_STREAM://在播放的同时将音频数据持续地转移到AudioTrack对象。
AudioTrack audioTrack = new AudioTrack(
  AudioManager.STREAM_MUSIC, frequency,
  channelConfiguration, audioEncoding, bufferSize,
  AudioTrack.MODE_STREAM);


构造了AudioTrack对象之后,就需要打开音频源,将音频数据读取到缓冲区中,并将它传递给AudioTrack对象。我们可以根据一个包含了正确格式的原始PCM数据的文件,构造DataInputStream。

DataInputStream dis = new DataInputStream(
  new BufferedInputStream(new FileInputStream(
    recordingFile)));

然后调用AudioTrack对象上的play方法,并开始从DataInputStream读取音频。

audioTrack.play();
while (isPlaying && dis.available() > 0)
{
  int i = 0;
  while (dis.available() > 0 && i < audiodata.length)
  {
    audiodata[i] = dis.readShort();
    i++;
  }
  audioTrack.write(audiodata, 0, audiodata.length);
}
dis.close();

下面是一个完整的示例,通过使用异步任务,每个操作都在它们各自的线程中完成,所以并不会阻碍UI线程。

public class AltAudioRecorder extends Activity implements OnClickListener
{
  RecordAudio recordTask;
  PlayAudio playTask;
  Button startRecordingButton, stopRecordingButton, startPlaybackButton,
      stopPlaybackButton;
  TextView statusText;
  File recordingFile;
  boolean isRecording = false;
  boolean isPlaying = false;
  int frequency = 11025;
  int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
  int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    statusText = (TextView) this.findViewById(R.id.StatusTextView);
    startRecordingButton = (Button) this
        .findViewById(R.id.StartRecordingButton);
    stopRecordingButton = (Button) this
        .findViewById(R.id.StopRecordingButton);
    startPlaybackButton = (Button) this
        .findViewById(R.id.StartPlaybackButton);
    stopPlaybackButton = (Button) this
        .findViewById(R.id.StopPlaybackButton);
    startRecordingButton.setOnClickListener(this);
    stopRecordingButton.setOnClickListener(this);
    startPlaybackButton.setOnClickListener(this);
    stopPlaybackButton.setOnClickListener(this);
    stopRecordingButton.setEnabled(false);
    startPlaybackButton.setEnabled(false);
    stopPlaybackButton.setEnabled(false);
    File path = new File(
        Environment.getExternalStorageDirectory().getAbsolutePath()
            + "/Android/data/AudioRecorder/files/");
    path.mkdirs();
    try
    {
      recordingFile = File.createTempFile("recording", ".pcm", path);
    } catch (IOException e)
    {
      throw new RuntimeException("Couldn't create file on SD card", e);
    }
  }
  public void onClick(View v)
  {
    if (v == startRecordingButton)
    {
      record();
    } else if (v == stopRecordingButton)
    {
      stopRecording();
    } else if (v == startPlaybackButton)
    {
      play();
    } else if (v == stopPlaybackButton)
    {
      stopPlaying();
    }
  }
  public void play()
  {
    startPlaybackButton.setEnabled(true);
    playTask = new PlayAudio();
    playTask.execute();
    stopPlaybackButton.setEnabled(true);
  }
  public void stopPlaying()
  {
    isPlaying = false;
    stopPlaybackButton.setEnabled(false);
    startPlaybackButton.setEnabled(true);
  }
  public void record()
  {
    startRecordingButton.setEnabled(false);
    stopRecordingButton.setEnabled(true);
    // For Fun
    startPlaybackButton.setEnabled(true);
    recordTask = new RecordAudio();
    recordTask.execute();
  }
  public void stopRecording()
  {
    isRecording = false;
  }
  private class PlayAudio extends AsyncTask<Void, Integer, Void>
  {
    @Override
    protected Void doInBackground(Void... params)
    {
      isPlaying = true;
      int bufferSize = AudioTrack.getMinBufferSize(frequency,
          channelConfiguration, audioEncoding);
      short[] audiodata = new short[bufferSize / 4];
      try
      {
        DataInputStream dis = new DataInputStream(
            new BufferedInputStream(new FileInputStream(
                recordingFile)));
        AudioTrack audioTrack = new AudioTrack(
            AudioManager.STREAM_MUSIC, frequency,
            channelConfiguration, audioEncoding, bufferSize,
            AudioTrack.MODE_STREAM);
        audioTrack.play();
        while (isPlaying && dis.available() > 0)
        {
          int i = 0;
          while (dis.available() > 0 && i < audiodata.length)
          {
            audiodata[i] = dis.readShort();
            i++;
          }
          audioTrack.write(audiodata, 0, audiodata.length);
        }
        dis.close();
        startPlaybackButton.setEnabled(false);
        stopPlaybackButton.setEnabled(true);
      } catch (Throwable t)
      {
        Log.e("AudioTrack", "Playback Failed");
      }
      return null;
    }
  }
  private class RecordAudio extends AsyncTask<Void, Integer, Void>
  {
    @Override
    protected Void doInBackground(Void... params)
    {
      isRecording = true;
      try
      {
        DataOutputStream dos = new DataOutputStream(
            new BufferedOutputStream(new FileOutputStream(
                recordingFile)));
        int bufferSize = AudioRecord.getMinBufferSize(frequency,
            channelConfiguration, audioEncoding);
        AudioRecord audioRecord = new AudioRecord(
            MediaRecorder.AudioSource.MIC, frequency,
            channelConfiguration, audioEncoding, bufferSize);
        short[] buffer = new short[bufferSize];
        audioRecord.startRecording();
        int r = 0;
        while (isRecording)
        {
          int bufferReadResult = audioRecord.read(buffer, 0,
              bufferSize);
          for (int i = 0; i < bufferReadResult; i++)
          {
            dos.writeShort(buffer[i]);
          }
          publishProgress(new Integer(r));
          r++;
        }
        audioRecord.stop();
        dos.close();
      } catch (Throwable t)
      {
        Log.e("AudioRecord", "Recording Failed");
      }
      return null;
    }
    protected void onProgressUpdate(Integer... progress)
    {
      statusText.setText(progress[0].toString());
    }
    protected void onPostExecute(Void result)
    {
      startRecordingButton.setEnabled(true);
      stopRecordingButton.setEnabled(false);
      startPlaybackButton.setEnabled(true);
    }
  }
}
目录
相关文章
使用SoundPool播放音频文件,使用简单
使用SoundPool播放音频文件,使用简单
112 0
|
编解码 Shell API
MediaPlayer音频与视频的播放介绍
Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码和播放音视频。 它支持三种不同的媒体来源: 本地资源 内部的URI,比如你可以通过ContentResolver来获取 外部URL(流)对于Android所支持的的媒体格式列表 1.相关方法详解 1)获得MediaPlayer实例: 可以直接new或者调用create方法创建: MediaPlayer mp = new MediaPlayer(); MediaPlayer mp = MediaPlaye
157 0
88.播放声音和音效
88.播放声音和音效
260 0
88.播放声音和音效
|
Windows C++
VC++中播放声音wav
因为只需在Windows上执行,先想到用MCI接口。试了一下,用mciSendCommand可以实现基本的播放wav文件的功能。但循环播放wav就麻烦了,必须向窗口传送MM_MCINOTIFY消息。
1898 0
【播放器--场景】纯音频播放
演示播放器播放纯音频mp3文件
4048 0
|
Android开发 缓存
Android MediaRecorder与AudioRecord音频录制
MediaRecorderAudioRecord MediaRecorder 需要的权限 /** * 开始录音 */ class startRecordListener implements OnClickList...
1426 0