首页 > 代码库 > AndroidFM模块学习之录音功能

AndroidFM模块学习之录音功能

前些天分析了一下FM的流程以及主要类,接下来我们分析一下FM的录音功能;

首先看下流程图:

技术分享

Fm录音时,当点击了录音按钮,会发一个广播出去,源码在FMRadioService.java中

<span style="font-family:KaiTi_GB2312;font-size:18px;"> public void startRecording() {

       Log.d(LOGTAG, "In startRecording of Recorder");
       if ((true == mSingleRecordingInstanceSupported) &&
                               (true == mOverA2DP )) {
            Toast.makeText( this,
                      "playback on BT in progress,can't record now",
                       Toast.LENGTH_SHORT).show();
            return;
       }
       sendRecordIntent(RECORD_START);
   }</span>


State状态控制FMRecordingService.javaservice启动与关闭

if (state == 1) {

               Log.d(TAG,"FM ONintent received");

               startService =true;

               context.startService(in);

           }elseif(state == 0){

               Log.d(TAG,"FM OFFintent received");

               startService =false;

               context.stopService(in);

            }

Fm广播接收的action publicstaticfinal StringACTION_FM = "codeaurora.intent.action.FM";

当FMRadioservice类的private void sendRecordServiceIntent(int action)方法发送一个广播并附带一个开关录音的状态int

<span style="font-family:KaiTi_GB2312;font-size:18px;">private void sendRecordServiceIntent(int action) {
       Intent intent = new Intent(ACTION_FM);
       intent.putExtra("state", action);
       intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
       Log.d(LOGTAG, "Sending Recording intent for = " +action);
       getApplicationContext().sendBroadcast(intent);
   }</span>


 State状态控制FMRecordingService.javaservice启动与关闭

<span style="font-family:KaiTi_GB2312;font-size:18px;">public class FMRecordingReceiver extends BroadcastReceiver {

    private static final String TAG = "FMRecordingReceiver";
    public static final String ACTION_FM =
           "codeaurora.intent.action.FM";
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "Received intent: " + action); if((action != null) && action.equals(ACTION_FM)) {
            Log.d(TAG, "FM intent received");
            Intent in = new Intent();
            in.putExtras(intent);
            in.setClass(context, FMRecordingService.class);
            int state = intent.getIntExtra("state", 0);
            boolean startService = true;

            if (state == 1) {Log.d(TAG, "FM ON intent received");
                startService = true;
                context.startService(in);
            } else if(state == 0){
                Log.d(TAG, "FM OFF intent received");
                startService = false;
                context.stopService(in);
            }
        }
   }
}
</span>

Fm接收广播的action

<span style="font-family:KaiTi_GB2312;font-size:18px;"> public static final String ACTION_FM_RECORDING =
                       "codeaurora.intent.action.FM_Recording";
    public static final String ACTION_FM_RECORDING_STATUS =
                "codeaurora.intent.action.FM.Recording.Status";</span>

 

onCreat()方法里注册广播接受机制,一个广播是录音状态,一个是关闭fm状态

<span style="font-family:KaiTi_GB2312;font-size:18px;">public void onCreate() {

        super.onCreate();
        Log.d(TAG, "FMRecording Service onCreate");
        registerRecordingListner();
        registerShutdownListner();
        registerStorageMediaListener();
        
    }</span>

 

onDestroy()方法里写了卸载注册停止录音

public void onDestroy() {
        Log.d(TAG, "FMRecording Service onDestroy");
        if (mFmRecordingOn == true) {
            Log.d(TAG, "Still recording on progress, Stoping it");
            stopRecord();
        }
        unregisterBroadCastReceiver(mFmRecordingReceiver);
        unregisterBroadCastReceiver(mFmShutdownReceiver);
        unregisterBroadCastReceiver(mSdcardUnmountReceiver);
        super.onDestroy();
    }


stopRecord();停止录音

 private void stopRecord() {
        Log.d(TAG, "Enter stopRecord");
        mFmRecordingOn = false;
        if (mRecorder == null)
            return;
        try {
             mRecorder.stop();
             mRecorder.reset();
             mRecorder.release();
             mRecorder = null;
        } catch(Exception e) {
             e.printStackTrace();
        }

        sendRecordingStatusIntent(STOP);
        saveFile();
        stopForeground(true);
        stopClientStatusCheck();
    }


registerShutdownListner();注册接受方法中停止fm录音

