溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android音頻開發(fā)(3):如何播放一幀音頻

發(fā)布時間:2020-08-28 01:44:44 來源:網(wǎng)絡(luò) 閱讀:17080 作者:Jhuster 欄目:移動開發(fā)

本文重點關(guān)注如何在Android平臺上播放一幀音頻數(shù)據(jù)。閱讀本文之前,建議先讀一下《Android音頻開發(fā)(1):基礎(chǔ)知識》,因為音頻開發(fā)過程中,經(jīng)常要涉及到這些基礎(chǔ)知識,掌握了這些重要的概念后,開發(fā)過程中的很多參數(shù)和流程就會更加容易理解。


Android SDK 提供了3套音頻播放的API,分別是:MediaPlayer,SoundPool,AudioTrack,關(guān)于它們的區(qū)別可以看這篇文章:《Intro to the three Android Audio APIs》,簡單來說,MediaPlayer 更加適合在后臺長時間播放本地音樂文件或者在線的流式資源; SoundPool 則適合播放比較短的音頻片段,比如游戲聲音、按鍵聲、鈴聲片段等等,它可以同時播放多個音頻; 而 AudioTrack 則更接近底層,提供了非常強大的控制能力,支持低延遲播放,適合流媒體和VoIP語音電話等場景。


音頻的開發(fā),更廣泛地應(yīng)用不僅僅局限于播放本地文件或者音頻片段,因此,本文重點關(guān)注如何利AudioTrack API 來播放音頻數(shù)據(jù)(注意,使用AudioTrack播放的音頻必須是解碼后的PCM數(shù)據(jù))。


1. AudioTrack 的工作流程


首先,我們了解一下 AudioTrack 的工作流程:


(1) 配置參數(shù),初始化內(nèi)部的音頻播放緩沖區(qū)

(2) 開始播放

(3) 需要一個線程,不斷地向 AudioTrack 的緩沖區(qū)“寫入”音頻數(shù)據(jù),注意,這個過程一定要及時,否則就會出現(xiàn)“underrun”的錯誤,該錯誤在音頻開發(fā)中比較常見,意味著應(yīng)用層沒有及時地“送入”音頻數(shù)據(jù),導(dǎo)致內(nèi)部的音頻播放緩沖區(qū)為空。

(4) 停止播放,釋放資源


2. AudioTrack 的參數(shù)配置


Android音頻開發(fā)(3):如何播放一幀音頻


上面是 AudioTrack 的構(gòu)造函數(shù)原型,主要靠構(gòu)造函數(shù)來配置相關(guān)的參數(shù),下面一一解釋(再次建議先閱讀一下《Android音頻開發(fā)(1):基礎(chǔ)知識》):


(1) streamType


這個參數(shù)代表著當(dāng)前應(yīng)用使用的哪一種音頻管理策略,當(dāng)系統(tǒng)有多個進程需要播放音頻時,這個管理策略會決定最終的展現(xiàn)效果,該參數(shù)的可選的值以常量的形式定義在 AudioManager 類中,主要包括:


STREAM_VOCIE_CALL:電話聲音

STREAM_SYSTEM:系統(tǒng)聲音

STREAM_RING:鈴聲

STREAM_MUSCI:音樂聲

STREAM_ALARM:警告聲

STREAM_NOTIFICATION:通知聲


(2) sampleRateInHz


采樣率,從AudioTrack源碼的“audioParamCheck”函數(shù)可以看到,這個采樣率的取值范圍必須在 4000Hz~192000Hz 之間。


(3) channelConfig


通道數(shù)的配置,可選的值以常量的形式定義在 AudioFormat 類中,常用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道)


(4) audioFormat


這個參數(shù)是用來配置“數(shù)據(jù)位寬”的,可選的值也是以常量的形式定義在 AudioFormat 類中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保證兼容所有Android手機的。


(5) bufferSizeInBytes


這個是最難理解又最重要的一個參數(shù),它配置的是 AudioTrack 內(nèi)部的音頻緩沖區(qū)的大小,該緩沖區(qū)的值不能低于一幀“音頻幀”(Frame)的大小,而前一篇文章介紹過,一幀音頻幀的大小計算如下:


int size = 采樣率 x 位寬 x 采樣時間 x 通道數(shù)


采樣時間一般取 2.5ms~120ms 之間,由廠商或者具體的應(yīng)用決定,我們其實可以推斷,每一幀的采樣時間取得越短,產(chǎn)生的延時就應(yīng)該會越小,當(dāng)然,碎片化的數(shù)據(jù)也就會越多。


