android 通話錄音

近期應公司要求,因為是一個銷售型公司,所以銷售人員需要通話錄音的一個需求,所以在老板的要求下,實現(xiàn)了這個項目。
1、app實現(xiàn)雙向錄音;
2、上傳錄音文件到node.js服務器,將音頻文件存到oss中(因為我的服務器用的是阿里云的oss存儲文件);
3、在數(shù)據(jù)庫中存儲錄音的訪問地址;
4、在公司內(nèi)部的oa系統(tǒng)中顯示可播放該錄音。

android實現(xiàn)錄音,我用的是MediaRecorder,剛開始錄制的音頻格式是.3gp的。之后采用了segmentFault上面一個大神的說法,用MediaRecorder將錄音錄制成mp4格式,用aac編碼,只需要把后綴名改成mp3格式的就可以了。實現(xiàn)思路如下:
注冊一個服務為PhoneListenServer繼承自Server。這個server類因為是服務,所以可以監(jiān)聽通話錄音的狀態(tài),當有電話打進來時就可以監(jiān)聽到,監(jiān)聽到之后就可以實現(xiàn)MediaRecorder錄音機的錄音,至于電話撥出的監(jiān)聽,是注冊一個廣播接收器MyPhoneStateReceiver myPhoneStateReceiver,registerReceiver(myPhoneStateReceiver, intentFilter)。文件上傳我采用的是OkHttp3的文件上傳,因為我的錄音數(shù)據(jù)放在sd卡新建的兩個文件夾中recorder_callMonitor_from和recorder_callMonitor_outgoingcall。所以上傳文件的時候獲取文件,并將當前用戶的信息和來電去電信息一起上傳到服務器進行處理。
具體實現(xiàn)代碼我貼出來,因為注釋特別詳細,我就大致講解一下就好了。不多啰嗦。
一、android端。


image.png

getOutgoingCall()這個函數(shù)是監(jiān)聽去電廣播


image.png

image.png

image.png
image.png

MyListener這個類繼承自PhoneStateListener,主要用來監(jiān)聽通話狀態(tài)并且實現(xiàn)錄音和錄音文件的存儲。
下面是文件上傳的功能,是在線程中完成的
final String[] flag = new String[1];
private final class UploadTask implements Runnable {
@Override
public void run() {

        /**
         * 對傳輸?shù)臄?shù)據(jù)進行封裝
         */
        final String filename; // 文件名(當前打電話的電話通話對方的號碼)
        final String filePath;  // 文件路徑,錄制的音頻所在的sd卡路徑
        String path = Environment.getExternalStorageDirectory().getPath();
        final String outPath = path + "/recorder_callMonitor_outgoingcall" + "/" + inComingNumber + ".mp3";
        final String fromPath = path + "/recorder_callMonitor_from" + "/" + inComingNumber + ".mp3";

        if (isExist(outPath)) {
            filePath = outPath;
            filename = getFileName(outPath);
            flag[0] = "0";
        } else {
            filePath = fromPath;
            filename = getFileName(fromPath);
            flag[0] = "1";
        }

        File file = new File(filePath);

        if (!file.exists()) {
            L.e(file.getAbsolutePath() + " not exist!");
            return;
        }
        // 數(shù)據(jù)封裝完畢

        // 1 拿到okHttpClient對象
        final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(5000, TimeUnit.MILLISECONDS)
                .readTimeout(5000,TimeUnit.MILLISECONDS)
                .build();

        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("phoneCustomer",filename)
                .addFormDataPart("phoneUser",getLocalName())
                .addFormDataPart("type",flag[0])
                .addFormDataPart("file",filename,RequestBody.create(MediaType.parse("audio/mp3"),file))
                .build();

        // 2 構造Request
        Request.Builder builder = new Request.Builder();
        Request request = builder.url("https://oa.100xuetang.com/android/audioFile/android_uploadAudio")
                .post(requestBody)
                .build();
        // 3 將Request封裝為Call
        Call call = okHttpClient.newCall(request);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                L.e("onFailure: " + e.getMessage());
                e.printStackTrace();
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                L.e("onResponse:");
                String res = response.body().string();
                L.e(res);
                Log.i("result", "success to save audio to oss & mysql");
                successHander();
            }
        });
    }
}

