Android中音频设置的那些事情

最近遇到很多声音方面的问题,仔细的研究了一轮。之前也看过书,但是实际上还是有很多问题。关于耳机、蓝牙、外放、声音通道等等的处理。

值得一提的是,Android中系统承包了音频的控制,所以设置对媒体通道,也就完成了80%的功能。
这也是我请教别人我遇到的问题,别人总是莫名其妙的原因,因为系统把事情都做好了。
如果你发现自己在声音的处理上需要多很多事情,估计是你没使用对方法。

如何切换音频通道?

有几个关键的设置:

STREAM_VOCIE_CALL
CONTENT_TYPE_SPEECH
USAGE_VOICE_COMMUNICATION

网上很多资料都是。

public class TestActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);

		setVolumeControlStream(AudioManager.STREAM_MUSIC);

	}
}	

也不是不能这样设置,起码Android原生的系统是没有任何问题的。
但是小米等这些第三方的系统,会有莫名其妙的问题。

如何监听耳机、蓝牙、外放,并处理切声音通道的切换?

无需处理,系统已经实现

音频焦点如何处理?

一个场景是,我们希望在播放的时候,把别的软件的声音给停掉,避免声音重叠。

可以这样做,每次打开都请求一下焦点,离开应用都释放焦点。

PS:这个的实现靠应用的自觉性,对主流的播放器是有效的。

public class AudioFocusHandler {

    private Handler handler = new Handler();
    private final AudioManager.OnAudioFocusChangeListener mChangeListener = focusChange -> {
        if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
            handler.post(this::release);
        }
    };

    public void request() {
        AudioManager am = (AudioManager) CGApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
        if (am == null) return;
        int result;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            AudioAttributes playbackAttributes = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build();
            AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN
            ).setAudioAttributes(playbackAttributes
            ).setAcceptsDelayedFocusGain(true
            ).setOnAudioFocusChangeListener(mChangeListener, handler
            ).build();
            result = am.requestAudioFocus(focusRequest);
        } else {
            result = am.requestAudioFocus(mChangeListener,
                    AudioManager.STREAM_MUSIC,
                    AudioManager.AUDIOFOCUS_GAIN);
        }
        Lg.d(AudioFocusHandler.class.getSimpleName(), "is granted:",
                AudioManager.AUDIOFOCUS_REQUEST_GRANTED == result);
    }

    public void release() {
        AudioManager am = (AudioManager) CGApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
        if (am == null) return;
        int result = am.abandonAudioFocus(mChangeListener);
        Lg.d(AudioFocusHandler.class.getSimpleName(), "is release:",
                AudioManager.AUDIOFOCUS_REQUEST_GRANTED == result);
    }
}

音量的调节与控制如何处理?

无需处理,系统已经实现

一些背景知识的节选

下面内容节选自《音视频开发进阶指南:基于Android和iOS平台的实践》
强烈建议买这本书,如果你要做音视频开发的话。

由于AudioTrack是Android SDK层提供的最底层的音频播放API,因此只允许输入裸数据。和MediaPlayer相比,对于一个压缩的音频文件(比如MP3、AAC等文件),它需要自行实现解码操作和缓冲区控制。因为这里只涉及AudioTrack的音频渲染端(解码部分已经在前面章节中介绍过了,对于缓冲区的控制机制,后续章节将会详细讲解),所以本节只介绍如何使用AudioTrack渲染音频PCM数据。

首先来看一下AudioTrack的工作流程,具体如下。

1)根据音频参数信息,配置出一个AudioTrack的实例。
2)调用play方法,将AudioTrack切换到播放状态。
3)启动播放线程,循环向AudioTrack的缓冲区中写入音频数据。
4)当数据写完或者停止播放的时候,停止播放线程,并且释放所有资源。

根据AudioTrack的上述工作流程,本节将以4个小部分分别介绍每个流程的详细步骤。

1.配置AudioTrack

先来看一下AudioTrack的参数配置,要想构造出一个AudioTrack类型的实例,必须先了解其构造函数原型,代码如下所示:

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig,
	int audioFormat, int bufferSizeInBytes, int mode);

