/********************************************************************************************
* author:conowen@大钟
* E-mail:[email protected]
* http://blog.csdn.net/conowen
* 注:本文为原创,仅作为学习交流使用,转载请标明作者及出处。
********************************************************************************************/
1、Android AudioTrack简介
在Android中播放声音可以用MediaPlayer和AudioTrack两种方案的,但是两种方案是有很大区别的,MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。而AudioTrack只能播放PCM数据流。
事实上,两种本质上是没啥区别的,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,最后由AudioFlinger进行混音,传递音频给硬件播放出来。利用AudioTrack播放只是跳过Mediaplayer的解码部分而已。Mediaplayer的解码核心部分是基于OpenCORE 来实现的,支持通用的音视频和图像格式,codec使用的是OpenMAX接口来进行扩展。因此使用audiotrack播放mp3文件的话,要自己加入一个音频解码器,如libmad。否则只能播放PCM数据,如大多数WAV格式的音频文件。
参考上一篇博文。
http://blog.csdn.net/conowen/article/details/7727145
2、使用Mediaplayer的不足
MediaPlayer提供了5个setDataSource方法,如其中一个,虽然可以设置文件流起始地址与文件流长度。
public void setDataSource(FileDescriptor fd, long offset, long length)
Since: API Level 1
Sets the data source (FileDescriptor) to use. The FileDescriptor must be seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility to close the file descriptor. It is safe to do so as soon as this call returns. Parameters
Throws
|
但是对于实时地播放加密过的音频文件却是束手无策。虽然对于一些加密过的音频文件,可以采用Audiotrack与Libmad结合的方式解决。
3、简单Demo程序
下面提供一个Audiotrack播放mp3的demo,mp3没有经过加密的,解码部分是由libmad完成。(若是要播放加密音频文件,可以操作的libmad解码文件流即可。)
直接贴代码,代码的大意已经在注释说明了。
@LibmadActivity.java
/*
* author:conowen
* date:2012.7.29
*/
package com.conowen.libmad;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class LibmadActivity extends Activity {
private Thread mThread;
private short[] audioBuffer;
private AudioTrack mAudioTrack;
private Button btnPlay, btnPauseButton;
private int samplerate;
private int mAudioMinBufSize;
private int ret;
private NativeMP3Decoder MP3Decoder;
private boolean mThreadFlag;
private String filePath = "/sdcard/test.mp3";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnPlay = (Button) findViewById(R.id.buttonPlay);
btnPauseButton = (Button) findViewById(R.id.buttonPause);
MP3Decoder = new NativeMP3Decoder();
ret = MP3Decoder.initAudioPlayer(filePath, 0);
if (ret == -1) {
Log.i("conowen", "Couldn't open file '" + filePath + "'");
} else {
mThreadFlag = true;
initAudioPlayer();
audioBuffer = new short[1024 * 1024];
mThread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (mThreadFlag) {
if (mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
// ****从libmad处获取data******/
MP3Decoder.getAudioBuf(audioBuffer,
mAudioMinBufSize);
mAudioTrack.write(audioBuffer, 0, mAudioMinBufSize);
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
});
mThread.start();
}
btnPlay.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (ret == -1) {
Log.i("conowen", "Couldn't open file '" + filePath + "'");
Toast.makeText(getApplicationContext(),
"Couldn't open file '" + filePath + "'",
Toast.LENGTH_SHORT).show();
} else {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
//mThreadFlag = true;// 音频线程开始
mAudioTrack.play();
// mThread.start();
} else if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
//mThreadFlag = true;// 音频线程开始
mAudioTrack.play();
} else {
Toast.makeText(getApplicationContext(),
"Already in play", Toast.LENGTH_SHORT).show();
}
}
}
});
btnPauseButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (ret == -1) {
Log.i("conowen", "Couldn't open file '" + filePath + "'");
Toast.makeText(getApplicationContext(),
"Couldn't open file '" + filePath + "'",
Toast.LENGTH_SHORT).show();
} else {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
mAudioTrack.pause();
} else {
Toast.makeText(getApplicationContext(), "Already stop",
Toast.LENGTH_SHORT).show();
}
}
}
});
}
private void initAudioPlayer() {
// TODO Auto-generated method stub
samplerate = MP3Decoder.getAudioSamplerate();
System.out.println("samplerate = " + samplerate);
samplerate = samplerate / 2;
// 声音文件一秒钟buffer的大小
mAudioMinBufSize = AudioTrack.getMinBufferSize(samplerate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, // 指定在流的类型
// STREAM_ALARM:警告声
// STREAM_MUSCI:音乐声,例如music等
// STREAM_RING:铃声
// STREAM_SYSTEM:系统声音
// STREAM_VOCIE_CALL:电话声音
samplerate,// 设置音频数据的采样率
AudioFormat.CHANNEL_CONFIGURATION_STEREO,// 设置输出声道为双声道立体声
AudioFormat.ENCODING_PCM_16BIT,// 设置音频数据块是8位还是16位
mAudioMinBufSize, AudioTrack.MODE_STREAM);// 设置模式类型,在这里设置为流类型
// AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。
// STREAM方式表示由用户通过write方式把数据一次一次得写到audiotrack中。
// 这种方式的缺点就是JAVA层和Native层不断地交换数据,效率损失较大。
// 而STATIC方式表示是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,
// 后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。
// 这种方法对于铃声等体积较小的文件比较合适。
}
static {
System.loadLibrary("mad");
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mAudioTrack.stop();
mAudioTrack.release();// 关闭并释放资源
mThreadFlag = false;// 音频线程暂停
MP3Decoder.closeAduioFile();
}
}
@NativeMP3Decoder.java
package com.conowen.libmad;
public class NativeMP3Decoder {
private int ret;
public NativeMP3Decoder() {
}
public native int initAudioPlayer(String file,int StartAddr);
public native int getAudioBuf(short[] audioBuffer, int numSamples);
public native void closeAduioFile();
public native int getAudioSamplerate();
}
@main.xml