前一段时间公司有需求要控制左右喇叭播放音乐测试,所有自己就做了demo 顺便和大家分享一下。
众所周知,Android的audiotrack只能播放原始的音频,也就是PCM数据,若是播放mp3编码格式的音频的话,就是 出现沙沙的噪音。所以,可以使用第三方库Libmad来对mp3文件解码称为PCM数据,再送给audiotrack播放即可。
Libmad是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3)。LIBMAD 提供 24-bit 的 PCM 输出,完全是定点计算,非常适合没有浮点支持的平台上使用。使用 libmad 提供的一系列 API,就可以非常简单地实现 MP3 数据解码工作。在 libmad 的源代码文件目录下的 mad.h 文件中,可以看到绝大部分该库的数据结构和 API 等。
作者已经在项目里下载好了,直接引用就ok。
下载的project可以直接用NDK编译通过的,但是要使用还是要写jni层供Java层调用
但是Android.mk要改成如下形式。
#ifeq ($(strip $(BUILD_WITH_GST)),true)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
version.c \
fixed.c \
bit.c \
timer.c \
stream.c \
frame.c \
synth.c \
decoder.c \
layer12.c \
layer3.c \
huffman.c \
FileSystem.c \
NativeMP3Decoder.c
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
LOCAL_MODULE:= libmad
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/android
LOCAL_CFLAGS := -DHAVE_CONFIG_H -DFPM_ARM -ffast-math -O3
include $(BUILD_SHARED_LIBRARY)
#endif
需要注意一点的是,得到音频的Samplerate(采样率)要先进行一次readSamples操作才能发采样率读出。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_mediatek_factorymode_NativeMP3Decoder */
#ifndef _Included_com_mediatek_factorymode_NativeMP3Decoder
#define _Included_com_mediatek_factorymode_NativeMP3Decoder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_mediatek_factorymode_NativeMP3Decoder
* Method: initAudioPlayer
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_com_mediatek_factorymode_NativeMP3Decoder_initAudioPlayer
(JNIEnv *, jobject, jstring, jint);
/*
* Class: com_mediatek_factorymode_NativeMP3Decoder
* Method: getAudioBuf
* Signature: ([SI)I
*/
JNIEXPORT jint JNICALL Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf
(JNIEnv *, jobject, jshortArray, jint);
/*
* Class: com_mediatek_factorymode_NativeMP3Decoder
* Method: closeAduioFile
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_mediatek_factorymode_NativeMP3Decoder_closeAduioFile
(JNIEnv *, jobject);
/*
* Class: com_mediatek_factorymode_NativeMP3Decoder
* Method: getAudioSamplerate
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioSamplerate
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
实现代码比较多 这里就不贴了 可以查看项目源码。
####
#include
typedef signed long T_S32; /* signed 32 bit integer */
#define T_pFILE T_S32
#define T_hFILE T_S32
#define _CREATE 0//"wb+"//0
#define _RDONLY 1//"rb" // 1
#define _WRONLY 2//"wb"// 2
#define _RDWR 3//"rb+"// 3
#define _FMODE_READ _RDONLY
#define _FMODE_WRITE _WRONLY
#define _FMODE_CREATE _CREATE
#define _FMODE_OVERLAY _RDWR
#define _FSEEK_CURRENT 1
#define _FSEEK_END 2
#define _FSEEK_SET 0
#define _FOPEN_FAIL -1
#define SEEK_CURRENT 1
#define SEEK_CUR 1
#define SEEK_END 2
#define SEEK_SET 0
#define FS_SEEK_SET 0
#include
#include
#include
#include
#include
#include
#include"FileSystem.h"
int file_open(const char *filename, int flags)
{
int access;
T_pFILE fd = 0;
if (flags == _CREATE) {
access = O_CREAT | O_TRUNC | O_RDWR;
} else if (flags == _WRONLY) {
access = O_CREAT | O_TRUNC | O_WRONLY;
} else if (flags == _RDONLY){
access = O_RDONLY;
} else if (flags == _RDWR){
access = O_RDWR;
} else{
return -1;
}
#ifdef O_BINARY
access |= O_BINARY;
#endif
fd = open(filename, access, 0666);
if (fd == -1)
return -1;
return fd;
}
int file_read(T_pFILE fd, unsigned char *buf, int size)
{
return read(fd, buf, size);
}
int file_write(T_pFILE fd, unsigned char *buf, int size)
{
return write(fd, buf, size);
}
int64_t file_seek(T_pFILE fd, int64_t pos, int whence)
{
if (whence == 0x10000) {
struct stat st;
int ret = fstat(fd, &st);
return ret < 0 ? -1 : st.st_size;
}
return lseek(fd, pos, whence);
}
int file_close(T_pFILE fd)
{
return close(fd);
}
在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格式的音频文件。
下面提供一个Audiotrack播放mp3的demo,mp3没有经过加密的,解码部分是由libmad完成。(若是要播放加密音频文件,可以操作的libmad解码文件流即可。)
package com.mediatek.factorymode;
public class NativeMP3Decoder {
static {
System.loadLibrary("mad");
}
public native int initAudioPlayer(String file,int StartAddr);
public native int getAudioBuf(short[] audioBuffer, int numSamples);
public native void closeAduioFile();
public native int getAudioSamplerate();
}
public class TrumpetActivity extends Activity implements OnClickListener {
private String TAG = "TrumpetActivity";
private String assertFolderName = "Android";
private String assertMusicName = "rich.mp3";
@SuppressLint("SdCardPath")
private String targetFolderPath = Environment.getExternalStorageDirectory()
.getPath() + File.separator + assertFolderName;
private String decodeFilePath = Environment.getExternalStorageDirectory()
.getPath()
+ File.separator
+ assertFolderName
+ File.separator
+ assertMusicName;
private Thread mThread;
private boolean mThreadFlag;
private short[] audioBuffer;
private AudioTrack mAudioTrack;
private int samplerate;
private int mAudioMinBufSize;
private int initRet;
private NativeMP3Decoder mMP3Decoder;
private Button PlayMusic, PauseMusic;
private Button leftSoundChannel, lightSoundChannel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.trumpet_layout);
FileHelper.copyFolderFromAssets(TrumpetActivity.this, assertFolderName,targetFolderPath);
PlayMusic = (Button) findViewById(R.id.playMusic);
PauseMusic = (Button) findViewById(R.id.PauseMusic);
leftSoundChannel = (Button) findViewById(R.id.leftsoundchannel);
lightSoundChannel = (Button) findViewById(R.id.rightsoundchannel);
PlayMusic.setOnClickListener(this);
PauseMusic.setOnClickListener(this);
leftSoundChannel.setOnClickListener(this);
lightSoundChannel.setOnClickListener(this);
mMP3Decoder = new NativeMP3Decoder();
initRet = mMP3Decoder.initAudioPlayer(decodeFilePath, 0);
if (initRet == -1) {
Log.i(TAG, "Couldn't open file '" + decodeFilePath + "'");
} else {
Log.i(TAG, "Couldn't open file else'" + decodeFilePath + "'");
mThreadFlag = true;
initAudioPlayer();
audioBuffer = new short[1024 * 20];
mThread = new Thread(new Runnable() {
@Override
public void run() {
while (mThreadFlag) {
if (mAudioTrack != null) {
if (mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
if (mAudioTrack != null) {
if (mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED) {
mMP3Decoder.getAudioBuf(audioBuffer,
mAudioMinBufSize);
mAudioTrack.write(audioBuffer, 0,
mAudioMinBufSize);
}
}
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
});
mThread.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
mAudioTrack.play();
}
}
public void setChannel(boolean left, boolean right) {
if (null != mAudioTrack) {
mAudioTrack.setStereoVolume(left ? 1 : 0, right ? 1 : 0);
mAudioTrack.play();
}
}
@SuppressWarnings("deprecation")
private void initAudioPlayer() {
samplerate = mMP3Decoder.getAudioSamplerate();
System.out.println("samplerate = " + samplerate);
Log.d("bfp", "samplerate:" + samplerate);
samplerate = samplerate / 2;
mAudioMinBufSize = AudioTrack.getMinBufferSize(samplerate,
AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
samplerate, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
AudioFormat.ENCODING_PCM_16BIT, mAudioMinBufSize,
AudioTrack.MODE_STREAM);
}
@Override
protected void onDestroy() {
mThreadFlag = false;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mAudioTrack != null) {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
mAudioTrack.stop();
mAudioTrack.release();
}
}
mAudioTrack = null;
audioBuffer = null;
mMP3Decoder.closeAduioFile();
super.onDestroy();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.playMusic:
if (initRet == -1) {
Toast.makeText(getApplicationContext(),
"Couldn't open file '" + decodeFilePath + "'",
Toast.LENGTH_SHORT).show();
} else {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
mAudioTrack.play();
} else if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
mAudioTrack.play();
} else {
Toast.makeText(getApplicationContext(),
"Already in play", Toast.LENGTH_SHORT).show();
}
}
break;
case R.id.PauseMusic:
if (initRet == -1) {
Log.i("conowen", "Couldn't open file '" + decodeFilePath + "'");
Toast.makeText(getApplicationContext(),
"Couldn't open file '" + decodeFilePath + "'",
Toast.LENGTH_SHORT).show();
} else {
if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
mAudioTrack.pause();
} else {
Toast.makeText(getApplicationContext(), "Already stop",
Toast.LENGTH_SHORT).show();
}
}
break;
case R.id.leftsoundchannel:
setChannel(true, false);
break;
case R.id.rightsoundchannel:
setChannel(false, true);
break;
default:
break;
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/playMusic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/PlayMusic" />
<Button
android:id="@+id/PauseMusic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/PauseMusic" />
LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/rightsoundchannel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lightSoundChannel" />
<Button
android:id="@+id/leftsoundchannel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/leftSoundChannel" />
LinearLayout>
LinearLayout>
源码链接地址:http://download.csdn.net/detail/feipeng_/9724644