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写的。

Android和IOS录制mp3语音文件的方法_第1张图片

在开发过程中,由于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的库文件,截图如下:

Android和IOS录制mp3语音文件的方法_第2张图片 Android || IOS录制mp3语音文件方法 - 暗夜幽狼 - 上古传说之暗夜幽狼

添加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);

@Override

public 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


更多相关文章

  1. android如何获取SD卡上的多媒体文件
  2. AndroidManifest.xml文件详解(uses-sdk)
  3. Android java List 转Json格式
  4. 修改 android 手机 hosts 文件的方法
  5. android 使用post方式上传文件
  6. Android NDK学习(5)调用.so文件
  7. Android使用GET_CONTENT Action获取打开相应文件类型的应用

随机推荐

  1. Android(安卓)Facebook-Rebound弹性动画
  2. Jenkins配置Android深坑
  3. Android-----AppBarLayout 的使用
  4. 2018-01-22 Android(安卓)屏幕完美适配方
  5. 【Android】Android自动开关机实现
  6. Android五层框架驱动编写(完整篇)
  7. Android(安卓)实现对话框圆角功能
  8. Android(安卓)IPC机制(三)在Android(安卓)S
  9. Android(安卓)应用桌面角标显示各厂商规
  10. Android开发学习笔记之 Service 的使用