Android6.0編程實(shí)現(xiàn)雙向通話自動錄音功能的方法詳解
本文實(shí)例講述了Android6.0編程實(shí)現(xiàn)雙向通話自動錄音功能的方法。分享給大家供大家參考,具體如下:
項目中需要實(shí)現(xiàn)基于Android 6.0 的雙向通話自動錄音功能,在查閱相關(guān)android電話狀態(tài)監(jiān)聽文章以及Git上的開源錄音項目后,整理出此文
首先,介紹一下android 電話狀態(tài)的監(jiān)聽(來電和去電):
http://www.dbjr.com.cn/article/32433.htm
實(shí)現(xiàn)手機(jī)電話狀態(tài)的監(jiān)聽,主要依靠兩個類:
TelephoneManger和PhoneStateListener
TelephonseManger提供了取得手機(jī)基本服務(wù)的信息的一種方式。因此應(yīng)用程序可以使用TelephonyManager來探測手機(jī)基本服務(wù)的情況。應(yīng)用程序可以注冊listener來監(jiān)聽電話狀態(tài)的改變。
我們不能對TelephonyManager進(jìn)行實(shí)例化,只能通過獲取服務(wù)的形式:
Context.getSystemService(Context.TELEPHONY_SERVICE);
注意:對手機(jī)的某些信息進(jìn)行讀取是需要一定許可(permission)的。
主要靜態(tài)成員常量:(它們對應(yīng)PhoneStateListener.LISTEN_CALL_STATE所監(jiān)聽到的內(nèi)容)
int CALL_STATE_IDLE //空閑狀態(tài),沒有任何活動。 int CALL_STATE_OFFHOOK //摘機(jī)狀態(tài),至少有個電話活動。該活動或是撥打(dialing)或是通話,或是 on hold。并且沒有電話是ringing or waiting int CALL_STATE_RINGING //來電狀態(tài),電話鈴聲響起的那段時間或正在通話又來新電,新來電話不得不等待的那段時間。
項目中使用服務(wù)來監(jiān)聽通話狀態(tài),所以需要弄清楚手機(jī)通話狀態(tài)在廣播中的對應(yīng)值:
EXTRA_STATE_IDLE //它在手機(jī)通話狀態(tài)改變的廣播中,用于表示CALL_STATE_IDLE狀態(tài),即空閑狀態(tài)。 EXTRA_STATE_OFFHOOK //它在手機(jī)通話狀態(tài)改變的廣播中,用于表示CALL_STATE_OFFHOOK狀態(tài),即摘機(jī)狀態(tài)。 EXTRA_STATE_RINGING //它在手機(jī)通話狀態(tài)改變的廣播中,用于表示CALL_STATE_RINGING狀態(tài),即來電狀態(tài) ACTION_PHONE_STATE_CHANGED //在廣播中用ACTION_PHONE_STATE_CHANGED這個Action來標(biāo)示通話狀態(tài)改變的廣播(intent)。 //注:需要許可READ_PHONE_STATE。 String EXTRA_INCOMING_NUMBER //在手機(jī)通話狀態(tài)改變的廣播,用于從extra取來電號碼。 String EXTRA_STATE //在通話狀態(tài)改變的廣播,用于從extra取來通話狀態(tài)。
如何實(shí)現(xiàn)電話監(jiān)聽呢?
Android在電話狀態(tài)改變是會發(fā)送action為android.intent.action.PHONE_STATE的廣播,而撥打電話時會發(fā)送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)){
//空閑狀態(tài)
stateChange =TelephonyManager.CALL_STATE_IDLE;
if (isIncoming){
onIncomingCallEnded(context,phoneNumber);
}else {
onOutgoingCallEnded(context,phoneNumber);
}
}else if (state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
//摘機(jī)狀態(tài)
stateChange = TelephonyManager.CALL_STATE_OFFHOOK;
if (lastCallState != TelephonyManager.CALL_STATE_RINGING){
//如果最近的狀態(tài)不是來電響鈴的話,意味著本次通話是去電
isIncoming =false;
onOutgoingCallStarted(context,phoneNumber);
}else {
//否則本次通話是來電
isIncoming = true;
onIncomingCallAnswered(context, phoneNumber);
}
}else if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)){
//來電響鈴狀態(tài)
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實(shí)現(xiàn):
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();
}
}
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android多媒體操作技巧匯總(音頻,視頻,錄音等)》、《Android開發(fā)入門與進(jìn)階教程》、《Android視圖View技巧總結(jié)》、《Android編程之a(chǎn)ctivity操作技巧總結(jié)》、《Android操作json格式數(shù)據(jù)技巧總結(jié)》、《Android文件操作技巧匯總》、《Android資源操作技巧匯總》及《Android控件用法總結(jié)》
希望本文所述對大家Android程序設(shè)計有所幫助。
- Android編程錄音工具類RecorderUtil定義與用法示例
- Android編程實(shí)現(xiàn)錄音及保存播放功能的方法【附demo源碼下載】
- Android編程檢測手機(jī)錄音權(quán)限是否打開的方法
- Android實(shí)現(xiàn)錄音功能實(shí)現(xiàn)實(shí)例(MediaRecorder)
- android語音即時通訊之錄音、播放功能實(shí)現(xiàn)代碼
- Android使用AudioRecord實(shí)現(xiàn)暫停錄音功能實(shí)例代碼
- Android 錄音與播放功能的簡單實(shí)例
- Android編程實(shí)現(xiàn)通話錄音功能的方法
- 利用libmp3lame實(shí)現(xiàn)在Android上錄音MP3文件示例
- Android錄音--AudioRecord、MediaRecorder的使用
- android 通過MediaRecorder實(shí)現(xiàn)簡單的錄音示例
- Android使用MediaRecorder實(shí)現(xiàn)錄音及播放
- Android錄音播放管理工具
- Android實(shí)現(xiàn)拍照、錄像、錄音代碼范例
- Android實(shí)現(xiàn)自制和播放錄音程序
- Android中簡單調(diào)用圖片、視頻、音頻、錄音和拍照的方法
- Android編程開發(fā)錄音和播放錄音簡單示例
- Android錄音mp3格式實(shí)例詳解
相關(guān)文章
Android自定義View展示W(wǎng)ifi信號強(qiáng)弱指示方法示例
這篇文章主要給大家介紹了關(guān)于Android自定義View展示W(wǎng)ifi信號強(qiáng)弱指示的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),文末給出了完整的實(shí)例供大家參考學(xué)習(xí),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08
利用Jetpack?Compose復(fù)刻游戲Flappy?Bird
Flappy?Bird是13年紅極一時的小游戲,其簡單有趣的玩法和變態(tài)的難度形成了強(qiáng)烈反差,引發(fā)全球玩家競相把玩!本文將通過Jetpack?Compose復(fù)刻這一游戲,感興趣的小伙伴可以了解一下2022-02-02
Android實(shí)現(xiàn)自定義華麗的水波紋效果
關(guān)于Android的水波紋效果小編之前給大家也分享幾篇類似的,有興趣可通過下面的相關(guān)文章進(jìn)行查看,今天給大家再分享一個華麗的水波紋效果,這個效果很不錯,感興趣的可以參考借鑒。2016-08-08
Android監(jiān)聽電池狀態(tài)實(shí)例代碼
這篇文章給大家介紹Android監(jiān)聽電池狀態(tài)實(shí)例代碼,對android監(jiān)聽電池狀態(tài)相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-03-03
詳細(xì)分析Android中onTouch事件傳遞機(jī)制
相信不少朋友在剛開始學(xué)習(xí)Android的時候,對于onTouch相關(guān)的事件一頭霧水。分不清onTouch(),onTouchEvent()和OnClick()之間的關(guān)系和先后順序,所以覺得有必要搞清onTouch事件傳遞的原理。經(jīng)過一段時間的琢磨以及相關(guān)博客的介紹,這篇文章就給大家詳細(xì)的分析介紹下。2016-10-10

