Android6.0源码分析之录音功能(一)【转】

本文转载自:http://blog.csdn.net/zrf1335348191/article/details/54949549

从现在开始一周时间研究录音,下周出来一个完整的博客,监督,激励!!!

2017-02-09--------2017-02-17

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Android源码录音功能说起来似乎也很简单,只不过就是一个录音的功能然后进行了一个保存的操作。为什么要研究这个呢?毕竟现

在语音通话、直播亦或者是语音助手比较流行,其中其实最基础的还是对录音的一些处理,所以还是有必要研究一下的。

说起来功能简单,但其实单单是录音功能其中又夹杂着一些别的东西,比如UI的实时更新,电话等各种状态的监控,音量大小的监

控。录音的保存也涉及到往手机中写数据,以及以何种格式写数据,比如当前流行的直播,音频流的传输到底以何种格式,手机可以

播放什么样的格式,这些都会涉及。但是手机的原生系统应用录音机不支持文件的播放。

代码所在目录为android\packages\apps\SoundRecorder

先从编译开始说起

chapter one 录音机编译脚本文件-Android.mk

[java] view plain copy

  1. LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE_TAGS := optional
  4. LOCAL_SRC_FILES := $(call all-subdir-java-files)
  5. LOCAL_PACKAGE_NAME := SoundRecorder
  6. LOCAL_PRIVILEGED_MODULE := true
  7. include $(BUILD_PACKAGE)

对于Android.mk文件的详细介绍可参考连接http://www.cnblogs.com/welhzh/p/4532142.html

简单介绍一下

LOCAL_MODULE_TAGS := optional表示模块在任何时候都参与编译

LOCAL_PACKAGE_NAME :=SoundRecorder表示编译完成后生成的应用名为SoundRecorder

LOCAL_PRIVILEGED_MODULD := true表示APP会安装在~/system/priv-app下拥有系统权限

编译脚本文件可以告诉我们APP的名字和APP会安装在哪里,以及APP是否参与编译

chapter two,模块的入口清单配置文件--Androidmanifest.xml

配置文件会告诉我们应用的入口,应用所需的权限以及应用的各种组件,看一个应用的复杂程度其实看配置文件即可

[java] view plain copy

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2. package="com.android.soundrecorder">
  3. <original-package android:name="com.android.soundrecorder" />
  4. <uses-permission android:name="android.permission.RECORD_AUDIO" />
  5. <uses-permission android:name="android.permission.INTERNET" />
  6. <uses-permission android:name="android.permission.WAKE_LOCK" />
  7. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  8. <application android:label="@string/app_name"
  9. android:icon="@drawable/ic_launcher_soundrecorder"
  10. android:usesCleartextTraffic="false">
  11. <activity android:name="SoundRecorder"
  12. android:configChanges="orientation|screenSize|keyboardHidden"
  13. android:screenOrientation="unspecified"
  14. android:clearTaskOnLaunch="true"
  15. android:theme="@style/Theme.SoundRecorder">
  16. <intent-filter>
  17. <action android:name="android.intent.action.MAIN" />
  18. </intent-filter>
  19. <intent-filter>
  20. <action android:name="android.provider.MediaStore.RECORD_SOUND" />
  21. <category android:name="android.intent.category.DEFAULT" />
  22. </intent-filter>
  23. </activity>
  24. </application>
  25. </manifest>

权限有:

1>,android.permission.RECORD_AUDIO:allows an application to record audio允许程序录制音频

2>,android.permission.INTERNET:allows an application to open network sockets允许程序打开网络套接字,即允许程序进行联网

3>,android.permission.WAKE_LOCK: Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming:允许程序使用电源屏幕锁保持手机不进入休眠或者变暗,即在录音时保持屏幕常亮

4>,android.permission.WRITE_EXTERNAL_STORAGE:允许往内存中写入数据

组件有:

该模块就注册了一个activity组件----SoundRecorder

组件下有一个clearTaskOnLaunch属性,由字面意思大家也可以看出来那就是再到launch界面后再次进入APP会清除栈内的activity重新加载,但这也得看系统的一些处理,如果系统按home回到launch就是要销毁所有activity的话那这个字段也没有任何意义了

组件的action为android.provider.MediaStore.RECORD_SOUND三方应用可通过调用该action来调起录音界面,亲测有效~~

[java] view plain copy

chapter three src进入源码

界面布局文件:

main.xml

对应用进行整体浏览后发现录音所涉及到的知识由以下几个

1>,录音计时

2>,录音音量UI设计

3>,开始,暂停,继续录音

4>,停止录音

5>,播放录音

6>,以某种格式保存录音,文件类型支持amr,3gpp,aac,wav

7>,显示录音文件列表

其实总的来说也就是录音然后以某种格式保存的功能,只是在录音的过程中需要根据不同情况对UI进行一个更新。

接下来对单个功能进行分析之前首先是对各个view的id进行一个标记,方便以后对UI上做修改

ID标记完了之后我们基本上涉及到的UI上的修改都可以进行了,既然UI上的更新都是由录音功能衍生出来的,那就先从录音功能说

