upgrade
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c58eb358ced9748b8913d328d4a96636
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.czt.mp3recorder;
|
||||
|
||||
import android.media.AudioRecord;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.czt.mp3recorder.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f6f8444c067b489ab5f68e3b42557cb
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.czt.mp3recorder;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.czt.mp3recorder.util.LameUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MP3Recorder {
|
||||
//=======================AudioRecord Default Settings=======================
|
||||
private static int DEFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
|
||||
/**
|
||||
* 以下三项为默认配置参数。Google Android文档明确表明只有以下3个参数是可以在所有设备上保证支持的。
|
||||
*/
|
||||
private static final int DEFAULT_SAMPLING_RATE = 44100;//模拟器仅支持从麦克风输入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;
|
||||
}
|
||||
|
||||
public void setRecordFile(File recordFile) {
|
||||
mRecordFile = recordFile;
|
||||
}
|
||||
|
||||
public void setAudioSource(int audioSource) {
|
||||
DEFAULT_AUDIO_SOURCE = audioSource;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 134eee28cbbda46bd8c5032af2a8fb0a
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.czt.mp3recorder;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cfce2307b2fa48d6ae51da8e719e1e3
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5796cc6b4b0a2492da182ee538742752
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.czt.mp3recorder.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();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 091a5e5f71553434695297dd6e4e2b16
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user