# Pcm 转 AAc
什么是Pcm?
PCM(Pulse Code Modulation)脉冲编码调制是数字通信的编码方式之一。主要过程是将话音、图像等模拟信号每隔一定时间进行取样,使其离散化,同时将抽样值按分层单位四舍五入取整量化,同时将抽样值按一组二进制码来表示抽样脉冲的幅值。
# Pcm 音频格式
PCM:其数据排列格式为左右声道每个样本点数据交错排列
一般来说,在做接收机开发的时候,考虑到网络传输负荷的问题,会考虑将音频(数据)信号进行下采样或者去噪的基 本处理,这样就要涉及到音频信号的滤波处理。但是不论是在时域滤波还是频域滤波,接收机直接输出的音频byte流不 能直接用,这时就要考虑用byte流恢复原始音频时域数据,这时必须清楚PCM编码的数据组织格式,涉及两个基本问题 一个pcm采样数据占多少字节,高低位存放顺序的问题,得到时域数据后才能使用滤波器对音频数据进行滤波,经过这 样的转换成功实现了音频信号中干扰噪声的滤波,使得音频更加清晰完全听不到干扰信号。
# AAC
aac 是音频文件的一种格式
# MP3和AAC有什么不同?
1、压缩技术的不同 MP3是利用人耳对高频声音信号不敏感的特性,将时域波形信号转换成频域信号,并划分成多个频段,对不同的频段使用不同的压缩率,对高频加大压缩比(甚至忽略信号)对低频信号使用小压缩比,保证信号不失真。 这样一来就相当于抛弃人耳基本听不到的高频声音,只保留能听到的低频部分,从而将声音用1∶10甚至1∶12的压缩率压缩。
AAC它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。
2、音频质量不同 AAC格式在96Kbps码率的表现超过了128Kbps的MP3格式。同样是128Kbps,AAC格式的音质明显好于MP3。AC是唯一一个,能够在所有的EBU试听测试项目的获得“优秀”的网络广播格式。
# 百度语音识别
最近开发上遇到一个功能 就是采用语音识别 将语音转换为文字 同时在转换完毕后要保留 文字信息和语音信息 文字间断输入 语音文件拼接 最后获取一串文字 和 一个.aac 文件
如何操作?
查找百度语音识别 文档发现 百度语音识别 语音转文字语音采集音频格式 如下:
默认为麦克风输入,可以设置参数为pcm格式16k采样率,16bit,小端序,单声道的音频流输入。
获取 百度语音识别 产生的音频文件:
/**
* 基于SDK集成2.2 发送开始事件
* 点击开始按钮
* 测试参数填在这里
*/
public void start() {
Map<String, Object> params = new LinkedHashMap<String, Object>();
String event = null;
event = SpeechConstant.ASR_START; // 替换成测试的event
if (enableOffline) {
params.put(SpeechConstant.DECODER, 2);
} else {
}
// 基于SDK集成2.1 设置识别参数
params.put(SpeechConstant.ACCEPT_AUDIO_VOLUME, false); //当前音量回调
//在联网情况下,在普通话的搜索模型或远场模型时可以使用,将识别出来的文本百度服务端做语义分析,获取文本的意图和词槽。
params.put(SpeechConstant.PID, 15373); // 中文输入法模型,有逗号
params.put(SpeechConstant.VAD_ENDPOINT_TIMEOUT, 3000); //开启长语音。开启VAD尾点检测,即静音判断的毫秒数。建议设置800ms-3000ms
// params.put(SpeechConstant.NLU, "enable");
params.put(SpeechConstant.DISABLE_PUNCTUATION, false);
// params.put(SpeechConstant.IN_FILE, "res:///com/baidu/android/voicedemo/16k_test.pcm");
params.put(SpeechConstant.VAD, SpeechConstant.VAD_DNN);
//是否输出语音文件
params.put(SpeechConstant.ACCEPT_AUDIO_DATA, true);
//输出文件目录
params.put(SpeechConstant.OUT_FILE, voicePcmUrl + "outfile.pcm");
// 请先使用如‘在线识别’界面测试和生成识别参数。 params同ActivityRecog类中myRecognizer.start(params);
// 复制此段可以自动检测错误
(new AutoCheck(mContext, new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 100) {
AutoCheck autoCheck = (AutoCheck) msg.obj;
synchronized (autoCheck) {
String message = autoCheck.obtainErrorMessage(); // autoCheck.obtainAllMessage();
}
}
}
}, enableOffline)).checkAsr(params);
String json = null; // 可以替换成自己的json
json = new JSONObject(params).toString(); // 这里可以替换成你需要测试的json
wakeup.send(event, json, null, 0, 0);
}
既然拿到了.pcm 文件 那么问题来了 如何将pcm 转换为AAc 且完成拼接呢?
一阵疯狂的csdn 百度 官方文档 等等。。。
Android pcm 转 aac 实现方法
- MediaCodec 配置编译器 实现编辑转码
- ffmpeg
# 封装一个工具类:
- 获取转化文件路径 转化完成输出文件保存路径
- 初始化MediaCodec 编码器
- 将.pcm bayte[] 数据存入 缓存队列
- 开启一个线程 读取缓存队列数据开始转换
- 转换监听
/**
* 设置输入输出文件位置
*
* @param srcPath
* @param dstPath
*/
public void setIOPath(String srcPath, String dstPath) {
this.srcPath = srcPath;
this.dstPath = dstPath;
}
/**
* 此类已经过封装
* 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作
*/
public void prepare() {
codeOver = false;
if (srcPath == null) {
throw new IllegalArgumentException("srcPath can't be null");
}
if (dstPath == null) {
throw new IllegalArgumentException("dstPath can't be null");
}
try {
File file = new File(srcPath);
fileTotalSize = file.length();
outFile = new File(dstPath);
if (!file.exists()) {
file.createNewFile();
}
fileOutSize = outFile.length();
// fos = new FileOutputStream(outFile);
// bos = new BufferedOutputStream(fos, (int) fileTotalSize);
queue = new ArrayBlockingQueue<byte[]>(10);
initAACMediaEncode();//AAC编码器
FileInputStream inputStream = new FileInputStream(file);
int available = inputStream.available();
byte[] pcm = new byte[available];
inputStream.read(pcm);
putPCMData(pcm);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 初始化AAC编码器
*/
private void initAACMediaEncode() {
try {
LogUtils.d(key_bit_rate + " " + key_channel_count + " " + key_sample_rate + " " + sampleRateType);
key_sample_rate = 16000;
key_channel_count = 1;
key_bit_rate = 16;
sampleRateType = ADTSUtils.getSampleRateType(16000);
MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,
key_sample_rate, key_channel_count);//参数对应-> mime type、采样率、声道数
encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, key_bit_rate);//比特率
encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, (int) fileTotalSize);
mediaEncode = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException e) {
e.printStackTrace();
}
if (mediaEncode == null) {
LogUtils.e("create mediaEncode failed");
return;
}
mediaEncode.start();
encodeInputBuffers = mediaEncode.getInputBuffers();
encodeOutputBuffers = mediaEncode.getOutputBuffers();
encodeBufferInfo = new MediaCodec.BufferInfo();
}
/**
* Author : eric
* CreateDate : 2018/1/4 15:28
* Email : ericli_wang@163.com
* Version : 2.0
* Desc :
* Modified :
*/
public class ADTSUtils {
private static Map<String, Integer> SAMPLE_RATE_TYPE;
static {
SAMPLE_RATE_TYPE = new HashMap<>();
SAMPLE_RATE_TYPE.put("96000", 0);
SAMPLE_RATE_TYPE.put("88200", 1);
SAMPLE_RATE_TYPE.put("64000", 2);
SAMPLE_RATE_TYPE.put("48000", 3);
SAMPLE_RATE_TYPE.put("44100", 4);
SAMPLE_RATE_TYPE.put("32000", 5);
SAMPLE_RATE_TYPE.put("24000", 6);
SAMPLE_RATE_TYPE.put("22050", 7);
SAMPLE_RATE_TYPE.put("16000", 8);
SAMPLE_RATE_TYPE.put("12000", 9);
SAMPLE_RATE_TYPE.put("11025", 10);
SAMPLE_RATE_TYPE.put("8000", 11);
SAMPLE_RATE_TYPE.put("7350", 12);
}
public static int getSampleRateType(int sampleRate) {
return SAMPLE_RATE_TYPE.get(sampleRate + "");
}
}
/**
* 开始转码
* 音频数据{@link #srcPath}先解码成PCM PCM数据在编码成MediaFormat.MIMETYPE_AUDIO_AAC音频格式
* mp3->PCM->aac
*/
public void startAsync() {
LogUtils.w("start");
new Thread(new EncodeRunnable()).start();
}
/**
* 编码PCM数据 得到MediaFormat.MIMETYPE_AUDIO_AAC格式的音频文件,并保存到{@link #dstPath}
*/
private void dstAudioFormatFromPCM() {
int inputIndex;
ByteBuffer inputBuffer;
int outputIndex;
ByteBuffer outputBuffer;
byte[] chunkAudio;
int outBitSize;
int outPacketSize;
byte[] chunkPCM;
for (int i = 0; i < encodeInputBuffers.length - 1; i++) {
chunkPCM = getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上
if (chunkPCM == null) {
break;
}
inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器
inputBuffer = encodeInputBuffers[inputIndex];//同解码器
inputBuffer.clear();//同解码器
inputBuffer.limit(chunkPCM.length);
inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer
mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码
}
outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器
while (outputIndex >= 0) {//同解码器
outBitSize = encodeBufferInfo.size;
outPacketSize = outBitSize + 7;//7为ADTS头部的大小
outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
outputBuffer.position(encodeBufferInfo.offset);
outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
chunkAudio = new byte[outPacketSize];
addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS 代码后面会贴上
outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得
outputBuffer.position(encodeBufferInfo.offset);
try {
//实现追加
RandomAccessFile randomFile = new RandomAccessFile(dstPath, "rw");
// 文件长度,字节数
long fileLength = randomFile.length();
// 将写文件指针移到文件尾。
randomFile.seek(fileLength);
randomFile.write(chunkAudio, 0, chunkAudio.length);
LogUtils.d("write " + chunkAudio.length);
randomFile.close();
} catch (IOException e) {
e.printStackTrace();
}
mediaEncode.releaseOutputBuffer(outputIndex, false);
outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);
codeOver = true;
}
}
/**
* 添加ADTS头
*
* @param packet
* @param packetLen
*/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; // AAC LC
int freqIdx = sampleRateType; // 44.1KHz
int chanCfg = 2; // CPE
// fill in ADTS data
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
注明:参考