起,顺带着分析再不同的状态下UI的显示变化。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

通过上述对录音界面的分析可以看出负责各种功能的button有recordButton、playButton、stopButton、acceptButton、

discardButton,还有负责界面UI更新显示的其他textview。对于button的点击事件找到onClick方法。

[java] view plain copy

  1. /*
  2. * Handle the buttons.
  3. */
  4. public void onClick(View button) {
  5. if (!button.isEnabled())
  6. return;
  7. if (Build.VERSION.SDK_INT >= 23) {
  8. String[] operationPermissionNames = getOperationPermissionName(button.getId());
  9. if (operationPermissionNames == null ||
  10. checkOperationPermission(operationPermissionNames, button.getId()))
  11. processClickEvent(button.getId());
  12. } else {
  13. processClickEvent(button.getId());
  14. }
  15. }

当进行button的点击事件进行处理时涉及到一个6.0权限的问题,首先要保证权限已经申请成功,然后在processClickEvent再对各种

点击事件进行处理。

<1>,recordButton录音,中止,继续

对于录音按钮的点击处理逻辑如下:

也就是说在录音按钮时如果想要开启录音首先要经过两次判断

第一是判断是否是中止状态,也就是说录音中止此时点击会继续录音。

第二是判断是否是正在进行录音,如果正在进行,则点击时会中止正在进行的录音

经过以上两步的判断后才会进入录音的准备,但至于录音是否需能够开启成功还需要看后续的判断。

那么如果当前录音处于中止状态的话该如何继续录音呢???

其实继续录音就三件事,一是开启录音,二是对录音进行开始计时,第三就是设置下当前的录音状态为正在录音

[java] view plain copy

  1. public void resumeRecording() {
  2. if (mRecorder == null) {
  3. return;
  4. }
  5. try {
  6. //mRecorder为MeidaRecorder对象,开始录音
  7. mRecorder.start();
  8. } catch (RuntimeException exception) {
  9. setError(INTERNAL_ERROR);
  10. Log.e(TAG, "Resume Failed");
  11. }
  12. //开始录音计时
  13. mSampleStart = System.currentTimeMillis();
  14. //设置当前的状态为正在录音
  15. setState(RECORDING_STATE);
  16. }

那如果当前录音处于正在录音的状态该如何中止录音呢?????

对照上述继续录音的代码可以看到中止录音进行了以下操作:一是暂停当前的录音,二是对本次录音(包括多次暂停和继续)的总时

长进行一个累加并记录入mSampleLength,三是设置录音的状态为暂停的状态

[java] view plain copy

  1. public void pauseRecording() {
  2. if (mRecorder == null) {
  3. return;
  4. }
  5. try {
  6. //mRecorder为MediaRecorder对象,暂停录音
  7. mRecorder.pause();
  8. } catch (RuntimeException exception) {
  9. setError(INTERNAL_ERROR);
  10. Log.e(TAG, "Pause Failed");
  11. }
  12. //记录从开始录音到现在的总的录音时长
  13. mSampleLength = mSampleLength + (System.currentTimeMillis() - mSampleStart);
  14. //设置录音的状态为暂停的状态
  15. setState(PAUSE_STATE);
  16. }

由这两部操作也可以看出来,录音的方法接口为MediaRecorder。在录音的过程中,如果发生了暂停或者继续,要做的除了调用接口

方法进行暂停或者继续的操作,还有就是对录音的时长需要进行一个计算,以及录音的状态进行一个设置。

但不论是暂停或者继续,都是对一个已经存在的录音对象所进行的操作,所以相对还是很简洁的,但想要一个东西从无到有,也就是

说这个创建对象的过程中需要考虑很多东西。所以在 对录音的两个简单的暂停或者继续进行分析后,接下里就开始分析开始录音的

操作。也就是说我们学会了当对象存在时如何操作对象后,就来研究一下如何去新建一个对象。(先不考虑手机播放音乐、来电等其

他audio的情况)。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

开始录音流程图如下:

开始录音的代码如下:

[java] view plain copy

  1. private void startRecord() {
  2. //创建录音对象
  3. mRecorder = new MediaRecorder();
  4. mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  5. mRecorder.setAudioChannels(1);
  6. mRecorder.setAudioSamplingRate(1000);
  7. mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  8. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  9. File sample = new File(Environment.getExternalStorageDirectory().toString()
  10. + "/SoundRecorder");
  11. if (!sample.exists()) {
  12. sample.mkdirs();
  13. }
  14. if (!sample.canWrite()) {
  15. sample = new File("sdcard/sdcard");
  16. }
  17. file = new File(sample, "fang.aac");
  18. //设置文件存储位置
  19. mRecorder.setOutputFile(file.getAbsolutePath());
  20. try {
  21. mRecorder.prepare();
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. mRecorder.reset();
  25. mRecorder.release();
  26. mRecorder = null;
  27. Toast.makeText(getApplicationContext(), "exception", Toast.LENGTH_SHORT).show();
  28. return;
  29. }
  30. mRecorder.start();
  31. //记录录音开始时间
  32. mSampleStart = System.currentTimeMillis();
  33. //更新ui
  34. updateTimeView();
  35. }

