您的位置:首页 > 移动开发 > Android开发

Android和IOS录制mp3语音文件的方法

2016-06-16 08:57 525 查看
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 = [[NSString stringWithFormat:@"%@/%@.mp3", DOCUMENTS_FOLDER, @"temp"] retain]; //新转换mp3文件路径

//进入转换

int read, write;

FILE *pcm = fopen([recorderFilePath cStringUsingEncoding:1], "rb");//被转换的文件

FILE *mp3 = fopen([mp3AudioPath cStringUsingEncoding:1], "wb");//转换后文件的存放位置

const int PCM_SIZE = 8192;

const int MP3_SIZE = 8192;

short int pcm_buffer[PCM_SIZE*2];

unsigned char mp3_buffer[MP3_SIZE];

lame_t lame = 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(short int), 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);

@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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS 安卓 音频 跨平台