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

前一段时间公司有需求要控制左右喇叭播放音乐测试,所有自己就做了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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BFP_BSP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值