停止录音代码如下

[java] view plain copy

  1. private void stopRecord(){
  2. if (mRecorder == null){
  3. return;
  4. }
  5. mHandler.removeCallbacks(updateTime);
  6. mRecorder.stop();
  7. mRecorder.reset();
  8. mRecorder.release();
  9. mSampleStart = 0;
  10. mRecorder = null;
  11. }

<2>,播放录音playbutton

播放录音代码如下:

[java] view plain copy

  1. private void playRecord(){
  2. mPlayer = new MediaPlayer();
  3. try {
  4. mPlayer.setDataSource(file.getAbsolutePath());
  5. mPlayer.prepare();
  6. mPlayer.start();
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }


<3>,录音过程中检测分贝的方法为:

[java] view plain copy

  1. mRecorder.getMaxAmplitude()

接下来贴出一张Android源码中关于录音频/视频的流程图,有需要的快点儿mark一下吧

至于中止和继续录音,系统应用有可以使用的方法,但是不供三方应用使用,所以需要自己实现,其中涉及到编解码的问题,下篇再续!!!

文章到此,基本上录音按钮的功能就分析完毕了。

接下来做个demo来验证下录音按钮的功能

----------------------------

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-07 16:57:10

Android6.0源码分析之录音功能(一)【转】的相关文章

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮

前言 之前写过屏蔽系统导航栏功能的文章,具体可看Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar 在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置.联系人等,就没法退出了,虽然可以在actionBar中添加back按钮,但总不能每一个app都去添加吧.所以灵机一动我们就给系统添加一个全屏可拖拽的浮窗按钮,点击的时候处理返回键的逻辑.它大概长这样(审美可能丑了点,你们可以自由发挥) 图1

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二)

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二) 1.通过mutate(put)操作,将单个put操作添加到缓冲操作中,这些缓冲操作其实就是Put的父类的一个List的集合.如下: private List<Row> writeAsyncBuffer = new LinkedList<>(); writeAsyncBuffer.add(m); 当writeAsyncBuffer满了之后或者是人为的调用backgroundFlushCommits操作促使缓冲池中的

Solr4.8.0源码分析(10)之Lucene的索引文件(3)

Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这两个文件.由于本文介绍的Solr4.8.0,所以对应的是SegmentInfoFormat的子类Lucene46SegmentInfoFormat. 首先来看下.si文件的格式 头部(header) 版本(SegVersion) doc个数(SegSize) 是否符合文档格式(IsCompoundF

Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建

Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建 由于公司里的Solr调试都是用远程jpda进行的,但是家里只有一台电脑所以不能jpda进行调试,这是因为jpda的端口冲突.所以只能在Eclipse 搭建Solr的环境,折腾了一小时终于完成了. 1. JDPA远程调试 搭建换完成Solr环境后,对${TOMCAT_HOME}/bin/startup.sh 最后一行进行修改,如下所示: 1 set JPDA_ADDRESS=7070 2 exec "$PRGDIR"

Solr4.8.0源码分析(25)之SolrCloud的Split流程

Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大致也了解split的原理了,所以也就有了这篇文章.本系列有两篇文章,第一篇为core split,第二篇为collection split. 1. 简介 这里首先需要介绍一个比较容易混淆的概念,其实Solr的HTTP API 和 SolrCloud的HTTP API是不一样,如果接受到的是Solr的

Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五)

Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五) 题记:关于SolrCloud的Recovery策略已经写了四篇了,这篇应该是系统介绍Recovery策略的最后一篇了.本文主要介绍Solr的主从同步复制.它与前文<Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)>略有不同,前文讲到的是SolrCloud的leader与replica之间的同步,不需要通过配置solrconfig.xml来实现.而本文主要介绍单机模式下,利用so

Solr4.8.0源码分析(19)之缓存机制(二)

Solr4.8.0源码分析(19)之缓存机制(二) 前文<Solr4.8.0源码分析(18)之缓存机制(一)>介绍了Solr缓存的生命周期,重点介绍了Solr缓存的warn过程.本节将更深入的来介绍下Solr的四种缓存类型,以及两种SolrCache接口实现类. 1.SolrCache接口实现类 前文已经提到SolrCache有两种接口实现类:solr.search.LRUCache 和 solr.search.LRUCache. 那么两者具体有啥区别呢? 1.1 solr.search.LR

Solr4.8.0源码分析(17)之SolrCloud索引深入(4)

Solr4.8.0源码分析(17)之SolrCloud索引深入(4) 前面几节以add为例已经介绍了solrcloud索引链建索引的三步过程,delete以及deletebyquery跟add过程大同小异,这里暂时就不介绍了.由于commit流程较为特殊,那么本节主要简要介绍下commit的流程. 1. SolrCloud的commit流程 SolrCloud的commit流程同样分为三步,本节主要简单介绍下三步过程. 1.1 LogUpdateProcessor LogUpdateProces