Android和IOS录制mp3语音文件的方法
Android
Android Supported Media Formats :http://developer.android.com/guide/appendix/media-formats.html
iOS
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions:http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html
总结
对比 Android 与 iOS 所支持的音频格式,如果需要跨平台进行音频数据交换,只有 AAC 和 Linear PCM 可以选择
AAC 对音频进行压缩,音频数据较小
Linear PCM未对音频进行压缩,实时性更好,但音频数据较大
近期在 做一个有关于语音播放的项目,其中用到了android录音部分,查了好多资料只能录制amr和3gp格式,不能录制mp3格式;IOS端遇到同样问题, 只能录制caf格式,不能录制mp3,所以通用性就得到了考验。在痛苦中挣扎,在烦恼中度过,终于在苦思冥想中,解决了这个问题,总结核心部分如下:
无论是android还是IOS都是同一个思路,android中先想办法录制wav格式,然后通过lame进行转换。IOS是先录制caf文件,然后通过lame转换成mp3格式。
lame是一个mp3的免费格式库,baidu或者google都可以查到源代码,是用c写的。
在开发过程中,由于IOS可以直接录制成caf文件,但是android录制wav遇到了 困难。大家肯定会问为什么不用3gp或者amr直接转换成mp3呢?我最开始也是这样想的,但是经过无数次3gp || amr进行lame转换,发现都不成功,最终确认3gp || amr通过lame转换MP3格式行不通。
================================ IOS part =============================================
相对来说,IOS的转换比较简单,下载编译好的lame库文件,libmp3lame.a放在Frameworks下面,把lame.h这个头文件引入项目中,在项目中转换函数如下,其中需要指定被转换和转换后文件路径,视项目需要而定:
//转换Mp3格式方法
-(IBAction)toMp3{
NSString*mp3AudioPath=[[NSStringstringWithFormat:@"%@/%@.mp3",DOCUMENTS_FOLDER,@"temp"]retain];//新转换mp3文件路径
//进入转换
intread,write;
FILE*pcm=fopen([recorderFilePathcStringUsingEncoding:1],"rb");//被转换的文件
FILE*mp3=fopen([mp3AudioPathcStringUsingEncoding:1],"wb");//转换后文件的存放位置
constintPCM_SIZE=8192;
constintMP3_SIZE=8192;
shortintpcm_buffer[PCM_SIZE*2];
unsignedcharmp3_buffer[MP3_SIZE];
lame_tlame=lame_init();
lame_set_in_samplerate(lame,44100);
lame_set_VBR(lame,vbr_default);
lame_init_params(lame);
do{
read=fread(pcm_buffer,2*sizeof(shortint),PCM_SIZE,pcm);
if(read==0)
write=lame_encode_flush(lame,mp3_buffer,MP3_SIZE);
else
write=lame_encode_buffer_interleaved(lame,pcm_buffer,read,mp3_buffer,MP3_SIZE);
fwrite(mp3_buffer,write,1,mp3);
}while(read!=0);
lame_close(lame);
fclose(mp3);
fclose(pcm);
}
至此,新的mp3文件已经生成。
================================ android part ===============================================
android录制wav用到了一个文件ExtAudioRecorder.java,代码如下:
package com.example.util;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.media.MediaRecorder.AudioSource;
import android.util.Log;
public class ExtAudioRecorder
{private final static int[] sampleRates = {44100, 22050, 11025, 8000};
public static ExtAudioRecorder getInstanse(Boolean recordingCompressed)
{ExtAudioRecorder result = null;
if(recordingCompressed)
{result = new ExtAudioRecorder( false,
AudioSource.MIC,
sampleRates[3],
AudioFormat.CHANNEL_IN_STEREO,
//AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
} else {int i=0;
do {result = new ExtAudioRecorder( true,
AudioSource.MIC,
sampleRates[i],
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
} while((++i<sampleRates.length) & !(result.getState() == ExtAudioRecorder.State.INITIALIZING));
}return result;
} /*** INITIALIZING : recorder is initializing;
* READY : recorder has been initialized, recorder not yet started
* RECORDING : recording
* ERROR : reconstruction needed
* STOPPED: reset needed
*/public enum State {INITIALIZING, READY, RECORDING, ERROR, STOPPED};
public static final boolean RECORDING_UNCOMPRESSED = true;
public static final boolean RECORDING_COMPRESSED = false;
// The interval in which the recorded samples are output to the file
// Used only in uncompressed mode
private static final int TIMER_INTERVAL = 120;
// Toggles uncompressed recording on/off; RECORDING_UNCOMPRESSED / RECORDING_COMPRESSED
private boolean rUncompressed;
// Recorder used for uncompressed recording
private AudioRecord audioRecorder = null;
// Recorder used for compressed recording
private MediaRecorder mediaRecorder = null;
// Stores current amplitude (only in uncompressed mode)
private int cAmplitude= 0;
// Output file path
private String filePath = null;
// Recorder state; see State
private State state;
// File writer (only in uncompressed mode)
private RandomAccessFile randomAccessWriter;
// Number of channels, sample rate, sample size(size in bits), buffer size, audio source, sample size(see AudioFormat)
private short nChannels;
private int sRate;
private short bSamples;
private int bufferSize;
private int aSource;
private int aFormat;
// Number of frames written to file on each output(only in uncompressed mode)
private int framePeriod;
// Buffer for output(only in uncompressed mode)
private byte[] buffer;
// Number of bytes written to file after header(only in uncompressed mode)
// after stop() is called, this size is written to the header/data chunk in the wave file
private int payloadSize;
/** ** Returns the state of the recorder in a RehearsalAudioRecord.State typed object.
* Useful, as no exceptions are thrown.
** @return recorder state
*/public State getState()
{return state;
} /* ** Method used for recording.
* */private AudioRecord.OnRecordPositionUpdateListener updateListener = new AudioRecord.OnRecordPositionUpdateListener()
{public void onPeriodicNotification(AudioRecord recorder)
{audioRecorder.read(buffer, 0, buffer.length); // Fill buffer
try
{randomAccessWriter.write(buffer); // Write buffer to file
payloadSize += buffer.length;
if (bSamples == 16)
{
for (int i=0; i<buffer.length/2; i++)
{ // 16bit sample size
short curSample = getShort(buffer[i*2], buffer[i*2+1]);
if (curSample > cAmplitude)
{ // Check amplitude
cAmplitude = curSample;
}
}
}
else
{ // 8bit sample size
for (int i=0; i<buffer.length; i++)
{
if (buffer[i] > cAmplitude)
{ // Check amplitude
cAmplitude = buffer[i];
}
}
}
}catch (IOException e)
{e.printStackTrace();
Log.e(ExtAudioRecorder.class.getName(), "Error occured in updateListener, recording is aborted");
//stop();
} }public void onMarkerReached(AudioRecord recorder)
{// NOT USED
} }; /** * ** Default constructor
** Instantiates a new recorder, in case of compressed recording the parameters can be left as 0.
* In case of errors, no exception is thrown, but the state is set to ERROR
* */public ExtAudioRecorder(boolean uncompressed, int audioSource, int sampleRate, int channelConfig, int audioFormat)
{ try {rUncompressed = uncompressed;
if (rUncompressed)
{ // RECORDING_UNCOMPRESSED
if (audioFormat == AudioFormat.ENCODING_PCM_16BIT)
{
bSamples = 16;
}
else
{
bSamples = 8;
}
if (channelConfig == AudioFormat.CHANNEL_CONFIGURATION_MONO)
{
nChannels = 1;
}
else
{
nChannels = 2;
}
aSource = audioSource;
sRate = sampleRate;
aFormat = audioFormat;
framePeriod = sampleRate * TIMER_INTERVAL / 1000;
bufferSize = framePeriod * 2 * bSamples * nChannels / 8;
if (bufferSize < AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat))
{ // Check to make sure buffer size is not smaller than the smallest allowed one
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
// Set frame period and timer interval accordingly
framePeriod = bufferSize / ( 2 * bSamples * nChannels / 8 );
Log.w(ExtAudioRecorder.class.getName(), "Increasing buffer size to " + Integer.toString(bufferSize));
}
audioRecorder = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSize);
if (audioRecorder.getState() != AudioRecord.STATE_INITIALIZED)
throw new Exception("AudioRecord initialization failed");
audioRecorder.setRecordPositionUpdateListener(updateListener);
audioRecorder.setPositionNotificationPeriod(framePeriod);
} else
{ // RECORDING_COMPRESSED
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
}cAmplitude = 0;
filePath = null;
state = State.INITIALIZING;
} catch (Exception e)
{e.printStackTrace();
if (e.getMessage() != null)
{Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
}else
{Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while initializing recording");
}state = State.ERROR;
} } /*** Sets output file path, call directly after construction/reset.
** @param output file path
* */public void setOutputFile(String argPath)
{ try {if (state == State.INITIALIZING)
{filePath = argPath;
if (!rUncompressed)
{
mediaRecorder.setOutputFile(filePath);
}
} }catch (Exception e)
{e.printStackTrace();
if (e.getMessage() != null)
{Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
}else
{Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured while setting output path");
}state = State.ERROR;
} } /** ** Returns the largest amplitude sampled since the last call to this method.
** @return returns the largest amplitude since the last call, or 0 when not in recording state.
* */public int getMaxAmplitude()
{if (state == State.RECORDING)
{if (rUncompressed)
{int result = cAmplitude;
cAmplitude = 0;
return result;
}else
{try
{
return mediaRecorder.getMaxAmplitude();
}
catch (IllegalStateException e)
{
e.printStackTrace();
return 0;
}
} } else {return 0;
} } /** ** Prepares the recorder for recording, in case the recorder is not in the INITIALIZING state and the file path was not set
* the recorder is set to the ERROR state, which makes a reconstruction necessary.
* In case uncompressed recording is toggled, the header of the wave file is written.
* In case of an exception, the state is changed to ERROR
* */public void prepare()
{ try {if (state == State.INITIALIZING)
{if (rUncompressed)
{
if ((audioRecorder.getState() == AudioRecord.STATE_INITIALIZED) & (filePath != null))
{
// write file header
randomAccessWriter = new RandomAccessFile(filePath, "rw");
randomAccessWriter.setLength(0); // Set file length to 0, to prevent unexpected behavior in case the file already existed
randomAccessWriter.writeBytes("RIFF");
randomAccessWriter.writeInt(0); // Final file size not known yet, write 0
randomAccessWriter.writeBytes("WAVE");
randomAccessWriter.writeBytes("fmt ");
randomAccessWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
randomAccessWriter.writeShort(Short.reverseBytes(nChannels));// Number of channels, 1 for mono, 2 for stereo
randomAccessWriter.writeInt(Integer.reverseBytes(sRate)); // Sample rate
randomAccessWriter.writeInt(Integer.reverseBytes(sRate*bSamples*nChannels/8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8
randomAccessWriter.writeShort(Short.reverseBytes((short)(nChannels*bSamples/8))); // Block align, NumberOfChannels*BitsPerSample/8
randomAccessWriter.writeShort(Short.reverseBytes(bSamples)); // Bits per sample
randomAccessWriter.writeBytes("data");
randomAccessWriter.writeInt(0); // Data chunk size not known yet, write 0
buffer = new byte[framePeriod*bSamples/8*nChannels];
state = State.READY;
}
else
{
Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on uninitialized recorder");
state = State.ERROR;
}
}
else
{
mediaRecorder.prepare();
state = State.READY;
}
}else
{Log.e(ExtAudioRecorder.class.getName(), "prepare() method called on illegal state");
release();
state = State.ERROR;
} }catch(Exception e)
{e.printStackTrace();
if (e.getMessage() != null)
{Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
}else
{Log.e(ExtAudioRecorder.class.getName(), "Unknown error occured in prepare()");
}state = State.ERROR;
} } /** * ** Releases the resources associated with this class, and removes the unnecessary files, when necessary
* */public void release()
{if (state == State.RECORDING)
{stop();
} else {if ((state == State.READY) & (rUncompressed))
{try
{
randomAccessWriter.close(); // Remove prepared file
}
catch (IOException e)
{
e.printStackTrace();
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
}
(new File(filePath)).delete();
} }if (rUncompressed)
{if (audioRecorder != null)
{audioRecorder.release();
} } else {if (mediaRecorder != null)
{mediaRecorder.release();
} } } /** * ** Resets the recorder to the INITIALIZING state, as if it was just created.
* In case the class was in RECORDING state, the recording is stopped.
* In case of exceptions the class is set to the ERROR state.
* */public void reset()
{ try {if (state != State.ERROR)
{release();
filePath = null; // Reset file path
cAmplitude = 0; // Reset amplitude
if (rUncompressed)
{
audioRecorder = new AudioRecord(aSource, sRate, nChannels+1, aFormat, bufferSize);
}
else
{
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
}
state = State.INITIALIZING;
} }catch (Exception e)
{e.printStackTrace();
Log.e(ExtAudioRecorder.class.getName(), e.getMessage());
state = State.ERROR;
} } /** * ** Starts the recording, and sets the state to RECORDING.
* Call after prepare().
* */public void start()
{if (state == State.READY)
{if (rUncompressed)
{payloadSize = 0;
audioRecorder.startRecording();
audioRecorder.read(buffer, 0, buffer.length);
}else
{mediaRecorder.start();
}state = State.RECORDING;
} else {Log.e(ExtAudioRecorder.class.getName(), "start() called on illegal state");
state = State.ERROR;
} } /** * ** Stops the recording, and sets the state to STOPPED.
* In case of further usage, a reset is needed.
* Also finalizes the wave file in case of uncompressed recording.
* */public void stop()
{if (state == State.RECORDING)
{if (rUncompressed)
{audioRecorder.stop();
try
{
randomAccessWriter.seek(4); // Write size to RIFF header
randomAccessWriter.writeInt(Integer.reverseBytes(36+payloadSize));
randomAccessWriter.seek(40); // Write size to Subchunk2Size field
randomAccessWriter.writeInt(Integer.reverseBytes(payloadSize));
randomAccessWriter.close();
}
catch(IOException e)
{
e.printStackTrace();
Log.e(ExtAudioRecorder.class.getName(), "I/O exception occured while closing output file");
state = State.ERROR;
}
}else
{mediaRecorder.stop();
}state = State.STOPPED;
} else {Log.e(ExtAudioRecorder.class.getName(), "stop() called on illegal state");
state = State.ERROR;
} } /* ** Converts a byte[2] to a short, in LITTLE_ENDIAN format
* */private short getShort(byte argB1, byte argB2)
{return (short)(argB1 | (argB2 << 8));
} }在开始录音的地方代码如下:
extRecorder=ExtAudioRecorder.getInstanse(false);//设置为false,录制wav
extRecorder.setOutputFile(tempPath); //输出SD卡路径
extRecorder.prepare();
extRecorder.start();
在停止录音的地方代码如下:
extRecorder.stop();
extRecorder.release();得到wav文件后,就可以开始lame转换mp3了,如下:
首先,导入相关lame的包,baidu和google都可以搜到lame的库文件,截图如下:
添加LameActivity.java文件,进行mp3的合成操作,LameActivity.java代码如下:
package cn.itcast.lame;
import java.io.File;
import com.example.util.FileUtil;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
public class LameActivity extends Activity {
private ProgressDialog pd;
private String tempPath;
private String realPath;
static{System.loadLibrary("mp3lame"); //加载mp3lame库文件
}public native String getVersion();
public native void Convert(String wav,String mp3);
@Overridepublic void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
pd = new ProgressDialog(this);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
Intent intent = getIntent();
tempPath = intent.getStringExtra("tempPath");
realPath = intent.getStringExtra("realPath");
//显示具体进度的进度条对话框
convert(null); //合成MP3语音
}public void getlameversion(View view){
String version = getVersion();
Toast.makeText(this, version, Toast.LENGTH_SHORT).show();
}public void convert(View view){
final String wav = tempPath;
final String mp3 = realPath;
File wavfile = new File(wav);
if(wavfile.exists()){
int length = (int) wavfile.length();
pd.setMax(length);
pd.show();
new Thread(){
public void run() {
Convert(wav, mp3);
FileUtil.deleteTempFile(tempPath);
pd.dismiss();
setResult(100);
finish();
};
}.start();
}else{Log.i("Debug", "合成MP3文件不存在");
finish();
return;
} }public void setPDProgress(int progress){
pd.setProgress(progress);
} }利用此文件就可以进行合成mp3,由于项目中涉及业务逻辑的问题比较敏感,只把lame的使用部分进行记录,给遇到同样问题的童鞋们一个参考。
From:http://www.tuicool.com/articles/6fmeqmJ
更多相关文章
- android如何获取SD卡上的多媒体文件
- AndroidManifest.xml文件详解(uses-sdk)
- Android java List 转Json格式
- 修改 android 手机 hosts 文件的方法
- android 使用post方式上传文件
- Android NDK学习(5)调用.so文件
- Android使用GET_CONTENT Action获取打开相应文件类型的应用