先對需要上傳的數(shù)據(jù)進行封裝,然后調(diào)用OkHttpClient對象。順便將一下OkHttp3這個網(wǎng)絡框架的使用。
1、先聲明一個OkHttpClient對象,可以在這個時候設置超時時間。記得別忘記.build();
2、對數(shù)據(jù)封裝到RequestBody這個對象中。


image.png

就像這樣,別忘記.build();
3、構造Request對象,主要有兩個,一個是url,一個是post(requestBody);
4、將Request封裝成Call,這個是必須的一步,call.execute()同步執(zhí)行上傳操作,call.enqueue(new Callback(){...})是將這個網(wǎng)絡請求放到隊列中等待執(zhí)行,是異步,這個里面需要強調(diào)的一點是回調(diào)函數(shù)里面的onResponse()在另一個線程中,不是主線程。

下面是幾個輔助函數(shù),我都列出來吧。既然造輪子就得讓最不會用的人也會使用嘛

public void successHander(){
    // 第一步:刪除本地存儲下來的音頻文件,防止占用內(nèi)存空間過大。
    deleteDir(Environment.getExternalStorageDirectory().getPath() + "/recorder_callMonitor_outgoingcall");
    deleteDir(Environment.getExternalStorageDirectory().getPath() + "/recorder_callMonitor_from");

    // 第二步:flag置為未知,當有電話來或者有電話撥出時候再重新賦值。
    flag[0] = "-1";

    // 第三步: inComingNumber 和 callNumber 置為空,防止誤命名錄音文件
    inComingNumber = "";
    callNum = "";
    phoneNumber = "";
}

@Override
public void onDestroy() {
    super.onDestroy();
    // 取消電話的監(jiān)聽,采取線程守護的方法,當一個服務關閉后,開啟另外一個服務,除非你很快把兩個服務同時關閉才能完成
    Intent i = new Intent(this,TelProtectService.class);
    startService(i);
    listener = null;
    System.out.println("關閉了服務");
}

/**
 * 提取文件名
 * @param pathandname 文件路徑
 * @return
 */
public String getFileName(String pathandname){
    int start=pathandname.lastIndexOf("/");
    int end=pathandname.lastIndexOf(".");
    if(start!=-1 && end!=-1){
        return pathandname.substring(start+1,end);
    }else{
        return null;
    }
}

/**
 * 獲得保存在本地的用戶名
 */
public String getLocalName() {
    //獲取SharedPreferences對象,使用自定義類的方法來獲取對象
    SharedPreferencesUtils helper = new SharedPreferencesUtils(this, "setting");
    String name = helper.getString("name");
    return name;
}

/**
 * 判斷文件夾是否存在
 * @param path 文件夾路徑
 */
public boolean isExist(String path) {
    File file = new File(path);
    //判斷文件夾是否存在,如果不存在則創(chuàng)建文件夾
    if (!file.exists()) {
        return false; // 不存在
    } else {
        return true;  // 存在
    }
}

/**
 * 刪除文件夾和文件夾里面的文件
 * @param pPath
 */
 private void deleteDir(final String pPath) {
    File dir = new File(pPath);
    deleteDirWihtFile(dir);
}

 private void deleteDirWihtFile(File dir) {
    if (dir == null || !dir.exists() || !dir.isDirectory())
        return;
    for (File file : dir.listFiles()) {
        if (file.isFile())
            file.delete(); // 刪除所有文件
        else if (file.isDirectory())
            deleteDirWihtFile(file); // 遞規(guī)的方式刪除文件夾
    }
    dir.delete();// 刪除目錄本身
}

線程守護的類:
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.util.Log;

/**

  • 保護監(jiān)聽服務Service
    */
    public class TelProtectService extends Service{

    @Override
    public void onCreate() {
    Intent i = new Intent(this, PhoneStateListener.class);
    startService(i);
    Log.i("TelProtectService", "TelProtectService.守護進程");
    super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
    return null;
    }
    }

二、node.js服務器端

首先是在Controller文件夾下audioFile.js下的android_uploadAudio這個路由,express框架這個是用nodeJs人最熟悉的了。multer這個框架require一下,因為是涉及到file。


image.png

我的函數(shù)封裝在Models下的android/upload.js里面。


image.png
image.png

這兒我不多做介紹了,邏輯上就是先從數(shù)據(jù)庫拿到傳過來的兩個手機號對應的課程顧問的id和顧客的id,然后先將文件讀出來,再命名,存儲到oss中,同時存儲到數(shù)據(jù)庫一條記錄,然后callbackOk()。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容