private void registerShutdownListner() {
        if (mFmShutdownReceiver == null) {
            mFmShutdownReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
                     Log.d(TAG, "Received intent " +intent);
                     String action = intent.getAction();
                     Log.d(TAG, " action = " +action);
                     if (action.equals("android.intent.action.ACTION_SHUTDOWN")) {
                         Log.d(TAG, "android.intent.action.ACTION_SHUTDOWN Intent received");
                         stopRecord();
                     }
                 }
            };
            IntentFilter iFilter = new IntentFilter();
            iFilter.addAction("android.intent.action.ACTION_SHUTDOWN");
            registerReceiver(mFmShutdownReceiver, iFilter);
        }
    }


获取sd卡有效空间

private static long getAvailableSpace() {
        String state = Environment.getExternalStorageState();
        Log.d(TAG, "External storage state=" + state);
        if (Environment.MEDIA_CHECKING.equals(state)) {
            return PREPARING;
        }
        if (!Environment.MEDIA_MOUNTED.equals(state)) {
            return UNAVAILABLE;
        }

        try {
             File sampleDir = Environment.getExternalStorageDirectory();
             StatFs stat = new StatFs(sampleDir.getAbsolutePath());
             return stat.getAvailableBlocks() * (long) stat.getBlockSize();
        } catch (Exception e) {
             Log.i(TAG, "Fail to access external storage", e);
        }
        return UNKNOWN_SIZE;
    }

主要通过以下方法实现:

FilesampleDir = Environment.getExternalStorageDirectory();

             StatFs stat = newStatFs(sampleDir.getAbsolutePath());

             return stat.getAvailableBlocks() *(long) stat.getBlockSize();

有四种值:

Environment.MEDIA_CHECKING检查sd卡准备读取

Environment.MEDIA_MOUNTED没有sd

UNKNOWN_SIZE sd卡有效空间等于UNKNOWN_SIZE

LOW_STORAGE_THRESHOLD低于存储空间极限值

 

更新显示存储判断提示方法

private boolean updateAndShowStorageHint() {
        mStorageSpace = getAvailableSpace();
        return showStorageHint();
    }

 

发送一个录音状态广播

private void sendRecordingStatusIntent(int status) {
        Intent intent = new Intent(ACTION_FM_RECORDING_STATUS);
        intent.putExtra("state", status);
        Log.d(TAG, "posting intent for FM Recording status as = " +status);
        getApplicationContext().sendBroadcastAsUser(intent, UserHandle.ALL);
    }

getApplicationContext().sendBroadcastAsUser(intent,UserHandle.ALL);

UserHandle.ALL一个类的对象,USER_ALL= -1返回的一个字符串UserHandle{-1}

回调方法,fmradioservice.java回调

mCallbacks.onRecordingStarted();方法

mCallbacks.onRecordingStopped();方法

public void registerFMRecordingStatus()

 

启动录音

