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的使用部分进行记录,给遇到同样问题的童鞋们一个参考。等有时间了会上传部分源码附件。




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的使用部分进行记录,给遇到同样问题的童鞋们一个参考。等有时间了会上传部分源码附件。