android6.0编程实现双向通话自动录音功能的方法详解(代码片段)

zhujiabin zhujiabin     2023-01-01     475

关键词:

本文实例讲述了Android6.0编程实现双向通话自动录音功能的方法。分享给大家供大家参考,具体如下:

        项目中需要实现基于Android 6.0 的双向通话自动录音功能,在查阅相关android电话状态监听文章以及Git上的开源录音项目后,整理出此文

        首先,介绍一下android 电话状态的监听(来电和去电):
http://www.jb51.net/article/32433.htm

        实现手机电话状态的监听,主要依靠两个类:

        TelephoneManger和PhoneStateListener

        TelephonseManger提供了取得手机基本服务的信息的一种方式。因此应用程序可以使用TelephonyManager来探测手机基本服务的情况。应用程序可以注册listener来监听电话状态的改变。

        我们不能对TelephonyManager进行实例化,只能通过获取服务的形式:

Context.getSystemService(Context.TELEPHONY_SERVICE);

     注意:对手机的某些信息进行读取是需要一定许可(permission)的。
主要静态成员常量:(它们对应PhoneStateListener.LISTEN_CALL_STATE所监听到的内容)

int CALL_STATE_IDLE  //空闲状态,没有任何活动。
int CALL_STATE_OFFHOOK //摘机状态,至少有个电话活动。该活动或是拨打(dialing)或是通话,或是 on hold。并且没有电话是ringing or waiting
int CALL_STATE_RINGING //来电状态,电话铃声响起的那段时间或正在通话又来新电,新来电话不得不等待的那段时间。

项目中使用服务来监听通话状态,所以需要弄清楚手机通话状态在广播中的对应值:

EXTRA_STATE_IDLE //它在手机通话状态改变的广播中,用于表示CALL_STATE_IDLE状态,即空闲状态。
EXTRA_STATE_OFFHOOK //它在手机通话状态改变的广播中,用于表示CALL_STATE_OFFHOOK状态,即摘机状态。
EXTRA_STATE_RINGING //它在手机通话状态改变的广播中,用于表示CALL_STATE_RINGING状态,即来电状态
ACTION_PHONE_STATE_CHANGED //在广播中用ACTION_PHONE_STATE_CHANGED这个Action来标示通话状态改变的广播(intent)。
//注:需要许可READ_PHONE_STATE。
String EXTRA_INCOMING_NUMBER //在手机通话状态改变的广播,用于从extra取来电号码。
String EXTRA_STATE //在通话状态改变的广播,用于从extra取来通话状态。

  如何实现电话监听呢?

        Android在电话状态改变是会发送action为android.intent.action.PHONE_STATE的广播,而拨打电话时会发送action为

public static final String ACTION_NEW_OUTGOING_CALL =
      "android.intent.action.NEW_OUTGOING_CALL";

   的广播。通过自定义广播接收器,接受上述两个广播便可。

        下面给出Java代码:(其中的Toast均为方便测试而添加)

package com.example.hgx.phoneinfo60.Recording;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.widget.Toast;
/**
 * Created by hgx on 2016/6/13.
 */