private boolean startRecord() {

        Log.d(TAG, "Enter startRecord");
        if (mRecorder != null) { /* Stop existing recording if any */
            Log.d(TAG, "Stopping existing record");
            try {
                 mRecorder.stop();
                 mRecorder.reset();
                 mRecorder.release();
                 mRecorder = null;
            } catch(Exception e) {
                 e.printStackTrace();
            }
        } if (!updateAndShowStorageHint())
            return false;
        long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;
        mRecorder = new MediaRecorder();
        try {
             mRecorder.setMaxFileSize(maxFileSize);
             if(mRecordDuration >= 0)
                mRecorder.setMaxDuration(mRecordDuration);
        } catch (RuntimeException exception) {

        }
        mSampleFile = null;
		
        
        File sampleDir;
<span style="font-family:KaiTi_GB2312;">        </span>if((Environment.getExternalSDStorageState(this).equals(Environment.MEDIA_MOUNTED))){
            sampleDir = new File(Environment.getExternalSDStorageDirectory(), "/FMRecording");
        }else{
            sampleDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/FMRecording");
        }  
       

        if(!(sampleDir.mkdirs() || sampleDir.isDirectory()))
            return false;

        try {
             mSampleFile = File.createTempFile("FMRecording", ".3gpp", sampleDir);
        } catch (IOException e) {
             Log.e(TAG, "Not able to access SD Card");
             Toast.makeText(this, "Not able to access SD Card", Toast.LENGTH_SHORT).show();
        }
        try {
             Log.d(TAG, "AudioSource.FM_RX" +MediaRecorder.AudioSource.FM_RX);
             mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);
             mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
             mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
             mAudioType = "audio/3gpp";
        } catch (RuntimeException exception) {
             Log.d(TAG, "RuntimeException while settings");
             mRecorder.reset();
             mRecorder.release();
             mRecorder = null;
             return false;
        }
        Log.d(TAG, "setOutputFile");
        mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
        try {mRecorder.prepare();
             Log.d(TAG, "start");
             mRecorder.start();
        } catch (IOException e) {
             Log.d(TAG, "IOException while start");
             mRecorder.reset();
             mRecorder.release();
             mRecorder = null;
             return false;
        } catch (RuntimeException e) {
             Log.d(TAG, "RuntimeException while start");
             mRecorder.reset();
             mRecorder.release();
             mRecorder = null;
             return false;
        }
        mFmRecordingOn = true; Log.d(TAG, "mSampleFile.getAbsolutePath() " +mSampleFile.getAbsolutePath());
        mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
             public void onInfo(MediaRecorder mr, int what, int extra) {
                 if ((what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) ||
                     (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)) {
                     if (mFmRecordingOn) {
                         Log.d(TAG, "Maximum file size/duration reached, stopping the recording");
                         stopRecord();
                     }
                     if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
                         // Show the toast.
                         Toast.makeText(FMRecordingService.this,
                                        R.string.FMRecording_reach_size_limit,
                                        Toast.LENGTH_LONG).show();
                     }
                 }
             }
             // from MediaRecorder.OnErrorListenerpublic void one rror(MediaRecorder mr, int what, int extra) {
                 Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
                 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
                     // We may have run out of space on the sdcard.
                     if (mFmRecordingOn) {
                         stopRecord();
                     }
                     updateAndShowStorageHint();
                 }
             }
        });
        mSampleStart = System.currentTimeMillis();
        sendRecordingStatusIntent(START);
        startNotification();
        return true;
    }

录音不为空先设置为停止录音从新设置释放资源

mRecorder!= null

mRecorder.stop();

                 mRecorder.reset();

                 mRecorder.release();

                 mRecorder = null;

 

录音判断存储空间够用不

 if (!updateAndShowStorageHint())

            return false;

 

录音最大值为当前值前去低于存储极限值。

longmaxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;

 

设置录音最大值

mRecorder.setMaxFileSize(maxFileSize);

设置录音持续

mRecorder.setMaxDuration(mRecordDuration);

 

设置录音来源,放置的位置,录音audio格式,audio写入音源编码格式

mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);

mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

mRecorder.setOutputFile(mSampleFile.getAbsolutePath());

 

录音监听mediaRecorder监听

mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener()

what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED

what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED

mFmRecordingOn 为true 停止录音

stopRecord();

录音过程报错停止录音

stopRecord();弹出提示

updateAndShowStorageHint();

 

获取当前时间,发送录音状态广播,启动通知

mSampleStart= System.currentTimeMillis();

        sendRecordingStatusIntent(START);

        startNotification();

 

发送通知,设置远程控制,startForeground(102,status);设置sevice至于前台

 private void startNotification() {
        RemoteViews views = new RemoteViews(getPackageName(), R.layout.record_status_bar);
        Notification status = new Notification();
        status.contentView = views;
        status.flags |= Notification.FLAG_ONGOING_EVENT;
        status.icon = R.drawable.ic_menu_record;
        startForeground(102, status);
    }

停止录音,保存录音文件,停止录音之前台,停止状体切换

 private void stopRecord()

sendRecordingStatusIntent(STOP);

        saveFile();

        stopForeground(true);

        stopClientStatusCheck();

 

保存录音方法(录音一开录,就在往默认内置T中写数据以字节方式写入数据)

private void saveFile() {
        int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
        Log.d(TAG, "Enter saveFile");
        if (sampleLength == 0)
            return;
        String state = Environment.getExternalStorageState();
        Log.d(TAG, "storage state is " + state);

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            try {
                 this.addToMediaDB(mSampleFile);
                 Toast.makeText(this,getString(R.string.save_record_file,
                                               mSampleFile.getAbsolutePath( )),
                                               Toast.LENGTH_LONG).show();
            } catch(Exception e) {
                 e.printStackTrace();
            }
        } else {
            Log.e(TAG, "SD card must have removed during recording. ");
            Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show();
        }
        return;
    }

 

获取当时减去录音起始时间如果为零就跳出保存方法不保存数据

intsampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );

 

Environment.MEDIA_MOUNTED.equals(state)sd卡可用就将录音路径添加到多媒体数据库中

