您的位置:首页 > 移动开发 > Android开发

使用AudioTrack播放MP3 左右声道控制 之移植Libmad到android平台

2016-12-29 16:00 986 查看
前一段时间公司有需求要控制左右喇叭播放音乐测试,所有自己就做了demo 顺便和大家分享一下。

众所周知,Android的audiotrack只能播放原始的音频,也就是PCM数据,若是播放mp3编码格式的音频的话,就是 出现沙沙的噪音。所以,可以使用第三方库Libmad来对mp3文件解码称为PCM数据,再送给audiotrack播放即可。

1、Libmad简介

Libmad是一个开源的高精度 MPEG 音频解码库,支持 MPEG-1(Layer I, Layer II 和 LayerIII(也就是 MP3)。LIBMAD 提供 24-bit 的 PCM 输出,完全是定点计算,非常适合没有浮点支持的平台上使用。使用 libmad 提供的一系列 API,就可以非常简单地实现 MP3 数据解码工作。在 libmad 的源代码文件目录下的 mad.h 文件中,可以看到绝大部分该库的数据结构和 API 等。


2、实现过程

2.1、下载Android平台下的Libmad工程

作者已经在项目里下载好了,直接引用就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


2.2、C代码编写API

需要注意一点的是,得到音频的Samplerate(采样率)要先进行一次readSamples操作才能发采样率读出。

2.2.1 头文件 NativeMP3Decoder.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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


实现代码比较多 这里就不贴了 可以查看项目源码。

2.3、文件读写操作

2.3.1 头文件 FileSystem.h

####

#include <stdlib.h>

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


2.3.2 实现 FileSystem.c

#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#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);
}


2.4、通过ndk-build 编译即可。关于jni调用这里不做介绍。

3、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格式的音频文件。


4、简单Demo程序

下面提供一个Audiotrack播放mp3的demo,mp3没有经过加密的,解码部分是由libmad完成。(若是要播放加密音频文件,可以操作的libmad解码文件流即可。)






4.1 NativeMP3Decoder.java

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();

}


4.2 TrumpetActivity.java

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;
}
}
}


4.3 main.xml

<?xml version="1.0" encoding="utf-8"?>
<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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息