Android allows you to pause recording.

Source: Internet
Author: User

Android allows you to pause recording.

 

I haven't updated my blog for a long time. I am a little embarrassed. I will keep updating my blog no matter whether I am busy or busy!

I officially entered the topic. Today I am sharing a piece of technical difficulties I encountered during my work and my solutions to these difficulties. This problem has plagued me for a long time, through continuous research and reading of materials, I finally solve this problem to meet my work needs. I hope my experience will be helpful to readers. We know that the Android ApI provides MediaRecorder and AudioRecord classes for developers to easily achieve audio and video recording (the former can achieve audio and video recording, and the latter can only achieve audio recording ). Both of these classes provide the start () and stop () methods for starting and ending audio or video recording, but it is puzzling that neither of them provides pause () this method is used to pause the recording of audio and video. In practice, it is necessary to pause the recording function. It is unclear how Google engineers did not consider this method when designing APIs, there may be another xuanjicang. Now that Android does not provide such a method, we can only implement it ourselves, the question is, how can we pause audio recording? Don't worry. First, let's talk about my requirements at work, as shown below:Audio recording must be paused and the generated audio file must be in the m4a format.. Why must audio files in the project be In the m4a format? There are several reasons:

1. when recording audio at the same time, the size of the files stored in the m4a format is smaller than that of the files stored in other formats, in the case of the same sampling rate of 16000, the audio is usually recorded for 5 minutes. The audio file stored in the m4a format is only 1.2 Mb, generally, 2-5 Mb is used for arm, mp3, and other formats. In this way, when you need to download or upload the recorded audio file, you can save traffic, in addition, with the same compression ratio, the audio quality in m4a format is higher than that in other formats;
2. the product has both an Android client and an IOS client. In order to prevent users who use the Android client from downloading the recorded audio to the server from playing the audio, we need to unify the storage format of the recorded audio. Since the officially recommended audio format for Iphone phones is m4a and supports audio files in m4a format, we chose m4a as the storage format for audio files.

After explaining why the audio recording file must use the m4a storage format, let's solve how to pause the recording. As mentioned above, the Android sdk api provides the MediaRecorder and AudioRecord classes to complete the recording of audio and video. Let's take a look at the features and differences between the two:

 

MediaRecorder:

Features: This class integrates recording, encoding, compression, and other functions. You can directly generate audio files in various formats (such as arm, mp3, or m4a) based on the set encoding format parameters ), because of its high integration, it is easy to use, but its flexibility is not high, so it cannot implement real-time audio processing like AudioRecord.

AudioRecord:

Feature: the audio recorded in this class is the original PCM binary audio data. If there is no file header or end, the generated PCM file cannot be played directly using Mediaplayer and can only be played using AudioTrack. You can use AudioRecord to Process audio recorded and played in real time.

 

After learning about the features of these two classes, I initially decided to use the MediaRecorder class to solve the recording pause problem. The specific ideas are as follows:

(1)Each time you trigger an event to start recording or pause recording audio, an audio file in the m4a format is stored until the event that stops recording audio is triggered, merge the previously recorded audio files in m4a format into one file. Below:

 

 

This method is easy to understand and think of. However, a technical difficulty is encountered in the implementation process, that is, the combination of multiple audio files in the m4a format does not simply copy the content of the file to a file, but analyzes each audio file in the m4a format, calculate the structure size of each file header, remove the file header, and copy and merge the file. After reading the materials, it is found that the audio file header in m4a format is composed of multiple ATOM structures, and the file size of each audio file in different m4a formats is different, this makes it complicated to parse and Merge multiple m4a file header files. If multiple m4a files need to be merged, it will be time-consuming. Furthermore, developers who do not have sufficient experience in audio/video file parsing and CODEC should accurately parse an m4a file, which is too challenging (there are few materials on the Internet ), interested readers can conduct in-depth research.

The above method does not work, so I had to leave it up. Later I thought of another method, which is also the Final Solution for solving the problem. The specific ideas are as follows:

(2)Because the audio recorded using the method provided by the AudioRecord class is binary data in the original PCM format, the file in this format has no file header information, therefore, when merging files, we do not need to parse the file structure and remove the corresponding file header, so that we can simply copy and merge binary data. The method I implemented here is to constantly write the recorded binary audio data to the same file while recording the audio. When the pause recording event is triggered, stop recording and stop writing binary data. When the recording event is triggered, continue recording and write data to the file. When data writing is stopped, the PCM binary audio file is encoded into an m4a audio file. Below:

 

 

