編輯:關於Android編程
一、ExtAudioRecorder
錄音類,來源於http://stackoverflow.com/questions/4871149/how-to-record-voice-in-wav-formt-using-android
package com.jltxgcy.wavrecord; 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_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); } else { int i=0; do { result = new ExtAudioRecorder( true, AudioSource.MIC, sampleRates[i], AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); } while((++icAmplitude) { // Check amplitude cAmplitude = curSample; } } } else { // 8bit sample size for (int i=0; i cAmplitude) { // Check amplitude cAmplitude = buffer[i]; } } } } catch (IOException e) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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)); } }
講解如何使用錄音類,參考http://i-liger.com/article/android-wav-audio-recording
package com.jltxgcy.wavrecord; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; public class MainActivity extends Activity { private ExtAudioRecorder extAudioRecorder; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); extAudioRecorder = ExtAudioRecorder.getInstanse(false); findViewById(R.id.start).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { extAudioRecorder.setOutputFile("/sdcard/my.wav"); extAudioRecorder.prepare(); extAudioRecorder.start(); } }); findViewById(R.id.stop).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { extAudioRecorder.stop(); extAudioRecorder.release(); } }); } }
三、activity_main.xml
兩個按鈕,一個開始錄音,一個停止錄音
准備寫幾篇博客講解Android的動畫,首先介紹Android動畫的整體輪廓。 Android動畫主要分為三大類 View Animation Drawable An
魅族發布了今年最後一款歷史性新品---魅藍metal,魅藍metal依舊采用了與或卡托(單卡槽雙卡位)設計,且支持雙卡雙待,目前預約的有移動定制版和公開版。
在MaterialDialog 仿Android 5.0原生的AlertDialog樣式的對話框一文中以及詳細介紹了單選/多選對話框的用法,但是在現實開發中,我們可能需要
多線程案例——計時器 這個案例中,屏幕啟動之後,進入如圖所示的界面。 屏幕上有一個文本框用於顯示逝去的時間,此外還有一個“停止計時”按鈕。案例的用例圖如圖所示。 &nbs