/********************************************************************************************
* author:conowen@大钟
* E-mail:[email protected]
*site:http://menwoo.com/
*深圳市大望谷科技有限公司
* 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
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="AudioTrack" /> <Button android:id = "@+id/buttonPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="PLAY" /> <Button android:id = "@+id/buttonPause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="PAUSE" /> </LinearLayout>