在Android開發(fā)中,AudioTrack 類提供了一個幫助你確定這個 bufferSizeInBytes 的函數(shù),原型如下:


int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);


不同的廠商的底層實現(xiàn)是不一樣的,但無外乎就是根據(jù)上面的計算公式得到一幀的大小,音頻緩沖區(qū)的大小則必須是一幀大小的2~N倍,有興趣的朋友可以繼續(xù)深入源碼探究探究。


實際開發(fā)中,強烈建議由該函數(shù)計算出需要傳入的 bufferSizeInBytes,而不是自己手動計算。


(6) mode


AudioTrack 提供了兩種播放模式,一種是 static 方式,一種是 streaming 方式,前者需要一次性將所有的數(shù)據(jù)都寫入播放緩沖區(qū),簡單高效,通常用于播放鈴聲、系統(tǒng)提醒的音頻片段; 后者則是按照一定的時間間隔不間斷地寫入音頻數(shù)據(jù),理論上它可用于任何音頻播放的場景。


可選的值以常量的形式定義在 AudioTrack 類中,一個是 MODE_STATIC,另一個是 MODE_STREAM,根據(jù)具體的應(yīng)用傳入對應(yīng)的值即可。


4. 示例代碼


我將 AudioTrack 類的接口簡單封裝了一下,提供了一個 AudioPlayer 類,可以到我的Github下載:https://github.com/Jhuster/Android/blob/master/Audio/AudioPlayer.java


這里也貼出來一份:


/*
 *  COPYRIGHT NOTICE  
 *  Copyright (C) 2016, Jhuster <lujun.hust@gmail.com>
 *  https://github.com/Jhuster/Android
 *   
 *  @license under the Apache License, Version 2.0 
 *
 *  @file    AudioPlayer.java
 *  
 *  @version 1.0     
 *  @author  Jhuster
 *  @date    2016/03/13    
 */
package com.jhuster.audiodemo;

import android.util.Log;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

public class AudioPlayer {
    
    private static final String TAG = "AudioPlayer";

    private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
    private static final int DEFAULT_SAMPLE_RATE = 44100;
    private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
    private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM;
            
    private boolean mIsPlayStarted = false;
    private int mMinBufferSize = 0;
    private AudioTrack mAudioTrack;  
    
    public boolean startPlayer() {
        return startPlayer(DEFAULT_STREAM_TYPE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT);
    }
    
    public boolean startPlayer(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) {
        
        if (mIsPlayStarted) {
            Log.e(TAG, "Player already started !");
            return false;
        }
        
        mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
        if (mMinBufferSize == AudioTrack.ERROR_BAD_VALUE) {
            Log.e(TAG, "Invalid parameter !");
            return false;
        }
        Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");
        
        mAudioTrack = new AudioTrack(streamType,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize,DEFAULT_PLAY_MODE);
        if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
            Log.e(TAG, "AudioTrack initialize fail !");
            return false;
        }            
        
        mIsPlayStarted = true;
        
        Log.d(TAG, "Start audio player success !");
        
        return true;
    }
    
    public int getMinBufferSize() {
        return mMinBufferSize;
    }
    
    public void stopPlayer() {
        
        if (!mIsPlayStarted) {
            return;
        }
        
        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            mAudioTrack.stop();                        
        }
        
        mAudioTrack.release();
        mIsPlayStarted = false;
           
        Log.d(TAG, "Stop audio player success !");
    }
    
    public boolean play(byte[] audioData, int offsetInBytes, int sizeInBytes) {
        
        if (!mIsPlayStarted) {
            Log.e(TAG, "Player not started !");
            return false;
        }
        
        if (sizeInBytes < mMinBufferSize) {
            Log.e(TAG, "audio data is not enough !");
            return false;
        }
        
        if (mAudioTrack.write(audioData,offsetInBytes,sizeInBytes) != sizeInBytes) {                
            Log.e(TAG, "Could not write all the samples to the audio device !");
        }                                   
                                                   
        mAudioTrack.play();
        
        Log.d(TAG , "OK, Played "+sizeInBytes+" bytes !");
        
        return true;
    }
}


5. 小結(jié)


關(guān)于如何在Android平臺使用AudioTrack播放一幀音頻數(shù)據(jù)就介紹到這兒了,文章中有不清楚的地方歡迎留言或者來信 lujun.hust@gmail.com 交流,或者關(guān)注我的新浪微博 @盧_俊 或者 微信公眾號 @Jhuster 獲取最新的文章和資訊。

Android音頻開發(fā)(3):如何播放一幀音頻

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI