android 通話錄音

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

android實現(xiàn)錄音祝拯,我用的是MediaRecorder,剛開始錄制的音頻格式是.3gp的。之后采用了segmentFault上面一個大神的說法佳头,用MediaRecorder將錄音錄制成mp4格式鹰贵,用aac編碼,只需要把后綴名改成mp3格式的就可以了康嘉。實現(xiàn)思路如下:
注冊一個服務(wù)為PhoneListenServer繼承自Server碉输。這個server類因為是服務(wù),所以可以監(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侨赡。所以上傳文件的時候獲取文件,并將當前用戶的信息和來電去電信息一起上傳到服務(wù)器進行處理纱控。
具體實現(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 構(gòu)造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)絡(luò)框架的使用。
1藐不、先聲明一個OkHttpClient對象匀哄,可以在這個時候設(shè)置超時時間。記得別忘記.build();
2雏蛮、對數(shù)據(jù)封裝到RequestBody這個對象中涎嚼。


image.png

就像這樣,別忘記.build()挑秉;
3法梯、構(gòu)造Request對象,主要有兩個犀概,一個是url立哑,一個是post(requestBody)夜惭;
4、將Request封裝成Call刁憋,這個是必須的一步滥嘴,call.execute()同步執(zhí)行上傳操作,call.enqueue(new Callback(){...})是將這個網(wǎng)絡(luò)請求放到隊列中等待執(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)聽,采取線程守護的方法,當一個服務(wù)關(guān)閉后尤莺,開啟另外一個服務(wù)旅敷,除非你很快把兩個服務(wù)同時關(guān)閉才能完成
    Intent i = new Intent(this,TelProtectService.class);
    startService(i);
    listener = null;
    System.out.println("關(guān)閉了服務(wù)");
}

/**
 * 提取文件名
 * @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)聽服務(wù)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服務(wù)器端

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


image.png

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


image.png
image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末觉啊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沈贝,更是在濱河造成了極大的恐慌杠人,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗡善,居然都是意外死亡辑莫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門罩引,熙熙樓的掌柜王于貴愁眉苦臉地迎上來各吨,“玉大人,你說我怎么就攤上這事袁铐〗已眩” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵剔桨,是天一觀的道長屉更。 經(jīng)常有香客問我,道長洒缀,這世上最難降的妖魔是什么瑰谜? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮树绩,結(jié)果婚禮上萨脑,老公的妹妹穿的比我還像新娘。我一直安慰自己饺饭,他們只是感情好渤早,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著砰奕,像睡著了一般蛛芥。 火紅的嫁衣襯著肌膚如雪提鸟。 梳的紋絲不亂的頭發(fā)上军援,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音称勋,去河邊找鬼胸哥。 笑死,一個胖子當著我的面吹牛赡鲜,可吹牛的內(nèi)容都是我干的空厌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼银酬,長吁一口氣:“原來是場噩夢啊……” “哼嘲更!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揩瞪,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赋朦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宠哄,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡壹将,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毛嫉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诽俯。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖承粤,靈堂內(nèi)的尸體忽然破棺而出暴区,到底是詐尸還是另有隱情,我是刑警寧澤密任,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布颜启,位于F島的核電站,受9級特大地震影響浪讳,放射性物質(zhì)發(fā)生泄漏缰盏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一淹遵、第九天 我趴在偏房一處隱蔽的房頂上張望口猜。 院中可真熱鬧,春花似錦透揣、人聲如沸济炎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽须尚。三九已至,卻和暖如春侍咱,著一層夾襖步出監(jiān)牢的瞬間耐床,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工楔脯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撩轰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓昧廷,卻偏偏與公主長得像堪嫂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子木柬,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容