spydroid-ipcamera源碼分析(三):AudioStream和相關(guān)子類的音頻流操作

AudioStream類

AudioStream類是音頻流的基類,重寫了MediaStream的encodeWithMediaRecorder方法,實(shí)現(xiàn)了MediaRecorder錄制音頻的操作芥驳。同時(shí)作為抽象類申屹,它并沒有重寫encodeWithMediaCodec方法溉苛,而是留給子類去具體實(shí)現(xiàn)。

    @Override
    protected void encodeWithMediaRecorder() throws IOException {
        
        // We need a local socket to forward data output by the camera to the packetizer
        createSockets();

        Log.v(TAG,"Requested audio with "+mQuality.bitRate/1000+"kbps"+" at "+mQuality.samplingRate/1000+"kHz");
        
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setAudioSource(mAudioSource);
        mMediaRecorder.setOutputFormat(mOutputFormat);
        mMediaRecorder.setAudioEncoder(mAudioEncoder);
        mMediaRecorder.setAudioChannels(1);
        mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate);
        mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate);
        
        // We write the ouput of the camera in a local socket instead of a file !           
        // This one little trick makes streaming feasible quiet simply: data from the camera
        // can then be manipulated at the other end of the socket
        mMediaRecorder.setOutputFile(mSender.getFileDescriptor());

        mMediaRecorder.prepare();
        mMediaRecorder.start();

        try {
            // mReceiver.getInputStream contains the data from the camera
            // the mPacketizer encapsulates this stream in an RTP stream and send it over the network
            mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
            mPacketizer.setInputStream(mReceiver.getInputStream());
            mPacketizer.start();
            mStreaming = true;
        } catch (IOException e) {
            stop();
            throw new IOException("Something happened with the local sockets :/ Start failed !");
        }
        
    }

重寫encodeWithMediaRecorder方法贺喝,主要是MediaRecorder錄制音頻的操作菱鸥。首先啟動(dòng)本地Socket,再對(duì)MediaRecorder對(duì)象設(shè)置音頻來源躏鱼、音頻編碼氮采、音頻質(zhì)量等參數(shù),然后開始錄制染苛。這里值得一提的是鹊漠,MediaRecorder輸出的音頻數(shù)據(jù)是寫入Socket中而不是文件中,這樣就可以在Socket的另一端進(jìn)行操作茶行。最后一步是使用Packetizer對(duì)象將數(shù)據(jù)打包并發(fā)送到網(wǎng)絡(luò)傳輸躯概。

AACStream類

