7

您好,
我正在为 Android 开发一个解决方案,该解决方案将记录呼叫(呼出和呼入)并将进一步处理记录的数据(在我的应用程序的终点,手机内存中不会保留任何音频文件数据)。我已经使用 PhoneStateListener.LISTEN_CALL_STATE 实现了 BroadcastReceiver,如果状态为 CALL_STATE_OFFHOOK,它会启动录制服务。然后在服务中,我启动一个新线程来尝试记录一个呼叫,以及另一个带有 PhoneStateListener.LISTEN_CALL_STATE 的 BroadcastReceiver,如果电话状态更改为 TelephonyManager.CALL_STATE_IDLE,它会调用一个停止记录的方法。

创建的音频文件为空。调用我的服务中的 recorder.stop() 方法时引发异常。哪里有错误?我能做的更好吗?

第一个(独立)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" />

异常输出:

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)
4

4 回答 4

4

我已经在我的三星 Galaxy S2 上稍作修改测试了您的代码。它炒得很好,没有例外。使用该服务成功录制了通话。我发现的一个问题是服务启动了两次(广播接收器启动了两次,TelephonyManager.PhoneStateListner 调用了两次,状态为 OFFHOOK)我必须将方法 setAudioSource 中的常量从 VOICE_CALL 更改为 VOICE_UPLINK。看起来 Java 枚举定义和 C 标头中的枚举是不同的。双方的通话录音(上行和下行)

于 2012-01-11T13:17:28.737 回答
1

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

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

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

于 2011-11-28T14:43:25.650 回答
1

佩特,

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

经过一些研究,我得出了一个悲惨的结论,即这在当今可用的大多数(甚至可能是所有)Android 手机上可能是不可能的。

尽管自 Android 1.6 以来就有必要的 API,但由于硬件架构或固件实现,大多数 Android 手机都不支持此功能。基本上,问题在于主电话处理器无法访问基带音频流。它通常可以访问本地麦克风,因此在某些手机上,可以记录本地用户以及可能通过听筒和麦克风之间的声学​​耦合到达麦克风的远程用户的微弱信号。这对我的应用程序的要求来说还不够好,所以我仍然没有 Android 版本。

我知道我没有解决您的问题(它甚至可能与您遇到的异常无关),但也许它可以避免您浪费时间。

有关更多信息,您可以查看以下内容:

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

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

于 2011-11-30T06:52:29.620 回答
1

尝试使用

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);

代替

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
于 2013-06-27T16:40:04.273 回答