效果圖
源碼
源碼下載,請先移步Android左右聲道的控制
我這裏主要是用到了AudioTrack實現的左右聲道的控制,手機一般都只有兩個聲道,即左聲道和右聲道,我們在輸出的時候可以選擇單聲道,也可以選擇雙聲道(立體聲)。
查看了AudioTrack
的API,提供了play()
、pause()
、stop()
、write()
等一系列的方法。
通過write()
方法,可以實現將音頻數據發送出去(播放出來)。
AudioTrack對象的構造
有三個構造方法
AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
AudioTrack (int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode, int sessionId)
AudioTrack (AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)
主要參數有如下幾個
streamType:以什麼形式播放
- STREAM_VOICE_CALL
- STREAM_SYSTEM
- STREAM_RING
- STREAM_MUSIC
- STREAM_ALARM
- STREAM_NOTIFICATION
sampleRateInHz:採樣率
channelConfig:聲道
- AudioFormat.CHANNEL_OUT_MONO:輸出單聲道音頻數據
- AudioFormat.CHANNEL_OUT_STEREO:輸出雙聲道音頻數據(立體聲)
audioFormat:音頻數據格式
mode:緩衝模式
- MODE_STATIC:一次性將音頻載入以後再播放
- MODE_STREAM:以流的形式,加載一點就播放一點
把channelConfig的相關參數都看了一遍,沒發現有可以指定向某聲道發送數據的,只能通過AudioFormat.CHANNEL_OUT_MONO
和AudioFormat.CHANNEL_OUT_STEREO
選擇是輸出單聲道的音頻數據還是雙聲道的音頻數據。
左右聲道控制
構造的時候不能選擇指定聲道輸出音頻,但是有這樣一個方法
setStereoVolume(float leftGain, float rightGain)
可以通過把某一個聲道的音量設置到最小,達到只想某個聲道輸出音頻的效果。
我自己也有點”呵呵“,但是也沒有發現還有別的方法可以實現這樣的效果。
這個方法還有一點小問題,在個別手機上,即使將某個聲道的聲音設置到了最小,也還是會有一點聲音,這個我也還沒有搞清楚爲什麼,個人猜測可能和手機硬件有關係。
封裝
我這裏的緩衝模式使用的MODE_STREAM
的形式,以流的形式播放,因爲這個邏輯要稍微複雜一點,尤其是暫停以後再繼續播放的位置。
package kong.qingwei.androidsoundmanagerdemo;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by kqw on 2016/8/26.
* 播放音樂的線程
*/
public class PlayThread extends Thread {
// 採樣率
private int mSampleRateInHz = 16000;
// 單聲道
private int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO;
// 雙聲道(立體聲)
// private int mChannelConfig = AudioFormat.CHANNEL_OUT_STEREO;
private static final String TAG = "PlayThread";
private Activity mActivity;
private AudioTrack mAudioTrack;
private byte[] data;
private String mFileName;
public PlayThread(Activity activity, String fileName) {
mActivity = activity;
mFileName = fileName;
int bufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, AudioFormat.ENCODING_PCM_16BIT);
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
mSampleRateInHz,
mChannelConfig,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize,
AudioTrack.MODE_STREAM);
}
@Override
public void run() {
super.run();
try {
if (null != mAudioTrack)
mAudioTrack.play();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
InputStream inputStream = mActivity.getResources().getAssets().open(mFileName);
// 緩衝區
byte[] buffer = new byte[1024];
// 播放進度
int playIndex = 0;
// 是否緩衝完成
boolean isLoaded = false;
// 緩衝 + 播放
while (null != mAudioTrack && AudioTrack.PLAYSTATE_STOPPED != mAudioTrack.getPlayState()) {
// 字符長度
int len;
if (-1 != (len = inputStream.read(buffer))) {
byteArrayOutputStream.write(buffer, 0, len);
data = byteArrayOutputStream.toByteArray();
Log.i(TAG, "run: 已緩衝 : " + data.length);
} else {
// 緩衝完成
isLoaded = true;
}
if (AudioTrack.PLAYSTATE_PAUSED == mAudioTrack.getPlayState()) {
// TODO 已經暫停
}
if (AudioTrack.PLAYSTATE_PLAYING == mAudioTrack.getPlayState()) {
Log.i(TAG, "run: 開始從 " + playIndex + " 播放");
playIndex += mAudioTrack.write(data, playIndex, data.length - playIndex);
Log.i(TAG, "run: 播放到了 : " + playIndex);
if (isLoaded && playIndex == data.length) {
Log.i(TAG, "run: 播放完了");
mAudioTrack.stop();
}
if (playIndex < 0) {
Log.i(TAG, "run: 播放出錯");
mAudioTrack.stop();
break;
}
}
}
Log.i(TAG, "run: play end");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 設置左右聲道平衡
*
* @param max 最大值
* @param balance 當前值
*/
public void setBalance(int max, int balance) {
float b = (float) balance / (float) max;
Log.i(TAG, "setBalance: b = " + b);
if (null != mAudioTrack)
mAudioTrack.setStereoVolume(1 - b, b);
}
/**
* 設置左右聲道是否可用
*
* @param left 左聲道
* @param right 右聲道
*/
public void setChannel(boolean left, boolean right) {
if (null != mAudioTrack) {
mAudioTrack.setStereoVolume(left ? 1 : 0, right ? 1 : 0);
mAudioTrack.play();
}
}
public void pause() {
if (null != mAudioTrack)
mAudioTrack.pause();
}
public void play() {
if (null != mAudioTrack)
mAudioTrack.play();
}
public void stopp() {
releaseAudioTrack();
}
private void releaseAudioTrack() {
if (null != mAudioTrack) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
}
}
使用
從頭開始播放
mPlayThread = new PlayThread(this, "tts1.pcm");
mPlayThread.start();
暫停
mPlayThread.pause();
暫停後繼續播放
mPlayThread.play();
停止播放
mPlayThread.stopp();
mPlayThread = null;
左右聲道控制
// 禁用左聲道(右聲道同理)
mPlayThread.setChannel(false, true);
向左右聲道單獨輸出不同的音頻數據
也是一個很”呵呵“的做法,但是依然還沒有找到更好的方法。
構造兩個AudioTrack
對象,分別輸出兩個音頻,一個禁用左聲道,一個禁用右聲道,達到預期效果。
mChannelLeftPlayer = new PlayThread(this, "tts1.pcm");
mChannelRightPlayer = new PlayThread(this, "tts2.pcm");
mChannelLeftPlayer.setChannel(true, false);
mChannelRightPlayer.setChannel(false, true);
mChannelLeftPlayer.start();
mChannelRightPlayer.start();