其中构造函数的参数说明如下。
streamType, Android手机上提供了多重音频管理策略(读者按一下手机侧边的按键,可以看到有多个音量管理,这其实就是不同音频策略的音量控制展示),当系统有多个进程需要播放音频的时候,管理策略会决定最终的呈现效果,该参数的可选值将以常量的形式定义在类AudioManager中,主要包括以下内容。

STREAM_VOCIE_CALL:电话声音
STREAM_SYSTEM:系统声音

STREAM_RING:铃声

STREAM_MUSCI:音乐声

STREAM_ALARM:警告声

STREAM_NOTIFICATION:通知声

sampleRateInHz,采样率,即播放的音频每秒钟会有多少次采样,可选用的采样频率列表为:8000、16000、22050、24000、32000、44100、48000等,大家可以根据自己的应用场景进行合理的选择。
channelConf ig,声道数(通道数)的配置,可选值以常量的形式配置在类AudioFormat中,常用的是CHANNEL_IN_MONO(单声道)、CHANNEL_IN_STEREO(双声道),因为现在大多数手机的麦克风都是伪立体声的采集,为了性能考虑,笔者建议使用单声道进行采集,而转变为立体声的过程可以在声音的特效处理阶段来完成。
audioFormat,该参数是用来配置“数据位宽”的,即采样格式,可选值以常量的形式定义在类AudioFormat中,分别为ENCODING_PCM_16BIT(16bit)、ENCODING_PCM_8BIT(8bit),注意,前者是可以兼容所有Android手机的。
bufferSizeInBytes,其配置的是AudioTrack内部的音频缓冲区的大小,AudioTrack类提供了一个帮助开发者确定bufferSizeInBytes的函数,其原型具体如下:

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

在实际开发中,强烈建议由该函数计算出需要传入的bufferSizeInBytes,而不是自己手动计算。
mode, AudioTrack提供了两种播放模式,可选的值以常量的形式定义在类AudioTrack中,一个是MODE_STATIC,需要一次性将所有的数据都写入播放缓冲区中,简单高效,通常用于播放铃声、系统提醒的音频片段;另一个是MODE_STREAM,需要按照一定的时间间隔不间断地写入音频数据,理论上它可以应用于任何音频播放的场景。

2.将AudioTrack切换到播放状态

首先判断AudioTrack实例是否初始化成功,如果当前状态处于初始化成功的状态,那么就调用它的play方法,并切换到播放状态,代码如下:

if (null ! = audioTrack && audioTrack.getState() ! = AudioTrack.STATE_UNINITIALIZED){
        
	audioTrack.play();}

3.开启播放线程

首先创建一个播放线程,代码如下:

playerThread = new Thread(new PlayerThread(), "playerThread");
    
playerThread.start();

接下来看看该线程中执行的任务,代码如下:

class PlayerThread implements Runnable {private short[] samples;public void run() {
            
		samples = new short[minBufferSize];while(! isStop) {int actualSize = decoder.readSamples(samples);
                
		audioTrack.write(samples, actualSize);}}}

线程中的minBufferSize是在初始化AudioTrack的时候获得的缓冲区大小,会对其进行换算,即以2个字节表示一个采样的大小,也就是2倍的关系(因为初始化的时候是以字节为单位的); decoder是一个解码器,假设已经初始化成功,最后将调用write方法把从解码器中获得的PCM采样数据写入AudioTrack的缓冲区中,注意此方法是阻塞的方法,比如:一般要写入200ms的音频数据需要执行接近200ms的时间。

4.销毁资源

首先停止AudioTrack,代码如下:

if (null ! = audioTrack && audioTrack.getState() ! = AudioTrack.STATE_UNINITIALIZED){
        
	audioTrack.stop();}

然后停止线程:

isStop = true;if (null ! = playerThread) {
        
	playerThread.join();
        
	playerThread = null;}

最后释放AudioTrack:

audioTrack.release();

具体实例请参看代码仓库中的AudioPlayer项目的AudioTrack部分,需要把项目中resource目录下的音频文件放入目标设备的sdcard根目录下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值