• 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写的。
    Android || IOS录制mp3语音文件方法 - 暗夜幽狼 - 上古传说之暗夜幽狼Android || IOS录制mp3语音文件方法 - 暗夜幽狼 - 上古传说之暗夜幽狼
    在开发过程中,由于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的库文件,截图如下:
    Android || IOS录制mp3语音文件方法 - 暗夜幽狼 - 上古传说之暗夜幽狼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的使用部分进行记录,给遇到同样问题的童鞋们一个参考。等有时间了会上传部分源码附件。




  • 相关阅读:
    真正VC++.net笔记1系统时间的获取
    真正VC++.net笔记5MessageBox变MessageBoxA?
    Judge Online 系统流程设计
    杂谈1:事情因每个人的参与而不同
    ESX/ESXi 4.1 Update 1 or later 同步NTP
    iSCSI CHAP认证
    JSTL中c:set标签的要点和技巧
    JSTL 判断对象是否为空
    Smartmontools——linux磁盘检测工具
    ECMAScript 对象类型
  • 原文地址:https://www.cnblogs.com/zsw-1993/p/4879642.html
Copyright © 2020-2023  润新知