使用AudioTrack播放MP3 左右声道控制 之移植Libmad到android平台
2016-12-29 16:00
986 查看
前一段时间公司有需求要控制左右喇叭播放音乐测试,所有自己就做了demo 顺便和大家分享一下。
众所周知,Android的audiotrack只能播放原始的音频,也就是PCM数据,若是播放mp3编码格式的音频的话,就是 出现沙沙的噪音。所以,可以使用第三方库Libmad来对mp3文件解码称为PCM数据,再送给audiotrack播放即可。
下载的project可以直接用NDK编译通过的,但是要使用还是要写jni层供Java层调用
但是Android.mk要改成如下形式。
实现代码比较多 这里就不贴了 可以查看项目源码。
源码链接地址:http://download.csdn.net/detail/feipeng_/9724644
众所周知,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
相关文章推荐
- android 使用libmad 生成MP3左右声道的PCM文件
- 使用AudioTrack播放PCM音频数据(android)
- 使用AudioTrack播放PCM音频数据(android)
- Android多媒体开发(5)————利用Android AudioTrack播放mp3文件
- Android AudioTrack播放mp3文件
- 使用AudioTrack播放PCM音频数据(android)
- Android音频: 怎样使用AudioTrack播放一个WAV格式文件?
- Android多媒体开发(5)————利用Android AudioTrack播放mp3文件
- Android开发:使用AudioTrack播放PCM音频数据【附源码】
- Android多媒体开发(5)————利用Android AudioTrack播放mp3文件
- 简单的使用libmad的low-level api和portaudio写的播放mp3的小程序
- Android多媒体开发(5)————利用Android AudioTrack播放mp3文件
- android使用AudioTrack播放多个音频文件
- 【Android 多媒体应用】使用MediaCodec解码使用AudioTrack播放音频数据
- Android使用AudioRecord录制pcm音频原始数据以及使用AudioTrack播放
- 使用AudioTrack播放PCM音频数据
- Android多媒体开发(4)————移植Libmad到android平台
- 【Android】AudioTrack播放caf音频文件
- win mobile 5播放mp3音乐的方法(2)--libmad库的使用篇
- Android 通过AudioTrack播放CAF音频