Android6.0编程实现双向通话自动录音功能的方法详解
发布时间 - 2026-01-11 02:16:02 点击率:次本文实例讲述了Android6.0编程实现双向通话自动录音功能的方法。分享给大家供大家参考,具体如下:

项目中需要实现基于Android 6.0 的双向通话自动录音功能,在查阅相关android电话状态监听文章以及Git上的开源录音项目后,整理出此文
首先,介绍一下android 电话状态的监听(来电和去电):
https://www./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控件用法总结》
希望本文所述对大家Android程序设计有所帮助。
# Android6.0
# 双向通话
# 自动录音功能
# Android编程录音工具类RecorderUtil定义与用法示例
# Android编程实现录音及保存播放功能的方法【附demo源码下载】
# Android编程检测手机录音权限是否打开的方法
# Android实现录音功能实现实例(MediaRecorder)
# android语音即时通讯之录音、播放功能实现代码
# Android使用AudioRecord实现暂停录音功能实例代码
# Android 录音与播放功能的简单实例
# Android编程实现通话录音功能的方法
# 利用libmp3lame实现在Android上录音MP3文件示例
# Android录音--AudioRecord、MediaRecorder的使用
# android 通过MediaRecorder实现简单的录音示例
# Android使用MediaRecorder实现录音及播放
# Android录音播放管理工具
# Android实现拍照、录像、录音代码范例
# Android实现自制和播放录音程序
# Android中简单调用图片、视频、音频、录音和拍照的方法
# Android编程开发录音和播放录音简单示例
# Android录音mp3格式实例详解
# 它在
# 操作技巧
# 那段
# 应用程序
# 录音功能
# 进阶
# 相关内容
# 有个
# 没有任何
# 均为
# 感兴趣
# 给大家
# 便可
# 自定义
# 可以使用
# 介绍一下
# 更多关于
# 新来
# 能对
# 所述
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
详解CentOS6.5 安装 MySQL5.1.71的方法
html5的keygen标签为什么废弃_替代方案说明【解答】
教你用AI润色文章,让你的文字表达更专业
如何快速搭建高效可靠的建站解决方案?
如何用腾讯建站主机快速创建免费网站?
Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】
企业网站制作这些问题要关注
如何为不同团队 ID 动态生成多个“认领值班”按钮
html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】
详解Android中Activity的四大启动模式实验简述
大同网页,大同瑞慈医院官网?
音乐网站服务器如何优化API响应速度?
高端云建站费用究竟需要多少预算?
敲碗10年!Mac系列传将迎来「触控与联网」双革新
Laravel如何实现API速率限制?(Rate Limiting教程)
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
如何在Tomcat中配置并部署网站项目?
Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】
Laravel怎么实现验证码(Captcha)功能
Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】
Laravel如何使用模型观察者?(Observer代码示例)
如何在宝塔面板中修改默认建站目录?
Laravel如何使用Telescope进行调试?(安装和使用教程)
深圳网站制作平台,深圳市做网站好的公司有哪些?
Laravel怎么使用Collection集合方法_Laravel数组操作高级函数pluck与map【手册】
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程
桂林网站制作公司有哪些,桂林马拉松怎么报名?
潮流网站制作头像软件下载,适合母子的网名有哪些?
java获取注册ip实例
如何用PHP工具快速搭建高效网站?
海南网站制作公司有哪些,海口网是哪家的?
ai格式如何转html_将AI设计稿转换为HTML页面流程【页面】
如何在搬瓦工VPS快速搭建网站?
Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】
如何用景安虚拟主机手机版绑定域名建站?
高性价比服务器租赁——企业级配置与24小时运维服务
如何在云主机快速搭建网站站点?
Laravel如何使用Gate和Policy进行授权?(权限控制)
Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能
网站制作大概要多少钱一个,做一个平台网站大概多少钱?
如何用西部建站助手快速创建专业网站?
如何快速生成可下载的建站源码工具?
Laravel怎么调用外部API_Laravel Http Client客户端使用
详解阿里云nginx服务器多站点的配置
学生网站制作软件,一个12岁的学生写小说,应该去什么样的网站?
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
如何基于PHP生成高效IDC网络公司建站源码?
Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】
胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?

