android mp3实时录制转换
程序员文章站
2022-06-15 11:49:20
Android mp3音频文件录制Android mp3音频文件录制我搞了2天的东西 公司遇到上传语音转文字 需要mp3文件 android ios 都无法录制MP3文件,好多文章都是ndk打包so库,借鉴了好多终于搞定了,给大家分享出来,代码小白勿喷录音权限必备各种权限自己加,这里就不一一列举了,文字末尾添加资源文件library依赖库导入implementation project(':mp3library')LameUtilpackage com.lebanban.mp3libra...
Android mp3音频文件录制
Android mp3音频文件录制
我搞了2天的东西 公司遇到上传语音转文字 需要mp3文件 android ios 都无法录制MP3文件,好多文章都是ndk打包so库,借鉴了好多终于搞定了,给大家分享出来,代码小白勿喷
录音权限必备
各种权限自己加,这里就不一一列举了,文字末尾添加资源文件
library依赖库导入
implementation project(':mp3library')
LameUtil
package com.lebanban.mp3library.util;
public class LameUtil {
static{
System.loadLibrary("mp3lame");
}
/**
* Initialize LAME.
*
* @param inSamplerate
* input sample rate in Hz.
* @param inChannel
* number of channels in input stream.
* @param outSamplerate
* output sample rate in Hz.
* @param outBitrate
* brate compression ratio in KHz.
* @param quality
* <p>quality=0..9. 0=best (very slow). 9=worst.</p>
* <p>recommended:</p>
* <p>2 near-best quality, not too slow</p>
* <p>5 good quality, fast</p>
* 7 ok quality, really fast
*/
public native static void init(int inSamplerate, int inChannel,
int outSamplerate, int outBitrate, int quality);
/**
* Encode buffer to mp3.
*
* @param bufferLeft
* PCM data for left channel.
* @param bufferRight
* PCM data for right channel.
* @param samples
* number of samples per channel.
* @param mp3buf
* result encoded MP3 stream. You must specified
* "7200 + (1.25 * buffer_l.length)" length array.
* @return <p>number of bytes output in mp3buf. Can be 0.</p>
* <p>-1: mp3buf was too small</p>
* <p>-2: malloc() problem</p>
* <p>-3: lame_init_params() not called</p>
* -4: psycho acoustic problems
*/
public native static int encode(short[] bufferLeft, short[] bufferRight,
int samples, byte[] mp3buf);
/**
* Flush LAME buffer.
*
* REQUIRED:
* lame_encode_flush will flush the intenal PCM buffers, padding with
* 0's to make sure the final frame is complete, and then flush
* the internal MP3 buffers, and thus may return a
* final few mp3 frames. 'mp3buf' should be at least 7200 bytes long
* to hold all possible emitted data.
*
* will also write id3v1 tags (if any) into the bitstream
*
* return code = number of bytes output to mp3buf. Can be 0
* @param mp3buf
* result encoded MP3 stream. You must specified at least 7200
* bytes.
* @return number of bytes output to mp3buf. Can be 0.
*/
public native static int flush(byte[] mp3buf);
/**
* Close LAME.
*/
public native static void close();
}
DataEncodeThread
package com.lebanban.mp3library;
import android.media.AudioRecord;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import com.lebanban.mp3library.util.LameUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DataEncodeThread extends HandlerThread implements AudioRecord.OnRecordPositionUpdateListener {
private StopHandler mHandler;
private static final int PROCESS_STOP = 1;
private byte[] mMp3Buffer;
private FileOutputStream mFileOutputStream;
private static class StopHandler extends Handler {
private DataEncodeThread encodeThread;
public StopHandler(Looper looper, DataEncodeThread encodeThread) {
super(looper);
this.encodeThread = encodeThread;
}
@Override
public void handleMessage(Message msg) {
if (msg.what == PROCESS_STOP) {
//处理缓冲区中的数据
while (encodeThread.processData() > 0);
// Cancel any event left in the queue
removeCallbacksAndMessages(null);
encodeThread.flushAndRelease();
getLooper().quit();
}
}
}
/**
* Constructor
* @param file file
* @param bufferSize bufferSize
* @throws FileNotFoundException file not found
*/
public DataEncodeThread(File file, int bufferSize) throws FileNotFoundException {
super("DataEncodeThread");
this.mFileOutputStream = new FileOutputStream(file);
mMp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];
}
@Override
public synchronized void start() {
super.start();
mHandler = new StopHandler(getLooper(), this);
}
private void check() {
if (mHandler == null) {
throw new IllegalStateException();
}
}
public void sendStopMessage() {
check();
mHandler.sendEmptyMessage(PROCESS_STOP);
}
public Handler getHandler() {
check();
return mHandler;
}
@Override
public void onMarkerReached(AudioRecord recorder) {
// Do nothing
}
@Override
public void onPeriodicNotification(AudioRecord recorder) {
processData();
}
/**
* 从缓冲区中读取并处理数据,使用lame编码MP3
* @return 从缓冲区中读取的数据的长度
* 缓冲区中没有数据时返回0
*/
private int processData() {
if (mTasks.size() > 0) {
Task task = mTasks.remove(0);
short[] buffer = task.getData();
int readSize = task.getReadSize();
int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
if (encodedSize > 0){
try {
mFileOutputStream.write(mMp3Buffer, 0, encodedSize);
} catch (IOException e) {
e.printStackTrace();
}
}
return readSize;
}
return 0;
}
/**
* Flush all data left in lame buffer to file
*/
private void flushAndRelease() {
//将MP3结尾信息写入buffer中
final int flushResult = LameUtil.flush(mMp3Buffer);
if (flushResult > 0) {
try {
mFileOutputStream.write(mMp3Buffer, 0, flushResult);
} catch (IOException e) {
e.printStackTrace();
}finally{
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
LameUtil.close();
}
}
}
private List<Task> mTasks = Collections.synchronizedList(new ArrayList<Task>());
public void addTask(short[] rawData, int readSize){
mTasks.add(new Task(rawData, readSize));
}
private class Task{
private short[] rawData;
private int readSize;
public Task(short[] rawData, int readSize){
this.rawData = rawData.clone();
this.readSize = readSize;
}
public short[] getData(){
return rawData;
}
public int getReadSize(){
return readSize;
}
}
}
MP3Recorder
我遇到的做大问题就是这里的19行 采样率 转换时候 采样率越高 转换文字出来的东西屁毛不沾
package com.lebanban.mp3library;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import com.lebanban.mp3library.util.LameUtil;
import java.io.File;
import java.io.IOException;
public class MP3Recorder {
//=======================AudioRecord Default Settings=======================
private static final int DEFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
/**
* 以下三项为默认配置参数。Google Android文档明确表明只有以下3个参数是可以在所有设备上保证支持的。
*/
private static final int DEFAULT_SAMPLING_RATE = 16000;//模拟器仅支持从麦克风输入8kHz采样率
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
/**
* 下面是对此的封装
* private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
*/
private static final PCMFormat DEFAULT_AUDIO_FORMAT = PCMFormat.PCM_16BIT;
//======================Lame Default Settings=====================
private static final int DEFAULT_LAME_MP3_QUALITY = 7;
/**
* 与DEFAULT_CHANNEL_CONFIG相关,因为是mono单声,所以是1
*/
private static final int DEFAULT_LAME_IN_CHANNEL = 1;
/**
* Encoded bit rate. MP3 file will be encoded with bit rate 32kbps
*/
private static final int DEFAULT_LAME_MP3_BIT_RATE = 32;
//==================================================================
/**
* 自定义 每160帧作为一个周期,通知一下需要进行编码
*/
private static final int FRAME_COUNT = 160;
private AudioRecord mAudioRecord = null;
private int mBufferSize;
private short[] mPCMBuffer;
private DataEncodeThread mEncodeThread;
private boolean mIsRecording = false;
private File mRecordFile;
/**
* Default constructor. Setup recorder with default sampling rate 1 channel,
* 16 bits pcm
* @param recordFile target file
*/
public MP3Recorder(File recordFile) {
mRecordFile = recordFile;
}
/**
* Start recording. Create an encoding thread. Start record from this
* thread.
*
* @throws IOException initAudioRecorder throws
*/
public void start() throws IOException {
if (mIsRecording) {
return;
}
mIsRecording = true; // 提早,防止init或startRecording被多次调用
initAudioRecorder();
mAudioRecord.startRecording();
new Thread() {
@Override
public void run() {
//设置线程权限
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
while (mIsRecording) {
int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize);
if (readSize > 0) {
mEncodeThread.addTask(mPCMBuffer, readSize);
calculateRealVolume(mPCMBuffer, readSize);
}
}
// release and finalize audioRecord
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
// stop the encoding thread and try to wait
// until the thread finishes its job
mEncodeThread.sendStopMessage();
}
/**
* 此计算方法来自samsung开发范例
*
* @param buffer buffer
* @param readSize readSize
*/
private void calculateRealVolume(short[] buffer, int readSize) {
double sum = 0;
for (int i = 0; i < readSize; i++) {
// 这里没有做运算的优化,为了更加清晰的展示代码
sum += buffer[i] * buffer[i];
}
if (readSize > 0) {
double amplitude = sum / readSize;
mVolume = (int) Math.sqrt(amplitude);
}
}
}.start();
}
private int mVolume;
/**
* 获取真实的音量。 [算法来自三星]
* @return 真实音量
*/
public int getRealVolume() {
return mVolume;
}
/**
* 获取相对音量。 超过最大值时取最大值。
* @return 音量
*/
public int getVolume(){
if (mVolume >= MAX_VOLUME) {
return MAX_VOLUME;
}
return mVolume;
}
private static final int MAX_VOLUME = 2000;
/**
* 根据资料假定的最大值。 实测时有时超过此值。
* @return 最大音量值。
*/
public int getMaxVolume(){
return MAX_VOLUME;
}
public void stop(){
mIsRecording = false;
}
public boolean isRecording() {
return mIsRecording;
}
/**
* Initialize audio recorder
*/
private void initAudioRecorder() throws IOException {
mBufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLING_RATE,
DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT.getAudioFormat());
int bytesPerFrame = DEFAULT_AUDIO_FORMAT.getBytesPerFrame();
/* Get number of samples. Calculate the buffer size
* (round up to the factor of given frame size)
* 使能被整除,方便下面的周期性通知
* */
int frameSize = mBufferSize / bytesPerFrame;
if (frameSize % FRAME_COUNT != 0) {
frameSize += (FRAME_COUNT - frameSize % FRAME_COUNT);
mBufferSize = frameSize * bytesPerFrame;
}
/* Setup audio recorder */
mAudioRecord = new AudioRecord(DEFAULT_AUDIO_SOURCE,
DEFAULT_SAMPLING_RATE, DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT.getAudioFormat(),
mBufferSize);
mPCMBuffer = new short[mBufferSize];
/*
* Initialize lame buffer
* mp3 sampling rate is the same as the recorded pcm sampling rate
* The bit rate is 32kbps
*
*/
LameUtil.init(DEFAULT_SAMPLING_RATE, DEFAULT_LAME_IN_CHANNEL, DEFAULT_SAMPLING_RATE, DEFAULT_LAME_MP3_BIT_RATE, DEFAULT_LAME_MP3_QUALITY);
// Create and run thread used to encode data
// The thread will
mEncodeThread = new DataEncodeThread(mRecordFile, mBufferSize);
mEncodeThread.start();
mAudioRecord.setRecordPositionUpdateListener(mEncodeThread, mEncodeThread.getHandler());
mAudioRecord.setPositionNotificationPeriod(FRAME_COUNT);
}
}
PCMFormat
package com.lebanban.mp3library;
import android.media.AudioFormat;
public enum PCMFormat {
PCM_8BIT (1, AudioFormat.ENCODING_PCM_8BIT),
PCM_16BIT (2, AudioFormat.ENCODING_PCM_16BIT);
private int bytesPerFrame;
private int audioFormat;
PCMFormat(int bytesPerFrame, int audioFormat) {
this.bytesPerFrame = bytesPerFrame;
this.audioFormat = audioFormat;
}
public int getBytesPerFrame() {
return bytesPerFrame;
}
public int getAudioFormat() {
return audioFormat;
}
}
资源文件下载
链接: https://pan.baidu.com/s/1vfUnuztDYztj8VOmghN8YA
提取码: nhkx
新手小白勿喷
本文地址:https://blog.csdn.net/qq_46041134/article/details/114323989