In the above method description, the function of recording and writing at the same time is relatively simple. The key and difficulty is how to encode PCM binary data into the target audio data in the m4a format and enable audio/video encoding/decoding, generally, third-party open-source codec libraries are used. FFMpeg and Speex are well-known. These libraries provide a complete solution for recording, conversion, and streaming audio and video, however, I only need to simply implement coding. Using these open-source libraries is too large, and it seems a bit cool. Therefore, through research and access to information, I found a very useful codec open source project android-aac-enc on github (Address: https://github.com/timsu/android-aac-enc ), this open-source project can perfectly encode the binary data in the original pcm format into a data file in the m4a format. Compared with the FFmpeg library, this library has the following advantages:

1. The aac-enc library is smaller than the FFmpeg library;

2. Compared with FFMpeg, aac-enc implements easier and faster format conversion;

3. aac-enc requires less underlying code than FFmpeg.

This open-source project is also very simple to use. By analyzing its sample code, we can use the following four steps to implement audio coding. The Code is as follows:

 

/*** 1. initial encoding configuration ** 32000: audio bit rate ** 2: audio channel * sampleRateInHz: audio sampling rate * 16: audio data format, PCM 16-bit each sample * FileUtils. getAAcFilePath (mAudioRecordFileName): storage path of aac audio files */encoder. init (32000, 2, sampleRateInHz, 16, FileUtils. getAAcFilePath (mAudioRecordFileName);/*** 2. encode the binary code ** B: the binary audio stream to be encoded */encoder. encode (B);/*** 3. transcoding from pcm binary data to aac audio file completed **/encoder. uninit ();/*** 4. transcode the aac file to an m4a file ** FileUtils. getAAcFilePath (mAudioRecordFileName): Path of the aac file to be encoded * FileUtils. getM4aFilePath (mAudioRecordFileName): The target path encoded into the m4a file */new AACToM4A (). convert (mContext, FileUtils. getAAcFilePath (mAudioRecordFileName), FileUtils. getM4aFilePath (mAudioRecordFileName ));
Is it easy to use? We don't need to judge and parse the audio file format and file header, you can quickly convert the original binary PCM audio data to an m4a audio data file by calling the api encapsulated by this open-source project. Interested readers can study the source code of the project and learn about its internal implementation. This article will not be further explored here.

 

After clarifying the ideas and coding implementation methods, the next step is the specific implementation process. We will implement an audio recording Demo with the pause function based on the above ideas and methods. First, let's take a look at the Demo project structure, such:

There is a lot of information about how to use the AudioRecord class to achieve audio recording. You can first learn and simply enter the door. Next, let's run the Demo to see the following:

(1) initial interface (2) recording interface (2) pause Interface

(4) playback interface (5) pause playback Interface

After a rough look at the running of the Demo, we will implement it next. here we need to use the aac-encode project to implement audio encoding, you need to integrate the project into our Demo in the form of a library. After completing this task, we can write other related logic code in the Demo project, the key code for implementing the demo is RecordAct. the code in the java file, which is the main interface class, mainly implements interface initialization, audio recording, and audio playback functions. The specific code is as follows:

 

Public class RecordAct extends Activity implements OnClickListener {/*** Status: Initial recording Status */private static final int STATUS_PREPARE = 0;/*** Status: recording in progress */private static final int STATUS_RECORDING = 1;/*** Status: Pause recording */private static final int STATUS_PAUSE = 2;/*** Status: initial playback Status */private static final int STATUS_PLAY_PREPARE = 3;/*** Status: Playing */private static final int STATUS_PLAY_PLAY ING = 4;/*** Status: Paused playback */private static final int STATUS_PLAY_PAUSE = 5; private int status = STATUS_PREPARE;/*** recording time */private TextView tvRecordTime; /*** recording button */private ImageView btnRecord; // recording button private PopupWindow popAddWindow;/*** audition interface */private LinearLayout layoutListen; /***** recording length */private TextView tvLength; private TextView recordContinue;/***** reset button */private View resetR Ecord;/*** End recording */private View recordOver; private ImageView audioRecordNextImage; private TextView audioRecordNextText;/*** audio playback progress */private TextView tvPosition; long startTime = 0; /*** maximum recording length */private static final int MAX_LENGTH = 300*1000; private Handler handler = new Handler (); private Runnable runnable; /*** total audio recording length */private static int voiceLength;/*** audio recording Help class */private Au DioRecordUtils mRecordUtils;/***** playback progress bar */private SeekBar seekBar;/***** audio playback class */private Player player;/***** audio file name */private String audioRecordFileName; @ Overrideprotected void onCreate (Bundle savedInstanceState) {// TODO Auto-generated method stubsuper. onCreate (savedInstanceState); setContentView (R. layout. pop_add_record); initView ();} public void initView () {// name of the audio recording file audioRecordFileName = Time Utils. getTimestamp (); // initialize the audio recording object mRecordUtils = new AudioRecordUtils (this, audioRecordFileName); View view = LayoutInflater. from (this ). inflate (R. layout. pop_add_record, null); tvRecordTime = (TextView) findViewById (R. id. TV _time); btnRecord = (ImageView) findViewById (R. id. iv_btn_record); btnRecord. setOnClickListener (this); recordContinue = (TextView) findViewById (R. id. record_continue_txt); resetRecord = findVi EwById (R. id. btn_record_reset); recordOver = findViewById (R. id. btn_record_complete); resetRecord. setOnClickListener (this); recordOver. setOnClickListener (this); audioRecordNextImage = (ImageView) findViewById (R. id. recrod_complete_img); audioRecordNextText = (TextView) findViewById (R. id. record_complete_txt); layoutListen = (LinearLayout) findViewById (R. id. layout_listen); tvLength = (TextView) findViewById (R. id. TV _length); tvPosition = (TextView) findViewById (R. id. TV _position); seekBar = (SeekBar) findViewById (R. id. seekbar_play); seekBar. setOnSeekBarChangeListener (new SeekBarChangeEvent (); seekBar. setEnabled (false); player = new Player (seekBar, tvPosition); player. setMyPlayerCallback (new MyPlayerCallback () {@ Overridepublic void onPrepared () {seekBar. setEnabled (true) ;}@ Overridepublic void onCompletion () {status = STATUS_PLAY_PREPARE; seekBar. setEnabled (false); seekBar. setProgress (0); tvPosition. setText (00:00); recordContinue. setBackgroundResource (R. drawable. record_audio_play) ;}}); popAddWindow = new PopupWindow (view, LayoutParams. MATCH_PARENT, LayoutParams. MATCH_PARENT); popAddWindow. setFocusable (true); popAddWindow. setAnimationStyle (R. style. pop_anim); popAddWindow. setBackgroundDrawable (new BitmapDrawable ();} pub Lic void handleRecord () {switch (status) {case STATUS_PREPARE: mRecordUtils. startRecord (); btnRecord. setBackgroundResource (R. drawable. record_round_red_bg); status = STATUS_RECORDING; voiceLength = 0; timing (); break; case STATUS_RECORDING: pauseAudioRecord (); resetRecord. setVisibility (View. VISIBLE); recordOver. setVisibility (View. VISIBLE); btnRecord. setBackgroundResource (R. drawable. record_round_blue_bg); recordCo Ntinue. setVisibility (View. VISIBLE); status = STATUS_PAUSE; break; case STATUS_PAUSE: mRecordUtils. startRecord (); resetRecord. setVisibility (View. INVISIBLE); recordOver. setVisibility (View. INVISIBLE); btnRecord. setBackgroundResource (R. drawable. record_round_red_bg); recordContinue. setVisibility (View. INVISIBLE); status = STATUS_RECORDING; timing (); break; case STATUS_PLAY_PREPARE: player. playUrl (FileUtils. getM4aFil EPath (audioRecordFileName); recordContinue. setBackgroundResource (R. drawable. record_audio_play_pause); status = STATUS_PLAY_PLAYING; break; case STATUS_PLAY_PLAYING: player. pause (); recordContinue. setBackgroundResource (R. drawable. record_audio_play); status = STATUS_PLAY_PAUSE; break; case STATUS_PLAY_PAUSE: player. play (); recordContinue. setBackgroundResource (R. drawable. record_audio_play_pause); status = STATU S_PLAY_PLAYING; break ;}/ *** pause recording */public void pauseAudioRecord () {mRecordUtils. pauseRecord (); if (handler! = Null & runnable! = Null) {handler. removeCallbacks (runnable); runnable = null;}/*** stop recording */public void stopAudioRecord () {pauseAudioRecord (); mRecordUtils. stopRecord (); status = STATUS_PLAY_PREPARE; showListen ();}/*** record parameter initialization */@ SuppressLint (NewApi) public void resetAudioRecord () {// stop playing the audio player. stop (); pauseAudioRecord (); mRecordUtils. reRecord (); status = STATUS_PREPARE; voiceLength = 0; tvRecordTime. setTextColor (Color. WH ITE); tvRecordTime. setText (TimeUtils. convertMilliSecondToMinute2 (voiceLength); recordContinue. setText (R. string. record_continue); recordContinue. setBackground (null); recordContinue. setVisibility (View. GONE); layoutListen. setVisibility (View. GONE); tvRecordTime. setVisibility (View. VISIBLE); audioRecordNextImage. setImageResource (R. drawable. btn_record_icon_complete); audioRecordNextText. setText (R. string. record _ Over); btnRecord. setBackgroundResource (R. drawable. record_round_blue_bg); resetRecord. setVisibility (View. INVISIBLE); recordOver. setVisibility (View. INVISIBLE);}/*** timing function */private void timing () {runnable = new Runnable () {@ Overridepublic void run () {voiceLength + = 100; if (voiceLength >=( MAX_LENGTH-10*1000) {tvRecordTime. setTextColor (getResources (). getColor (R. color. red_n);} else {tvRecordTime. se TTextColor (Color. WHITE);} if (voiceLength> MAX_LENGTH) {stopAudioRecord ();} else {tvRecordTime. setText (TimeUtils. convertMilliSecondToMinute2 (voiceLength); handler. postDelayed (this, 100) ;}}; handler. postDelayed (runnable, 100) ;}@ Overridepublic void onClick (View v) {// TODO Auto-generated method stubswitch (v. getId () {case R. id. iv_btn_record: handleRecord (); break; case R. id. btn_record_reset: resetAudio Record (); break; case R. id. btn_record_complete: stopAudioRecord (); break; default: break;}/** display playback interface */private void showListen () {layoutListen. setVisibility (View. VISIBLE); tvLength. setText (TimeUtils. convertMilliSecondToMinute2 (voiceLength); tvRecordTime. setVisibility (View. GONE); resetRecord. setVisibility (View. VISIBLE); recordOver. setVisibility (View. INVISIBLE); recordContinue. setVisibility (View. VISIBLE); s EekBar. setProgress (0); tvPosition. setText (00:00); btnRecord. setBackgroundResource (R. drawable. record_round_blue_bg); recordContinue. setText (null); recordContinue. setBackgroundResource (R. drawable. record_audio_play);}/***** SeekBar progress bar changes the event listening class */class SeekBarChangeEvent implements SeekBar. onSeekBarChangeListener {int progress; @ Overridepublic void onProgressChanged (SeekBar seekBar, int progress, boolean fro MUser) {if (null! = Player & player. mediaPlayer! = Null) {this. progress = progress * player. mediaPlayer. getDuration ()/seekBar. getMax (); tvPosition. setText (TimeUtils. convertMilliSecondToMinute2 (player. currentPosition) ;}@ Overridepublic void onStartTrackingTouch (SeekBar seekBar) {}@ Overridepublic void onStopTrackingTouch (SeekBar seekBar) {if (player. mediaPlayer! = Null) {player. mediaPlayer. seekTo (progress) ;}}@ Overrideprotected void onDestroy () {// TODO Auto-generated method stubsuper. onDestroy (); player. stop ();}}

The above code comments are clear and easy to understand, so there is not much analysis, and readers will learn it on their own. Next let's take a look at the AudioRecordUtils class code, which is the main implementation code of the audio recording function. It simply encapsulates several methods to start recording, pause recording, stop recording, and record recording, in development, you only need to call the code to see the specific implementation code, as shown below:

 

Public class AudioRecordUtils {private final int audioSource = MediaRecorder. audioSource. MIC; // set the audio sampling rate. 44100 is the current standard, but some devices still support 11025, 16000, private final int sampleRateInHz =; // set the audio recording channel CHANNEL_IN_STEREO to dual-channel, and CHANNEL_CONFIGURATION_MONO to single-channel private final int channelConfig = AudioFormat. CHANNEL_IN_STEREO; // Audio Data Format: PCM 16-bit each sample. Ensure device support. PCM 8-bit each sample. Not necessarily supported by devices. Private final int audioFormat = AudioFormat. ENCODING_PCM_16BIT; private int inBufSize = 0; private AudioRecord audioRecord; private AACEncoder encoder = null; private ProgressDialog mProgressDialog = null; private boolean isRecord = false; private Context mContext; /*** name of the recorded audio file */private String mAudioRecordFileName; private static final int ready = 0; private static final int RECORDED_COMPLETED_DELETE = 1; public AudioRecordUtils (Context context, String audioRecordFileName) {mContext = context; mAudioRecordFileName = audioRecordFileName; initAudioRecord () ;}/ *** initialization object */private void initAudioRecord () {inBufSize = AudioRecord. getMinBufferSize (bytes, channelConfig, audioFormat); audioRecord = new AudioRecord (audioSource, channels, channelConfig, audioFormat, inBufSize); encoder = new AACEncoder (); deleteAllFiles (bytes ); mProgressDialog = new ProgressDialog (mContext); mProgressDialog. setProgressStyle (ProgressDialog. STYLE_SPINNER); mProgressDialog. setCanceledOnTouchOutside (false); mProgressDialog. setCancelable (false); mProgressDialog. setTitle (prompt); mProgressDialog. setMessage (the recording is being saved. Please wait ......);} /*** start recording */public void startRecord () {new audiorecordtask(.exe cute () ;}/ *** pause recording */public void pauseRecord () {isRecord = false ;} /*** stop recording */public void stopRecord () {new audioencodertask(.exe cute () ;}/ *** record */public void reRecord () {// when rerecording, delete all files in the recording folder deleteAllFiles (RECORDED_INIT_DELETE);} private void encodeAudio () {try {// read the recorded pcm audio file DataInputStream mDataInputStream = new DataInputStream (new FileInputStream (FileUtils. getPcmFilePath (mAudioRecordFileName); byte [] B = new byte [(int) new File (FileUtils. getPcmFilePath (mAudioRecordFileName )). length ()]; mDataInputStream. read (B); // initialize the encoding configuration encoder. init (32000, 2, sampleRateInHz, 16, FileUtils. getAAcFilePath (mAudioRecordFileName); // encode the binary code encoder. encode (B); // encode encoder. uninit (); // close the stream mDataInputStream. close (); try {// transcode the aac file to the m4a file new AACToM4A (). convert (mContext, FileUtils. getAAcFilePath (mAudioRecordFileName), FileUtils. getM4aFilePath (mAudioRecordFileName);} catch (IOException e) {Log. e (ERROR, error converting, e);} deleteAllFiles (RECORDED_COMPLETED_DELETE);} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke. printStackTrace ();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace () ;}} class AudioRecordTask extends AsyncTask
 
  
{@ Overrideprotected Void doInBackground (Void... params) {// TODO Auto-generated method stubif (audioRecord = null) {random ();} RandomAccessFile mRandomAccessFile = null; try {mRandomAccessFile = new RandomAccessFile (new File (FileUtils. getPcmFilePath (mAudioRecordFileName), rw); byte [] B = new byte [inBufSize/4]; // start recording audio audioRecord. startRecording (); // determines whether isRecord is being recorded = true; while (isRecord) {audioRecord. read (B, 0, B. length); // Append content to the file mRandomAccessFile. seek (mRandomAccessFile. length (); mRandomAccessFile. write (B, 0, B. length);} // stop recording audioRecord. stop (); mRandomAccessFile. close ();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke. printStackTrace ();} catch (IOException e) {// TODO Auto-generated catch blocke. printStackTrace ();} return null;} class AudioEncoderTask extends AsyncTask
  
   
{@ Overrideprotected void onPreExecute () {// TODO Auto-generated method stubsuper. onPreExecute (); if (mProgressDialog! = Null &&! MProgressDialog. isShowing () {mProgressDialog. show () ;}@ Overrideprotected Long doInBackground (Void... params) {// TODO Auto-generated method stubencodeAudio (); return null ;}@ Overrideprotected void onPostExecute (Long result) {// TODO Auto-generated method stubsuper. onPostExecute (result); if (mProgressDialog. isShowing () {mProgressDialog. cancel (); mProgressDialog. dismiss () ;}}/ *** clear all files in the audio recording folder * @ param IsRecorded */public void deleteAllFiles (int isRecorded) {File [] files = new File (FileUtils. getAudioRecordFilePath ()). listFiles (); switch (isRecorded) {case RECORDED_INIT_DELETE: for (File file: files) {file. delete ();} break; case RECORDED_COMPLETED_DELETE: for (File file: files) {if (! File. getName (). equals (mAudioRecordFileName + Constants. M4A_SUFFIX) {file. delete () ;}} break; default: break ;}}}
  
 

The key points of the above Code are annotated and readers can learn it on their own. Since then, we have basically been familiar with the key code for implementing the pause recording function. The code is not all pasted out. If you want a complete Demo, you can download it at the end of the article for further research. Finally, I would like to add that if the reader has no strict requirements on the recorded audio format, for example, if the recorded audio format is arm, there is no need to consider the audio codec issue, because the file header information of an audio file in arm format is fixed to 6 bytes, in this case, the first method mentioned at the beginning of the article can be used, this means that each click pause event is recorded as an arm file. During the final merge, you only need to remove the first 6 bytes of the 2nd to n files, then copy and merge the files,

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.