AACStream類是一個(gè)關(guān)于AAC音頻格式的AudioStream的子類,內(nèi)部封裝了對(duì)AAC音頻格式的配置和編碼操作拢军。

    private static boolean AACStreamingSupported() {
        if (Build.VERSION.SDK_INT<14) return false;
        try {
            MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

構(gòu)造函數(shù)中會(huì)判斷是否支持AAC編碼格式楞陷。

        // Checks if the user has supplied an exotic sampling rate
        int i=0;
        for (;i<AUDIO_SAMPLING_RATES.length;i++) {
            if (AUDIO_SAMPLING_RATES[i] == mQuality.samplingRate) {
                mSamplingRateIndex = i;
                break;
            }
        }
        // If he did, we force a reasonable one: 16 kHz
        if (i>12) mQuality.samplingRate = 16000;

configure()方法中的部分代碼。在start()開始時(shí)需要先檢查一下采樣率配置信息茉唉,不符合規(guī)范則強(qiáng)行設(shè)置默認(rèn)采樣率固蛾。

    @Override
    protected void encodeWithMediaRecorder() throws IOException {
        testADTS();
        ((AACADTSPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
        super.encodeWithMediaRecorder();
    }

重寫encodeWithMediaRecorder方法,testADTS()方法是先從麥克風(fēng)記錄AAC ADTS的簡短樣本度陆,以了解該設(shè)備支持的真實(shí)的采樣率艾凯,便于設(shè)置配置信息和防止報(bào)錯(cuò)。篇幅原因懂傀,這里就不貼出testADTS()的代碼了趾诗。

下面我們開始分析重寫encodeWithMediaCodec()方法里面的內(nèi)容,我把逐句分析寫在注釋里面蹬蚁。

        //計(jì)算出緩沖區(qū)的大小
        final int bufferSize = AudioRecord.getMinBufferSize(mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)*2;
        //設(shè)置打包器的采樣率
        ((AACLATMPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
        //實(shí)例化AudioRecord恃泪,參數(shù)依次為:聲音來源、采樣率犀斋、聲道數(shù)贝乎、編碼方式、緩沖區(qū)
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
        //實(shí)例化MediaCodec編碼器
        mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
        //配置信息依次為:格式叽粹、位速率览效、頻道數(shù)、采樣率虫几、AAC文件锤灿、最大輸入緩沖區(qū)
        MediaFormat format = new MediaFormat();
        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
        format.setInteger(MediaFormat.KEY_BIT_RATE, mQuality.bitRate);
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mQuality.samplingRate);
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        //開始錄制音頻
        mAudioRecord.startRecording();
        mMediaCodec.start();
        
        //設(shè)置編碼流,在后面的文章會(huì)詳細(xì)講到
        final MediaCodecInputStream inputStream = new MediaCodecInputStream(mMediaCodec);
        final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

        //開啟一個(gè)線程辆脸,讀取和處理
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int len = 0, bufferIndex = 0;
                try {
                    //無限循環(huán)讀取
                    while (!Thread.interrupted()) {
                        //從輸入流隊(duì)列中取數(shù)據(jù)進(jìn)行編碼操作(出隊(duì)列)但校。
                        bufferIndex = mMediaCodec.dequeueInputBuffer(10000);
                        if (bufferIndex>=0) {
                            inputBuffers[bufferIndex].clear();
                            //從mAudioRecord讀取數(shù)據(jù)到inputBuffers[bufferIndex]中
                            len = mAudioRecord.read(inputBuffers[bufferIndex], bufferSize);
                            if (len ==  AudioRecord.ERROR_INVALID_OPERATION || len == AudioRecord.ERROR_BAD_VALUE) {
                                Log.e(TAG,"An error occured with the AudioRecord API !");
                            } else {
                                //Log.v(TAG,"Pushing raw audio to the decoder: len="+len+" bs: "+inputBuffers[bufferIndex].capacity());
                                //輸入流入隊(duì)列(往編碼器中添加數(shù)據(jù)做編碼處理)
                                mMediaCodec.queueInputBuffer(bufferIndex, 0, len, System.nanoTime()/1000, 0);
                            }
                        }
                    }
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
            }
        });

        mThread.start();
        //把編碼完成的數(shù)據(jù)流封裝打包并進(jìn)行網(wǎng)絡(luò)傳輸
        // The packetizer encapsulates this stream in an RTP stream and send it over the network
        mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
        mPacketizer.setInputStream(inputStream);
        mPacketizer.start();

        mStreaming = true;

以上可以看到,重寫encodeWithMediaCodec()方法主要流程就是:實(shí)例化和配置AudioRecord用來錄取音頻數(shù)據(jù)啡氢,實(shí)例化和配置MediaCodec用于對(duì)數(shù)據(jù)編碼状囱,開啟一個(gè)線程循環(huán)讀取AudioRecord錄取的音頻數(shù)據(jù)流州刽,并將原始音頻數(shù)據(jù)流添加到MediaCodec編碼器中進(jìn)行編碼,然后將編碼完成的數(shù)據(jù)流通過Packetizer打包器打包并發(fā)送出去浪箭。

AMRNBStream類

AMRNBStream類是一個(gè)關(guān)于ANR音頻格式的AudioStream的子類穗椅。

@Override
    protected void encodeWithMediaCodec() throws IOException {
        super.encodeWithMediaRecorder();
    }

AMRNBStream可操作的動(dòng)作不多,這里重寫encodeWithMediaCodec()方法直接指向了super.encodeWithMediaRecorder()奶栖。關(guān)于AAC和AMR的比較可參考:AAC和AMR音頻編碼標(biāo)準(zhǔn)介紹

至此我們完整的了解了音頻數(shù)據(jù)流的配置匹表、采集、編碼的整個(gè)過程宣鄙,下一篇我們將分析VideoStream類和它的子類袍镀,詳細(xì)了解視頻流的配置、采集和編碼的流程冻晤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苇羡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鼻弧,更是在濱河造成了極大的恐慌设江,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攘轩,死亡現(xiàn)場(chǎng)離奇詭異叉存,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)度帮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門歼捏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笨篷,你說我怎么就攤上這事瞳秽。” “怎么了率翅?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵练俐,是天一觀的道長。 經(jīng)常有香客問我安聘,道長痰洒,這世上最難降的妖魔是什么瓢棒? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任浴韭,我火速辦了婚禮,結(jié)果婚禮上脯宿,老公的妹妹穿的比我還像新娘念颈。我一直安慰自己,他們只是感情好连霉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布榴芳。 她就那樣靜靜地躺著嗡靡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窟感。 梳的紋絲不亂的頭發(fā)上讨彼,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音柿祈,去河邊找鬼哈误。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躏嚎,可吹牛的內(nèi)容都是我干的蜜自。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼卢佣,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼重荠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虚茶,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤戈鲁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嘹叫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荞彼,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年待笑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸣皂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暮蹂,死狀恐怖寞缝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仰泻,我是刑警寧澤荆陆,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站集侯,受9級(jí)特大地震影響被啼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棠枉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一浓体、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辈讶,春花似錦命浴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媳溺。三九已至,卻和暖如春碍讯,著一層夾襖步出監(jiān)牢的瞬間悬蔽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國打工捉兴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屯阀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓轴术,卻偏偏與公主長得像难衰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逗栽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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