音频 PCM 数据的采集和播放
PCM(Pulse Code Modulation)脉冲编码调制 —— 音频的采集与量化过程。
PCM数据是最原始的音频数据完全无损,所以PCM数据虽然音质优秀但体积庞大。
为了解决这个问题先后诞生了一系列的音频格式,这些音频格式运用不同的方法对音频数据进行压缩,其中有无损压缩(ALAC、APE、FLAC)和有损压缩(MP3、AAC、OGG、WMA)两种。
代码实现逻辑过程:
使用AudioRecord录制pcm音频 ——> PCM转WAV(只要加上wav头文件即可)——> 使用AudioTrack播放pcm音频
——> 使用 AudioTrack 播放音频
使用AudioRecord录制PCM音频代码:
/**
* 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。
*/
private static final int SAMPLE_RATE_INHZ = 44100; /**
* 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。
*/
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
/**
* 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
*/
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ,
CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize); final byte data[] = new byte[minBufferSize];
final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
if (!file.mkdirs()) {
Log.e(TAG, "Directory not created");
}
if (file.exists()) {
file.delete();
} audioRecord.startRecording();
isRecording = true; new Thread(new Runnable() {
@Override public void run() { FileOutputStream os = null;
try {
os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
} if (null != os) {
while (isRecording) {
int read = audioRecord.read(data, 0, minBufferSize);
// 如果读取音频数据没有出现错误,就将数据写入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
Log.i(TAG, "run: close file output stream !");
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
PCM转WAV:
// 音频数据的大小
long totalAudioLen = fileInputStream.getChannel().size();
// wav总区块大小
long totalDataLen = totalAudioLen + 36;
// 声道数量
int channels;
// 采样率
long longSampleRate;
// 位元率
long byteRate = 16 * longSampleRate * channels / 8; 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);
使用AudioTrack 播放音频
/**
* 播放,使用stream模式
*/
private void playInModeStream() {
/*
* SAMPLE_RATE_INHZ 对应pcm音频的采样率
* channelConfig 对应pcm音频的声道
* AUDIO_FORMAT 对应pcm音频的格式
* */
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, 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(channelConfig)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.play(); File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
try {
fileInputStream = new FileInputStream(file);
new Thread(new Runnable() {
@Override public void run() {
try {
byte[] tempBuffer = new byte[minBufferSize];
while (fileInputStream.available() > 0) {
int readCount = fileInputStream.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {
audioTrack.write(tempBuffer, 0, readCount);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start(); } catch (IOException e) {
e.printStackTrace();
}
} /**
* 播放,使用static模式
*/
private void playInModeStatic() {
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区 new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
InputStream in = getResources().openRawResource(R.raw.ding);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b; (b = in.read()) != -1; ) {
out.write(b);
}
Log.d(TAG, "Got the data");
audioData = out.toByteArray();
} finally {
in.close();
}
} catch (IOException e) {
Log.wtf(TAG, "Failed to read", e);
}
return null;
} @Override
protected void onPostExecute(Void v) {
Log.i(TAG, "Creating track...audioData.length = " + audioData.length); // R.raw.ding铃声文件的相关属性为 22050Hz, 8-bit, Mono
audioTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder().setSampleRate(22050)
.setEncoding(AudioFormat.ENCODING_PCM_8BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
audioData.length,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE);
Log.d(TAG, "Writing audio data...");
audioTrack.write(audioData, 0, audioData.length);
Log.d(TAG, "Starting playback");
audioTrack.play();
Log.d(TAG, "Playing");
} }.execute(); }
demo代码: https://i.cnblogs.com/Files.aspx
音频 PCM 数据的采集和播放的更多相关文章
- ffplay代码播放pcm数据
摘抄雷兄 http://blog.csdn.net/leixiaohua1020/article/details/46890259 /** * 最简单的SDL2播放音频的例子(SDL2播放PCM) * ...
- Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
Android音频处理--通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能 音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下 ...
- iOS 实时音频采集与播放Audio Unit使用
前言 在iOS中有很多方法可以进行音视频采集.如 AVCaptureDevice, AudioQueue以及Audio Unit.其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; ...
- AudioToolbox--利用AudioQueue音频队列,通过缓存对声音进行采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- 【iOS录音与播放】实现利用音频队列,通过缓存进行对声音的采集与播放
都说iOS最恶心的部分是流媒体,其中恶心的恶心之处更在即时语音. 所以我们先不谈即时语音,研究一下,iOS中声音采集与播放的实现. 要在iOS设备上实现录音和播放功能,苹果提供了简单的做法,那就是利用 ...
- FFMPEG学习----使用SDL播放PCM数据
参考雷神的代码: /** * 最简单的SDL2播放音频的例子(SDL2播放PCM) * Simplest Audio Play SDL2 (SDL2 play PCM) * * 本程序使用SDL2播放 ...
- JavaScript基础修炼(14)——WebRTC在浏览器中如何获得指定格式的PCM数据
目录 一. PCM格式是什么 二. 浏览器中的音频采集处理 三. 需求实现 方案1--服务端FFmpeg实现编码 方案2--ScriptProcessorNode手动处理数据流 参考文献 示例代码托管 ...
- [转]directsound抓取麦克风PCM数据封装类
网上有很多方法从麦克风读取PCM数据,不想一一举例.只是在这里发布一个我自己写的directsound的麦克风PCM数据采集类,通过它,可以很方便的利用directsound技术把麦克风的数据采集到, ...
- wav格式文件、pcm数据
wav格式文件是常见的录音文件,是声音波形文件格式之一,wav 文件由文件头和数据体两部分组成. 文件头是我们在做录音保存到文件的时候,要存储的文件的说明信息,播放器要通过文件头的相关信息去读取数据播 ...
随机推荐
- Win7下npm命令Error: ENOENT问题解决
Win7下在执行npm命令,比如npm list时出现下面错误:
- C++指针理解
指针是C/C++编程中的重要概念之一,也是最容易产生困惑并导致程序出错的问题之一.利用指针编程可以表示各种数据结构,通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯:指针能 ...
- ORA-00600: internal error code, arguments: [4193]问题解决
操作环境 SuSE+Oracle11gR2 问题现象 单板宕机自动重启后,ORACLE运行不正常,主要表现如下: 1.执行shutdown immedate停止数据库时,提示ORA-00600: in ...
- 217/219. Contains Duplicate /Contains Duplicate II
原文题目: 217. Contains Duplicate 219. Contains Duplicate II 读题: 217只要找出是否有重复值, 219找出重复值,且要判断两者索引之差是否小于k ...
- delphi 实现用户自定义通知(User Notification)
unit Form_Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Sy ...
- python垃圾回收机制(转)
Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾.在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的 ...
- typora中文版官方免费快速下载以及Markdown的一些常用语法、Java知识点
typora下载 链接:https://某度云盘的域名/s/1geD1APxnyV3gogYW3E08bQ 密码:8fdp 把某度云盘的域名进行替换 1.标题 # 标题1 ## 标题2 ### 标题3 ...
- Java多线程及线程状态转换
以下内容整理自:http://blog.csdn.net/wtyvhreal/article/details/44176369 线程:是指进程中的一个执行流程. 线程与进程的区别:每个进程都需要操作 ...
- echarts 树图
1 事件:事件绑定,事件命名统一挂载到require('echarts/config').EVENT(非模块化为echarts.config.EVENT)命名空间下,建议使用此命名空间作为事件名引用, ...
- CentOS7 安装 GitLab
虽然GitHub已经很好了,但是我们必须联上公网才可以使用并且如果不付费的话,你的代码在网上就是公开的!但是在企业环境中,我们公司的代码不希望被公开并且也不想付费给GitHub,这时怎么办呢?我们可以 ...