this.addToMediaDB(mSampleFile);

 

将信息添加到多媒体数据库,音频的audio格式存入数据库中

 private Uri addToMediaDB(File file) {
        Log.d(TAG, "In addToMediaDB");
        Resources res = getResources();
        ContentValues cv = new ContentValues();
        long current = System.currentTimeMillis();
        long modDate = file.lastModified();
        Date date = new Date(current);
        SimpleDateFormat formatter = new SimpleDateFormat(
                  res.getString(R.string.audio_db_title_format));
        String title = formatter.format(date);

        // Lets label the recorded audio file as NON-MUSIC so that the file
        // won't be displayed automatically, except for in the playlist.
        cv.put(MediaStore.Audio.Media.IS_MUSIC, "1");cv.put(MediaStore.Audio.Media.TITLE, title);
        cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
        cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
        cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000));
        cv.put(MediaStore.Audio.Media.MIME_TYPE, mAudioType);
        cv.put(MediaStore.Audio.Media.ARTIST,
                res.getString(R.string.audio_db_artist_name));
        cv.put(MediaStore.Audio.Media.ALBUM,
                res.getString(R.string.audio_db_album_name));
        Log.d(TAG, "Inserting audio record: " + cv.toString());ContentResolver resolver = getContentResolver();
        Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        Log.d(TAG, "ContentURI: " + base);
        Uri result = resolver.insert(base, cv);
        if (result == null) {
            Toast.makeText(this, R.string.unable_to_store, Toast.LENGTH_SHORT).show();
            return null;
        }
        if (getPlaylistId(res) == -1) {
            createPlaylist(res, resolver);
        }
        int audioId = Integer.valueOf(result.getLastPathSegment());
        addToPlaylist(resolver, audioId, getPlaylistId(res));

        // Notify those applications such as Music listening to the
        // scanner events that a recorded audio file just created.
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
        return result;
    }

获取audiouri

Uri base =MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

 

添加到播放列表

addToPlaylist(resolver,audioId, getPlaylistId(res));

可以存入music数据

cv.put(MediaStore.Audio.Media.IS_MUSIC,"1")

发送广播扫描

sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));

 

 

privatevoid registerRecordingListner()广播接收者

private void registerRecordingListner() {
        if (mFmRecordingReceiver == null) {
            mFmRecordingReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
                     Log.d(TAG, "Received intent " +intent);
                     String action = intent.getAction();
                     Log.d(TAG, " action = " +action);
                     if (action.equals(ACTION_FM_RECORDING)) {
                         int state = intent.getIntExtra("state", STOP);
                         Log.d(TAG, "ACTION_FM_RECORDING Intent received" + state);
                         if (state == START) {
                             Log.d(TAG, "Recording start");
                             mRecordDuration = intent.getIntExtra("record_duration", mRecordDuration);
                             if(startRecord()) {
                                clientProcessName = intent.getStringExtra("process_name");
                                clientPid = intent.getIntExtra("process_id", -1);
                                startClientStatusCheck();
                             }} else if (state == STOP) {
                             Log.d(TAG, "Stop recording");
                             stopRecord();
                         }
                     }
                 }
            };
            IntentFilter iFilter = new IntentFilter();
            iFilter.addAction(ACTION_FM_RECORDING);
            registerReceiver(mFmRecordingReceiver, iFilter);
        }
    }


广播状态为1的时候就开始录音并检查线程

 private void startClientStatusCheck()

 

获取客户端所有进程信息进行匹配如果启动的app没有被杀死就继续录音否则停止

privateboolean getClientStatus(int pid, String processName)

ActivityManageractvityManager =

                   (ActivityManager)this.getSystemService(

                                                          this.ACTIVITY_SERVICE);

 

      List<RunningAppProcessInfo>procInfos =

                                     actvityManager.getRunningAppProcesses();

 

      for(RunningAppProcessInfo procInfo :procInfos) {

         if ((pid == procInfo.pid)

 &&

             (procInfo.processName.equals(processName))) {

              status = true;

              break;

         }

      }

      procInfos.clear();

 

privateRunnable clientStatusCheckThread = new Runnable()

停止录音后睡眠500毫秒

 

privatevoid stopClientStatusCheck()中断线程mStatusCheckThread

private void stopClientStatusCheck() {
       if(mStatusCheckThread != null) {
          mStatusCheckThread.interrupt();
       }
   }


 


AndroidFM模块学习之录音功能