1. 程式人生 > >android實現之高清音訊錄製編碼

android實現之高清音訊錄製編碼

場景說明:

在現在有安卓手機中AudioRecord錄製出的音訊是通過揚聲器或表克風錄製出來的。對於外錄即揚聲器錄製的視訊而言,音質十分渣。對於想要錄製現場的聲音如開會時,兩人對話時聲音證據儲存的聲音錄製,完全不能滿足需求。鑑於此,研究安卓平臺下高清音訊錄製的解決方案。

原理:本人實現原理很簡單,分為兩部分:1, 原始音訊採集,2 音訊編碼 。

1,原始音訊採集:

還是通過安卓的API AudioRecord 去採集聲音,這個API錄製聲音不行,但在安卓上採集音訊通常只能通過這個API,因為它直接封裝了底層audio_device裝置。

想要自己另外實現一套採集手段,不大實現且費力。我們可以通過設定一定的音訊取樣率,通道型別,位元速率,緩衝區大小來初始化AudioRecord,讓它來為我們

採集原始音訊,即 PCM 格式音訊。

2 , 音訊轉碼:

採集到PCM音訊後,將PCM格式轉碼為MP3格式音訊。這裡我用的是LAME編碼器,它是一款出色的音訊編碼器,摘自網上的一段介紹,LAME(mitiok.ma.cx)編碼出來的MP3音色純厚、空間寬廣、低音清晰、細節表現良好,它獨創的心理音響模型技術保證了CD音訊還原的真實性。也不知是真是假。呵呵,反正本人用的還不錯,保留了MP3音訊的高清質量。

具體實現步驟:

1,音訊採集:

    這裡使用設定的取樣率,位元速率,緩衝區大小初始化AudioRecord

    並且設制處理音訊的間隔時間及回撥監聽。

/**
     * Initialize audio recorder
     */
    private void initAudioRecorder() throws IOException {
        int bytesPerFrame = audioFormat.getBytesPerFrame();
        /* Get number of samples. Calculate the buffer size (round up to the
           factor of given frame size) */
        int frameSize = AudioRecord.getMinBufferSize(samplingRate,
                channelConfig, audioFormat.getAudioFormat()) / bytesPerFrame;
        if (frameSize % FRAME_COUNT != 0) {
            frameSize = frameSize + (FRAME_COUNT - frameSize % FRAME_COUNT);
            Log.d(TAG, "Frame size: " + frameSize);
        }
        
        bufferSize = frameSize * bytesPerFrame;

        /* Setup audio recorder */
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                samplingRate, channelConfig, audioFormat.getAudioFormat(),
                bufferSize);
        
        // Setup RingBuffer. Currently is 10 times size of hardware buffer
        // Initialize buffer to hold data
        ringBuffer = new RingBuffer(10 * bufferSize);
        buffer = new byte[bufferSize];
        
        // Initialize lame buffer
        // mp3 sampling rate is the same as the recorded pcm sampling rate
        // The bit rate is 32kbps
        RecordLib.init(samplingRate, 1, samplingRate, BIT_RATE);
        
        // Initialize the place to put mp3 file
//        String externalPath = Environment.getExternalStorageDirectory()
//                .getAbsolutePath();
//        File directory = new File(externalPath + "/" + "AudioRecorder");
//        if (!directory.exists()) {
//            directory.mkdirs();
//            Log.d(TAG, "Created directory");
//        }
        mp3File = new File(mFilePath);
        os = new FileOutputStream(mp3File);

        // Create and run thread used to encode data
        // The thread will
        encodeThread = new DataEncodeThread(ringBuffer, os, bufferSize);
        encodeThread.start();
        
        audioRecord.setRecordPositionUpdateListener(encodeThread, encodeThread.getHandler());
        audioRecord.setPositionNotificationPeriod(FRAME_COUNT);
        
        threads = new AcquireAudioPower();
    }

  2,音訊轉碼

  通過AudioRecord的回撥函式來進行實時音訊轉碼
    @Override
    public void onPeriodicNotification(AudioRecord recorder) {
        processData();
    }
    
    /**
     * Get data from ring buffer
     * Encode it to mp3 frames using lame encoder
     * @return  Number of bytes read from ring buffer
     *             0 in case there is no data left
     */
    private int processData() {        
        int bytes = ringBuffer.read(buffer, bufferSize);
        //Log.d(TAG, "Read size: " + bytes);
        if (bytes > 0) {
            short[] innerBuf = new short[bytes / 2];
            ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(innerBuf);
            int encodedSize = RecordLib.encode(innerBuf, innerBuf, bytes / 2, mp3Buffer);
            
            if (encodedSize < 0) {
                Log.e(TAG, "Lame encoded size: " + encodedSize);                
            }

            try {
                os.write(mp3Buffer, 0, encodedSize);
            } catch (IOException e) {
                Log.e(TAG, "Unable to write to file");
            }
            
            return bytes;
        }
        return 0;
    }

  3,lame編碼技術實現:

       
