来电录音/处理服务! – Android

你好,
我正在开发Android的解决scheme,将logging呼叫(包括输出和input),并进一步处理logging的数据(在我的应用程序的终点,没有audio文件数据将保存在手机内存)。 我已经实现与PhoneStateListener.LISTEN_CALL_STATE BroadcastReceiver启动logging服务,如果状态是CALL_STATE_OFFHOOK。 然后在服务中,我开始一个尝试录制呼叫的新线程,以及另一个带有PhoneStateListener.LISTEN_CALL_STATE的BroadcastReceiver,如果电话状态更改为TelephonyManager.CALL_STATE_IDLE,则调用方法停止录制。

所创build的audio文件是空的。 我的服务中的recorder.stop()方法被调用时抛出exception。 错误在哪里? 我能做得更好吗?

首先(独立)BroadcastReceiver:

package com.piotr.callerrecogniser; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; public class CallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { MyPhoneStateListener phoneListener = new MyPhoneStateListener(context); TelephonyManager telephony = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); telephony.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE); } class MyPhoneStateListener extends PhoneStateListener { public static final String tag = "CallerRecogniser - CallReceiver"; private Context context; MyPhoneStateListener(Context c) { super(); context = c; } public static final int NOTIFICATION_ID_RECEIVED = 0x1221; @Override public void onCallStateChanged(int state, String incomingNumber) { SharedPreferences preferences = context.getSharedPreferences("CallReceiver", Context.MODE_PRIVATE); switch (state) { case TelephonyManager.CALL_STATE_IDLE: break; //If call is answered, run recording service. Also pass "phone_number" variable with incomingNumber to shared prefs, so service will be able to access that via shared prefs. case TelephonyManager.CALL_STATE_OFFHOOK: // The call is answered String phone_number = preferences.getString("phone_number",null); Intent serv = new Intent(context, CallRecordingService.class); serv.putExtra("number", phone_number); context.startService(serv); break; //If phone is ringing, save phone_number. This is done because incomingNumber is not saved on CALL_STATE_OFFHOOK case TelephonyManager.CALL_STATE_RINGING: SharedPreferences.Editor editor = preferences.edit(); editor.putString("phone_number", incomingNumber); editor.commit(); break; } } } } 

服务:

 package com.piotr.callerrecogniser; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; public class CallRecordingService extends Service implements Runnable { CallerRecogniserDB database = new CallerRecogniserDB(this); String phoneNumber; MediaRecorder recorder; private final Handler mHandler = new Handler(); private final BroadcastReceiver myCallStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { MyPhoneStateListener phoneListener = new MyPhoneStateListener( context); TelephonyManager telephony = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); telephony.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE); } class MyPhoneStateListener extends PhoneStateListener { private Context context; MyPhoneStateListener(Context c) { super(); context = c; } @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: if (recording) { NotificationManager mNotificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); Notification not; not = new Notification(R.drawable.ic_launcher, "Phone call being processed", System.currentTimeMillis()); Intent notIntent = new Intent(); PendingIntent contentIntent = PendingIntent .getActivity(context, 0, notIntent, 0); not.setLatestEventInfo(context, "CallRecordingService", "Notification from idle", contentIntent); mNotificationManager.notify(NOTIFICATION_ID_RECEIVED, not); stopRecording(); } break; } } } }; private static boolean recording = false; private String INCOMING_CALL_ACTION = "android.intent.action.PHONE_STATE"; @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); IntentFilter intentToReceiveFilter = new IntentFilter(); intentToReceiveFilter.addAction(INCOMING_CALL_ACTION); this.registerReceiver(myCallStateReceiver, intentToReceiveFilter, null, mHandler); Thread aThread = new Thread(this); aThread.start(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub super.onStart(intent, startId); phoneNumber = intent.getExtras().getString("number"); return START_STICKY; } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } public static final int NOTIFICATION_ID_RECEIVED = 0x1221; @Override public void run() { Looper.myLooper(); Looper.prepare(); // TODO Auto-generated method stub database.open(); String[] numbers = database.getData(CallerRecogniserDB.KEY_NUMBER); int index = -1; outside_for: for (int i = 0; i < numbers.length; i++) { String[] splitted = numbers[i].split(","); for (String nu : splitted) { if (nu.equals(phoneNumber)) { index = i; break outside_for; } } } database.close(); if (index >= 0) { // Phone number is in a database and it's state==0 => // it has no data recorded // Notification that call is recorded recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); recorder.setOutputFile(Environment.getExternalStorageDirectory() .getAbsolutePath() + "/" + phoneNumber + ".3gp"); try { recorder.prepare(); } catch (Exception e) { e.printStackTrace(); } recording = true; recorder.start(); } } void stopRecording() { // Then clean up with when it hangs up: recorder.stop(); recorder.release(); recording=false; } } 

权限:

 <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 

exception输出:

 11-28 14:28:44.385: E/MediaRecorder(22438): stop called in an invalid state: 8 11-28 14:28:44.400: D/AndroidRuntime(22438): Shutting down VM 11-28 14:28:44.425: W/dalvikvm(22438): threadid=1: thread exiting with uncaught exception (group=0x4001e578) 11-28 14:28:44.435: E/AndroidRuntime(22438): FATAL EXCEPTION: main 11-28 14:28:44.435: E/AndroidRuntime(22438): java.lang.IllegalStateException 11-28 14:28:44.435: E/AndroidRuntime(22438): at android.media.MediaRecorder.stop(Native Method) 11-28 14:28:44.435: E/AndroidRuntime(22438): at com.piotr.callerrecogniser.CallRecordingService.stopRecording(CallRecordingService.java:159) 11-28 14:28:44.435: E/AndroidRuntime(22438): at com.piotr.callerrecogniser.CallRecordingService$1$MyPhoneStateListener.onCallStateChanged(CallRecordingService.java:65) 11-28 14:28:44.435: E/AndroidRuntime(22438): at android.telephony.PhoneStateListener$2.handleMessage(PhoneStateListener.java:369) 11-28 14:28:44.435: E/AndroidRuntime(22438): at android.os.Handler.dispatchMessage(Handler.java:99) 11-28 14:28:44.435: E/AndroidRuntime(22438): at android.os.Looper.loop(Looper.java:123) 11-28 14:28:44.435: E/AndroidRuntime(22438): at android.app.ActivityThread.main(ActivityThread.java:3691) 11-28 14:28:44.435: E/AndroidRuntime(22438): at java.lang.reflect.Method.invokeNative(Native Method) 11-28 14:28:44.435: E/AndroidRuntime(22438): at java.lang.reflect.Method.invoke(Method.java:507) 11-28 14:28:44.435: E/AndroidRuntime(22438): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847) 11-28 14:28:44.435: E/AndroidRuntime(22438): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605) 11-28 14:28:44.435: E/AndroidRuntime(22438): at dalvik.system.NativeStart.main(Native Method) 