public class PhoneCallReceiver extends BroadcastReceiver 
  private int lastCallState = TelephonyManager.CALL_STATE_IDLE;
  private boolean isIncoming = false;
  private static String contactNum;
  Intent audioRecorderService;
  public PhoneCallReceiver() 
  
  @Override
  public void onReceive(Context context, Intent intent) 
    //如果是去电
    if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL))
      contactNum = intent.getExtras().getString(Intent.EXTRA_PHONE_NUMBER);
    else //android.intent.action.PHONE_STATE.查了下android文档,貌似没有专门用于接收来电的action,所以,非去电即来电.
    
      String state = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
      String phoneNumber = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
      int stateChange = 0;
      if (state.equals(TelephonyManager.EXTRA_STATE_IDLE))
        //空闲状态
        stateChange =TelephonyManager.CALL_STATE_IDLE;
        if (isIncoming)
          onIncomingCallEnded(context,phoneNumber);
        else 
          onOutgoingCallEnded(context,phoneNumber);
        
      else if (state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
        //摘机状态
        stateChange = TelephonyManager.CALL_STATE_OFFHOOK;
        if (lastCallState != TelephonyManager.CALL_STATE_RINGING)
          //如果最近的状态不是来电响铃的话,意味着本次通话是去电
          isIncoming =false;
          onOutgoingCallStarted(context,phoneNumber);
        else 
          //否则本次通话是来电
          isIncoming = true;
          onIncomingCallAnswered(context, phoneNumber);
        
      else if (state.equals(TelephonyManager.EXTRA_STATE_RINGING))
        //来电响铃状态
        stateChange = TelephonyManager.CALL_STATE_RINGING;
        lastCallState = stateChange;
        onIncomingCallReceived(context,contactNum);
      
    
  
  protected void onIncomingCallStarted(Context context,String number)
    Toast.makeText(context,"Incoming call is started",Toast.LENGTH_LONG).show();
    context.startService(new Intent(context,AudioRecorderService.class));
  
  protected void onOutgoingCallStarted(Context context,String number)
    Toast.makeText(context, "Outgoing call is started", Toast.LENGTH_LONG).show();
    context.startService(new Intent(context, AudioRecorderService.class));
  
  protected void onIncomingCallEnded(Context context,String number)
    Toast.makeText(context, "Incoming call is ended", Toast.LENGTH_LONG).show();
    context.startService(new Intent(context, AudioRecorderService.class));
  
  protected void onOutgoingCallEnded(Context context,String number)
    Toast.makeText(context, "Outgoing call is ended", Toast.LENGTH_LONG).show();
    context.startService(new Intent(context, AudioRecorderService.class));
  
  protected void onIncomingCallReceived(Context context,String number)
    Toast.makeText(context, "Incoming call is received", Toast.LENGTH_LONG).show();
  
  protected void onIncomingCallAnswered(Context context, String number) 
    Toast.makeText(context, "Incoming call is answered", Toast.LENGTH_LONG).show();
  

  下面是AudioRecorderService的java实现:

package com.example.hgx.phoneinfo60.Recording;
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.IBinder;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
import com.example.hgx.phoneinfo60.MyApplication;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
 * Created by hgx on 2016/6/13.
 */
public class AudioRecorderService extends Service 
  private static int RECORD_RATE = 0;
  private static int RECORD_BPP = 32;
  private static int RECORD_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
  private static int RECORD_ENCODER = AudioFormat.ENCODING_PCM_16BIT;
  private AudioRecord audioRecorder = null;
  private Thread recordT = null;
  private Boolean isRecording = false;
  private int bufferEle = 1024, bytesPerEle = 2;// want to play 2048 (2K) since 2 bytes we use only 1024 2 bytes in 16bit format
  private static int[] recordRate =44100 , 22050 , 11025 , 8000;
  int bufferSize = 0;
  File uploadFile;
  @Override
  public IBinder onBind(Intent intent) 
    // TODO: Return the communication channel to the service.
    //maintain the relationship between the caller activity and the callee service, currently useless here
    return null;
  
  @Override
  public void onDestroy() 
    if (isRecording)
      stopRecord();
    else
      Toast.makeText(MyApplication.getContext(), "Recording is already stopped",Toast.LENGTH_SHORT).show();
    
    super.onDestroy();
  
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) 
    if (!isRecording)
      startRecord();
    else 
      Toast.makeText(MyApplication.getContext(), "Recording is already started",Toast.LENGTH_SHORT).show();
    
    return 1;
  
  private void startRecord()
    audioRecorder = initializeRecord();
    if (audioRecorder != null)
      Toast.makeText(MyApplication.getContext(), "Recording is started",Toast.LENGTH_SHORT).show();
      audioRecorder.startRecording();
    else
      return;
    isRecording = true;
    recordT = new Thread(new Runnable() 
      @Override
      public void run() 
        writeToFile();
      
    ,"Recording Thread");
    recordT.start();
  
  private void writeToFile()
    byte bDate[] = new byte[bufferEle];
    FileOutputStream fos =null;
    File recordFile = createTempFile();
    try 
      fos = new FileOutputStream(recordFile);
     catch (FileNotFoundException e) 
      e.printStackTrace();
    
    while (isRecording)
      audioRecorder.read(bDate,0,bufferEle);
    
    try 
      fos.write(bDate);
     catch (IOException e) 
      e.printStackTrace();
    
    try 
      fos.close();
     catch (IOException e) 
      e.printStackTrace();
    
  
  //Following function converts short data to byte data
  private byte[] writeShortToByte(short[] sData) 
    int size = sData.length;
    byte[] byteArrayData = new byte[size * 2];
    for (int i = 0; i < size; i++) 
      byteArrayData[i * 2] = (byte) (sData[i] & 0x00FF);
      byteArrayData[(i * 2) + 1] = (byte) (sData[i] >> 8);
      sData[i] = 0;
    
    return byteArrayData;
  
  //Creates temporary .raw file for recording
  private File createTempFile() 
    File tempFile = new File(Environment.getExternalStorageDirectory(), "aditi.raw");
    return tempFile;
  
  //Create file to convert to .wav format
  private File createWavFile() 
    File wavFile = new File(Environment.getExternalStorageDirectory(), "aditi_" + System.currentTimeMillis() + ".wav");
    return wavFile;
  
  /*
   * Convert raw to wav file
   * @param java.io.File temporay raw file
   * @param java.io.File destination wav file
   * @return void
   *
   * */
  private void convertRawToWavFile(File tempFile, File wavFile) 
    FileInputStream fin = null;
    FileOutputStream fos = null;
    long audioLength = 0;
    long dataLength = audioLength + 36;
    long sampleRate = RECORD_RATE;
    int channel = 1;
    long byteRate = RECORD_BPP * RECORD_RATE * channel / 8;
    String fileName = null;
    byte[] data = new byte[bufferSize];
    try 
      fin = new FileInputStream(tempFile);
      fos = new FileOutputStream(wavFile);
      audioLength = fin.getChannel().size();
      dataLength = audioLength + 36;
      createWaveFileHeader(fos, audioLength, dataLength, sampleRate, channel, byteRate);
      while (fin.read(data) != -1) 
        fos.write(data);
      
      uploadFile = wavFile.getAbsoluteFile();
     catch (FileNotFoundException e) 
      //Log.e("MainActivity:convertRawToWavFile",e.getMessage());
     catch (IOException e) 
      //Log.e("MainActivity:convertRawToWavFile",e.getMessage());
     catch (Exception e) 
      //Log.e("MainActivity:convertRawToWavFile",e.getMessage());
    
  
  /*
  * To create wav file need to create header for the same
  *
  * @param java.io.FileOutputStream
  * @param long
  * @param long
  * @param long
  * @param int
  * @param long
  * @return void
  */
  private void createWaveFileHeader(FileOutputStream fos, long audioLength, long dataLength, long sampleRate, int channel, long byteRate) 
    byte[] header = new byte[44];
    header[0] = R; // RIFF/WAVE header
    header[1] = I;
    header[2] = F;
    header[3] = F;
    header[4] = (byte) (dataLength & 0xff);
    header[5] = (byte) ((dataLength >> 8) & 0xff);
    header[6] = (byte) ((dataLength >> 16) & 0xff);
    header[7] = (byte) ((dataLength >> 24) & 0xff);
    header[8] = W;
    header[9] = A;
    header[10] = V;
    header[11] = E;
    header[12] = f; // ‘fmt ‘ chunk
    header[13] = m;
    header[14] = t;
    header[15] =  ;
    header[16] = 16; // 4 bytes: size of ‘fmt ‘ chunk
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    header[20] = 1; // format = 1
    header[21] = 0;
    header[22] = (byte) channel;
    header[23] = 0;
    header[24] = (byte) (sampleRate & 0xff);
    header[25] = (byte) ((sampleRate >> 8) & 0xff);
    header[26] = (byte) ((sampleRate >> 16) & 0xff);
    header[27] = (byte) ((sampleRate >> 24) & 0xff);
    header[28] = (byte) (byteRate & 0xff);
    header[29] = (byte) ((byteRate >> 8) & 0xff);
    header[30] = (byte) ((byteRate >> 16) & 0xff);
    header[31] = (byte) ((byteRate >> 24) & 0xff);
    header[32] = (byte) (2 * 16 / 8); // block align
    header[33] = 0;
    header[34] = 16; // bits per sample
    header[35] = 0;
    header[36] = d;
    header[37] = a;
    header[38] = t;
    header[39] = a;
    header[40] = (byte) (audioLength & 0xff);
    header[41] = (byte) ((audioLength >> 8) & 0xff);
    header[42] = (byte) ((audioLength >> 16) & 0xff);
    header[43] = (byte) ((audioLength >> 24) & 0xff);
    try 
      fos.write(header, 0, 44);
     catch (IOException e) 
      // TODO Auto-generated catch block
      //Log.e("MainActivity:createWavFileHeader()",e.getMessage());
    
  
  /*
  * delete created temperory file
  * @param
  * @return void
  */
  private void deletTempFile() 
    File file = createTempFile();
    file.delete();
  
  /*
   * Initialize audio record
   *
   * @param
   * @return android.media.AudioRecord
   */
  private AudioRecord initializeRecord() 
    short[] audioFormat = new short[]AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_PCM_8BIT;
    short[] channelConfiguration = new short[]AudioFormat.CHANNEL_IN_MONO, AudioFormat.CHANNEL_IN_STEREO;
    for (int rate : recordRate) 
      for (short aFormat : audioFormat) 
        for (short cConf : channelConfiguration) 
          //Log.d("MainActivity:initializeRecord()","Rate"+rate+"AudioFormat"+aFormat+"Channel Configuration"+cConf);
          try 
            int buffSize = AudioRecord.getMinBufferSize(rate, cConf, aFormat);
            bufferSize = buffSize;
            if (buffSize != AudioRecord.ERROR_BAD_VALUE) 
              AudioRecord aRecorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, rate, cConf, aFormat, buffSize);
              if (aRecorder.getState() == AudioRecord.STATE_INITIALIZED) 
                RECORD_RATE = rate;
                //Log.d("MainActivity:InitializeRecord - AudioFormat",String.valueOf(aFormat));
                //Log.d("MainActivity:InitializeRecord - Channel",String.valueOf(cConf));
                //Log.d("MainActivity:InitialoizeRecord - rceordRate", String.valueOf(rate));
                return aRecorder;
              
            
           catch (Exception e) 
            //Log.e("MainActivity:initializeRecord()",e.getMessage());
          
        
      
    
    return null;
  
  /*
  * Method to stop and release audio record
  *
  * @param
  * @return void
  */
  private void stopRecord() 
    if (null != audioRecorder) 
      isRecording = false;
      audioRecorder.stop();
      audioRecorder.release();
      audioRecorder = null;
      recordT = null;
      Toast.makeText(getApplicationContext(), "Recording is stopped", Toast.LENGTH_LONG).show();
    
    convertRawToWavFile(createTempFile(), createWavFile());
    if (uploadFile.exists()) 
      //Log.d("AudioRecorderService:stopRecord()", "UploadFile exists");
    
    new UploadFile().execute(uploadFile);
    deletTempFile();
  

 更多关于Android相关内容感兴趣的读者可查看本站专题:《Android多媒体操作技巧汇总(音频,视频,录音等)》、《Android开发入门与进阶教程》、《Android视图View技巧总结》、《Android编程之activity操作技巧总结》、《Android操作json格式数据技巧总结》、《Android文件操作技巧汇总》、《Android资源操作技巧汇总》及《Android控件用法总结》