JNIEXPORT void JNICALL Java_com_cunnar_lame_RecordLib_init(
        JNIEnv *env, jclass cls, jint inSamplerate, jint outChannel,
        jint outSamplerate, jint outBitrate, jint quality) {
    if (glf != NULL) {
        lame_close(glf);
        glf = NULL;
    }
    glf = lame_init();
    lame_set_in_samplerate(glf, inSamplerate);
    lame_set_num_channels(glf, outChannel);
    lame_set_out_samplerate(glf, outSamplerate);
    lame_set_brate(glf, outBitrate);
    lame_set_quality(glf, quality);
    lame_init_params(glf);
}

JNIEXPORT jint JNICALL Java_com_cunnar_lame_RecordLib_encode(
        JNIEnv *env, jclass cls, jshortArray buffer_l, jshortArray buffer_r,
        jint samples, jbyteArray mp3buf) {
    jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);

    jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);

    const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
    jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);

    int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
            samples, j_mp3buf, mp3buf_size);

    (*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
    (*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
    (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);

    return result;
}

JNIEXPORT jint JNICALL Java_com_cunnar_lame_RecordLib_flush(
        JNIEnv *env, jclass cls, jbyteArray mp3buf) {
    const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
    jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);

    int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);

    (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);

    return result;
}

JNIEXPORT void JNICALL Java_com_cunnar_lame_RecordLib_close(
        JNIEnv *env, jclass cls) {
    lame_close(glf);
    glf = NULL;
}

  總結:將安卓採集到的原始音訊做一次到MP3格式的編碼,即可達到高清錄製的效果。這裡可以再進行聲音變聲的效果,有興趣的可以自己研究一下。

              一般都是通過控制採集率,位元速率來實現變聲的。

  下面附上本人的實現原始碼。下載原始碼

相關推薦

android實現清音錄製編碼

場景說明: 在現在有安卓手機中AudioRecord錄製出的音訊是通過揚聲器或表克風錄製出來的。對於外錄即揚聲器錄製的視訊而言,音質十分渣。對於想要錄製現場的聲音如開會時,兩人對話時聲音證據儲存的聲音錄製,完全不能滿足需求。鑑於此,研究安卓平臺下高清音訊錄製的解決方案。

Android Studio德地圖實現定位和3D地圖顯示

tor uil track width 博客 5.0 eight ext wid 在應用開發中,地圖開發是常常須要使用的“組件”,國內比較出名的是就是百度地圖和高德地

Android 小樣仿淘寶時間軸物流資訊

最近做訂單系統,用到時間軸資訊,首先想到的是淘寶的物流時間軸(網購狗)。不多廢話,首先來看看淘寶物流資訊的樣式 這裡給出兩種解決方法: 使用LinearLayout動態新增view生成物流資訊

Android實現圖片 斯模糊,以及圖片映象 翻轉。

好久沒寫部落格,發現不止手癢,,原來不學習還是會頹廢的….. 哎….. 速速找了網上比較感興趣的功能,,看著前人大神門的方法實現,方便自己也方便別人: 上圖: 程式碼: MainActivity.class package com.hero.

Android開發亮引導

