我是靠谱客的博主 整齐奇异果,最近开发中收集的这篇文章主要介绍Android音视频技术2--Android AudioRecord 和 AudioTrack音频PCM数据采集和播放,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一.AudioRecord 和 AudioTrack

AudioRecord:Android平台用于录制音频的类。

AudioTrack:Android平台用于播放音频的类。

二.AudioRecord 和 AudioTrack构造方法

AudioRecord音频采集

Public constructors
<init>(audioSource: Int, sampleRateInHz: Int, channelConfig: Int, audioFormat: Int, bufferSizeInBytes: Int)
  1. audioSource:音频源
  2. sampleRateInHz:采样频率
  3. channelConfig:音频通道,单声道,双声道,立体声。
  4. audioFormat:音频数据源格式
  5. bufferSizeInBytes:音频数据缓存总大小

AudioTrack音频播放

@param streamType the type of the audio stream. See
*
{@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
*
{@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},
*
{@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}.
Public constructors
<init>(streamType: Int, sampleRateInHz: Int, channelConfig: Int, audioFormat: Int, bufferSizeInBytes: Int, mode: Int)
  1. streamType:音频流数据类型,类型如下:
  • STREAM_VOICE_CALL:电话
  • STREAM_SYSTEM:系统
  • STREAM_RING:铃声
  • STREAM_MUSIC:音乐
  • STREAM_ALARM:闹钟
  • STREAM_NOTIFICATION:通知

 三.AudioRecord 和 AudioTrack使用

  1. AudioRecord
  • AudioRecord初始化:
int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
CHANNEL_CONFIG, AUDIO_FORMAT, bufferSizeInBytes);
  • 获取音频数据并写入文件:
final byte data[] = new byte[bufferSizeInBytes];
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
while (isRecording) {
int read = audioRecord.read(data, 0, data.length);
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
fos.write(data);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  1.  
  2. AudioTrack
  • AudioTrack初始化:
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
bufferSizeInBytes,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play();
  • AudioTrack读取数据并写入:
File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[bufferSizeInBytes];
while (fileInputStream.available() > 0) {
int count = fileInputStream.read(bytes);
if (count != 0 && count != -1) {
audioTrack.write(bytes, 0, count);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

PCM数据转换:

利用audioRecord获取的麦克风PCM数据是原始音频数据,没有格式播放器适是播放不了的。所以需要给pcm数据头部加上格式才能让播放器识别。具体如下(网上很多):

PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
File pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
File wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.wav");
if (!wavFile.mkdirs()) {
mLogger.info("wavFile Directory not created");
}
if (wavFile.exists()) {
wavFile.delete();
}
pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath());

创建需要转换的目标文件wavFile和构建源文件,通过PcmToWavUtil进行加头部

public void pcmToWav(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 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录制PCM数据并保存到sdcard,利用AudioTrack实现播放实例

public class MainActivity extends AppCompatActivity {
private Logger mLogger = Logger.getLogger("MainActivity");
/**
* 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
*/
public static final int SAMPLE_RATE_INHZ = 44100;
/**
* 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
*/
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
/**
* 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
*/
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
AudioRecord audioRecord;
private String[] permissions = new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
private boolean mHasTrack = false;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 2000) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
mLogger.info(permissions[i] + "is PERMISSION_GRANTED");
} else {
mLogger.info(permissions[i] + "is not PERMISSION_DENIED");
}
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, 2000);
}
}
findViewById(R.id.btn_audio_recorder_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startRecord();
}
});
findViewById(R.id.btn_audio_recorder_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (audioRecord != null) {
isRecording = false;
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
}
});
findViewById(R.id.btn_pcm_convert).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
File pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
File wavFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.wav");
if (!wavFile.mkdirs()) {
mLogger.info("wavFile Directory not created");
}
if (wavFile.exists()) {
wavFile.delete();
}
pcmToWavUtil.pcmToWav(pcmFile.getAbsolutePath(), wavFile.getAbsolutePath());
}
});
final Button btnAudioTrack = findViewById(R.id.btn_audio_track);
btnAudioTrack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mHasTrack) {
mHasTrack = true;
playPcm();
btnAudioTrack.setText("stop audio track");
} else {
mHasTrack = false;
btnAudioTrack.setText("start audio track");
if (audioTrack != null) {
audioTrack.stop();
audioTrack.release();
audioTrack = null;
}
}
}
});
}
AudioTrack audioTrack;
private void playPcm() {
final int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
bufferSizeInBytes,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play();
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
new Thread(new Runnable() {
@Override
public void run() {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[bufferSizeInBytes];
while (fileInputStream.available() > 0) {
int count = fileInputStream.read(bytes);
if (count != 0 && count != -1) {
audioTrack.write(bytes, 0, count);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
private boolean isRecording = true;
private void startRecord() {
int bufferSizeInBytes = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
CHANNEL_CONFIG, AUDIO_FORMAT, bufferSizeInBytes);
final byte data[] = new byte[bufferSizeInBytes];
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "record.pcm");
if (!file.mkdirs()) {
mLogger.info("create file");
}
if (file.exists()) {
mLogger.info("delete file");
file.delete();
}
mLogger.info("file path=" + file.getAbsolutePath());
audioRecord.startRecording();
new Thread(new Runnable() {
@Override
public void run() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
while (isRecording) {
int read = audioRecord.read(data, 0, data.length);
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
fos.write(data);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}

五.总结

下面总结下要通过AudioRecord录制PCM数据到AudioTrack需要完成哪些步骤:

  1. 构建AudioRecord实例对象并获取pcm数据存入sdcard中。
  2. 将pcm数据转换---进行头部添加格式。
  3. 构建AudioTrack对象并读取第一步中写入sdcard的数据进行播放。
  4. 播放完成需要释放AudioRecord和AudioTrack资源。

Demo:https://github.com/zhouwenhn/ZWDemo/tree/master/audiorecordtrack

最后

以上就是整齐奇异果为你收集整理的Android音视频技术2--Android AudioRecord 和 AudioTrack音频PCM数据采集和播放的全部内容,希望文章能够帮你解决Android音视频技术2--Android AudioRecord 和 AudioTrack音频PCM数据采集和播放所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部