电话录音怎么保存起来

参考技术A手机电话录音需要打开将手机进入到通话通话页面,然后选择录音功能,电话结束后录音也会自动保存,而苹果手机不能直接录音,需要两部手机同时进行,将其中一部手机进行通话,另一部手机将录音功能打开。手... 查看详情

手机打电话时如何录音

若是使用vivo手机,拨打电话时,点击通话界面的“录音”即可录制通话内容。一、设置通话自动录音的方法:1、进入i管家--实用工具--辅助功能--通话录音--选择“所有通话自动录音”或“指定”号码自动录音;2、设置--(应用... 查看详情

android6.0源码修改之settings音量调节界面增加通话音量调节(代码片段)

前言今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了。需要优化两个地方1、在正常情况下,按物理音... 查看详情

录音盒那家支持二次开发,并且可以电脑上可以批量拨号,耳麦通话的?

...发。二次开发包及DEMO提供丰富的功能接口函数、完善的编程范例来电、去电弹屏-外线来电,电话机直接响铃接听-内线电话机摘挂机判断-电话通话录音-电话留言-对电话通话的各种状态(摘机、挂机、按键)进行判断-电脑软件鼠... 查看详情

icu探视对讲

...护士站主机,由护士站再转接至要探视的病床分机,进行双向可视对讲。????护士站主机与病床分机对讲:护士站主机自带摄像头,与病床分机可相互呼叫,双向可视对讲。?3.3.录音录像???可对通话的过程进行录音录像,并可记录... 查看详情

安卓通话录音功能

】安卓通话录音功能【英文标题】:Androidcallrecordingfeature【发布时间】:2011-08-0316:19:45【问题描述】:正如其他帖子中所讨论的,大多数Android设备不支持通话录音。在我的三星GalaxyS2上录制AudioSource.VOICE_CALL确实可以正常工作。... 查看详情

蓝牙通话机制原理

[摘要]:本文主要论述基于android6.0的蓝牙上层(Java层)通话机制;总结了蓝牙通话框架,并且给出了接听电话的详细的流程图;最后说明了apk的实现以及总结了蓝牙/android相关的知识点。1,蓝牙框架主要代码路径:路径1:frameworks\\base\\core\\j... 查看详情

android6.0设置开机启动默认横屏并兼顾自动旋转功能

    关于Android6.0开机默认横屏显示,网上有一些文章提到过,但是我仔细研究了下,要么不全,要么修改方式会引入新的问题。为了让开机强制横屏显示,并且又不影响sensor的屏幕自动旋转功能,我对旋... 查看详情

录音怎么保存到手机

...了。参考技术A你好,楼主你指的是什么录音呢,如果是通话录音或者手机中的录音机进行录音,都会自动保存到手机当中的。 参考技术B录音怎么保存到手机手机的附加功能有录音功能,录音好就可以保存起来到手机了, 参考... 查看详情

Android 通话录音 来电未录音

...录器应用程序,我可以使用MediaRecorder.AudioSource.VOICE_CALL在android6以下记录语音通话,从android6开始,无法使用VOICE_CALL录制语音通话。我设法使 查看详情

crm系统怎么实现通话功能?

...在CRM中集成通话能力,企业的工作效率就会大大提升。CRM实现通话能力,不一定需要呼叫中心。最简单的方法是用点击拨号API接口集成,只需要几行代码就可以轻松实现通话。无论是PC、网页或手机app,直接点击客户名字或电话... 查看详情

android6.0指纹识别app开发demo

在android6.0中google终于给android系统加上了指纹识别的支持,这个功能在iPhone上早就已经实现了,并且在很多厂商的定制的ROM中也都自己内部实现这个功能了,这个功能来的有点晚啊。在google全新发布的nexus设备:nex... 查看详情

android6.0一个完整的nativeservice

    上一篇博客《Android6.0如何添加完整的系统服务(app-framework-kernel)》http://www.cnblogs.com/hackfun/p/7418902.html介绍了如何添加一个系统服务,客户端和服务端都是基于JAVA实现的OpersysService。经过进一步的学习,我将演示... 查看详情

求助,使用sipml5,能打通手机,但是没有声音

...,安装该软件之后,使用本机手机号注册,注册完成即可双向通话录音,录音机功能,并存储到云端平台(保证清理手机时不会误删,也不用担心手机内存不够),通过云端存储的录音音质清晰,具备相当高的法律效力,而且录... 查看详情

如何基于zegosdk实现回声消除自动增益控制降噪功能

在实时音视频通话时,可以对音频进行3A处理,提高通话质量和用户体验。主要包括AEC(AcousticEchoCancelling,回声消除),AGC(AutomaticGainControl,自动增益控制)和ANS(ActiveNoiseControl,降噪)。AEC(回声消除):对采集到的音频数... 查看详情

第二天,关于sip的学习(freeswitch增加配置录音功能)

...命令,再细节地了解下FS。看下是不是之前怀疑的,二次编程的时候更改配置文件,或者java注入一些参数到配置文件,详细了解下配置文件。这个应该比较难的,不清楚问下百度先生吧。学习了一个新知识 FS增加录音功能如... 查看详情

unity在android6.0及以上版本弹出权限申请窗口的问题

参考技术AAndroid版本大于等于6.0(SDK版本大于等于23),权限需要应用自主申请,动态申请权限。如果Unity做为Android的子模块,除了应用本身会有申请权限的弹窗之外,Unity也会自动弹出权限申请窗口。关于Android平台Unity的权限说... 查看详情

什么是ippbx,ippbx特点及其发展方向

...统电话线路,提供IVR语音导航,ACD呼叫排队,远程分机,通话录音,自带录音存储等功能。目前IPPBX--系统多语言支持--丰富的IVR菜单,不限层级,不限数量,功能远超电脑话务员。语音提示音可以通过web上传。--支持分机分组、... 查看详情