Solutions Collecting From Web of "来电录音/处理服务! – Android"

我已经在我的三星Galaxy S2上进行了小小的修改来testing你的代码。 它很好,没有例外。 通话已成功使用该服务录制。 我发现的一个问题是服务被启动了两次(Broadcast Receiver被启动了两次,TelephonyManager.PhoneStateListner被状态为OFFHOOK的两次调用),我必须在方法setAudioSource中将常量从VOICE_CALL改为VOICE_UPLINK。 它看起来像Java枚举定义和C头中的枚举是不同的。 呼叫logging为双方(UPLINK和DOWNLINK)

android 文档指出,如果在start()之前调用stop(),则会引发此exception。

我会添加一个检查,以确保在您调用stop()方法之前设置您的logging标志。 这可能会摆脱你的错误。

但是,由于某种原因,start()永远不会被调用,可能是因为你的数据库代码,这是你真正的问题。

Pejter,

当我尝试移植到我为Symbian手机开发的应用程序时,我有类似的要求。

经过一番研究,我得出了一个可悲的结论,那就是今天大部分(甚至可能是所有的)Android手机都不可能实现。

尽pipe自Android 1.6以来拥有必要的API,但由于硬件架构或固件实施,大多数Android手机都不支持这一function。 基本上,问题是主电话处理器不能访问基带audiostream。 它通常可以访问本地麦克风,因此在某些电话上可以logging本地用户,并可能通过听筒和麦克风之间的声学​​耦合logging到达麦克风的远程用户的微弱信号。 这不足以满足我的应用程序的要求,所以我还没有一个Android版本。

我知道我没有解决你的问题(甚至可能与你得到的例外无关),但是也许这样可以避免浪费你的时间。

欲了解更多信息,你可以看看下面的内容:

  1. http://code.google.com/p/android/issues/detail?id=2117

  2. https://groups.google.com/d/topic/android-developers/AbU85mtDgQw/discussion

尝试使用

 mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 

代替

 mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);