通话录音/处理服务! - Android

时间:2011-11-28 13:33:37

标签: android service broadcastreceiver mediarecorder android-sdk-2.3

您好
我正致力于Android的解决方案,它将记录呼叫(包括输出和接收)并将进一步处理记录的数据(在我的应用程序的终点,没有音频文件数据将保留在手机内存上)。我已经使用PhoneStateListener.LISTEN_CALL_STATE实现了BroadcastReceiver,如果state为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 个答案:

答案 0 :(得分:4)

我在三星Galaxy S2上进行了少量修改,测试了您的代码。没有例外,它很好。使用该服务成功录制了呼叫。 我找到的一个问题是服务启动了两次(广播接收器启动了两次,TelephonyManager.PhoneStateListner调用状态为OFFHOOK两次) 我必须将方法setAudioSource中的常量从VOICE_CALL更改为VOICE_UPLINK。看起来像Java枚举中的Java枚举定义和枚举是不同的。双方都记录了呼叫(UPLINK和DOWNLINK)

答案 1 :(得分:1)

android documentation表示如果在start()之前调用stop(),则抛出此异常。

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

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

答案 2 :(得分:1)

Pejter,

当我尝试将我为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

答案 3 :(得分:1)

尝试使用

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);

而不是

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);