看下圖,今天的任務就是它了,app 的高亮引導的實現,找到幾個github上面已經實現的庫,下載下來原始碼對比分析實現原理,整理自己的知識體系。下面是其中一個的效果圖(我用DialogFragment實

Android 實現圖片斯模糊演算法,真正有效的工具類

import android.graphics.Bitmap; import android.graphics.Color; public class FastBlur {     /***      * 高斯模糊演算法      * @param bmp 要處理的影象  

android開發仿中國建設銀行App

皇天不負有心人,今天終於被我找到了這篇神文!關於高仿中國建設銀行App的一篇Blog,於是我就不自覺的把它消化成了我的東西了,嘿嘿!不過我是有節操滴,在本文的最後我貼上了此文轉載於哪裡?也希望各位在以後的學習道路上,不要做忘恩負義的人! 各位,準備好了嗎?讓我們一起來看看大

Android實現快速斯模糊

高斯模糊想必大家都聽說過,百度百科對於高斯模糊的解釋為: 高斯模糊(英語:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等影象處理軟體中廣泛使用的處理效果,通常用它來減少影象噪聲以及降低細節層次。所謂"模糊",

Android短視中如何實現720P磨皮美顏錄製

視訊中磨皮、美顏功能已成為剛需,那麼如何在Android短視訊中實現720P磨皮美顏錄製?本篇文章中,網易雲信資深開發工程師將向大家介紹具體的操作方法。   相關閱讀推薦 《短視訊技術詳解:Android端的短視訊開發技術》 《如何快速實現移動端短視訊功能?》 在And

android學習筆記】activity間的通訊案例德地圖實現天氣查詢

【概述】app實現天氣查詢是再正常不過的功能了,又因為往往不止一個activity去獲取資料,那就想到封裝一個類,需要時去呼叫獲取即可。 【注】因為看文件還有點懵,故將自己抓腦寫的程式碼記錄下,以便查詢 【思路】activity傳送請求--獲取地址--根據地址獲取天氣

Android仿騰微博

            匯入原始碼到eclipse出現報錯的童鞋注意:我用的是utf-8編碼,因為騰訊的api是utf-8編碼,其實我也不喜歡改來該去的!        國慶後,一直在忙,都沒有什麼時間做自己的事情,哎,上班的孩子傷不起啊!這個微博也是斷斷續續的,每天晚

Android 實現仿iOS桌面效果可拖動的GridView(上)

     最近專案中遇到一個LIstview的拖動效果,github上一搜發現有叫DragListview的開源專案,然後自己再小手一搜拖動排序的GridView,卻沒發現什麼很全很好的開源專案,後

Android開發通過藍芽耳機實現飛語音識別的功能

近階段在開發一款app,實現通過藍芽耳機進行訊飛語音識別,獲取識別結果之後再通過語音合成從藍芽耳機播報出識別結果。上網也查了很多資料,大多是說通過一下兩行程式碼:      mAudioManager.setBluetoothScoOn(true);           

Android筆記(圖片斯+Glide實現微信圖片載入策略+仿微信進度條)

很久以前就想自己實現一下仿微信圖片載入的那種策略了,先載入一張模糊的圖片,然後再載入清晰大圖,今天研究了一下,不過要是Glide支援進度條顯示就好了,不得不說Glide很強大, 不囉嗦了,直接上程式碼了。 首先看看高斯模糊到底怎麼實現,你問我我也不會(^__

Android開發布局文件裏實現OnClick事件關聯處理方法

intent dsm nbsp ext 關聯 you vertica findview 時間 一般監聽OnClickListener事件,我們都是通過Button button = (Button)findViewById(....); button.se

Keepalive 可用實現

fwmark lvs nginx keepalive1 概述本文將介紹三個Keepalive高可用的實現案例,分別是keepalive實現LVS高可用,keepalive通過fwmark實現LVS高可用,keepalive實現Nginx高可用。2 實驗準備.(1) 各節點時間必須同步,這個操作很關鍵。

2017.12.18 Android開發消息隊列(實現子線程修改UI組件)

nds ace text read exce xtend prot ktr sta 1.界面布局,以及組件初始化: 組件初始化: private Button button; private Handler handler; @Ove

實現redis可用主從sentinel

redis sentinel redis主從 redis高可用 sentinel作用 監控(Monitoring): Sentinel 會不斷地檢查你的主服務器和從服務器是否運作正常。 提醒(Notification): 當被監控的某個 Redis 服務器出現問題時, Sentinel 可以

Android面試HashMap的實現原理

amp 安全 itl 轉載 提高效率 基礎上 ash cti data- 1、HashMap與HashTable的區別 HashMap允許key和value為null; HashMap是非同步的,線程不安全,也可以通過Collections.synchro

基於nginx+swoole+phalcon+atlas實現性能負載均衡集群系列【構建篇】

p12 adb 列表 服務器性能 nodeps devel unit tcl aio 一、簡介   php一直詬病於性能,可對開發者如此友好的語言為什麽不能登上大雅之堂? 於是php一線開發者站了出來。 先有鳥哥優化php引擎,又有rango大神開源swoole。至此,基