欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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