为 Android 平台编译 MPG123

MPG123 简介

MPG123 是一个轻量小巧的 MPG 格式解码和播放的库。在 Android 平台上,我们使用 libmpg123 来解码 MPG 文件,并转换为 PCM 数据。下面是将 MPG123 移植到 Android 平台的一些简单的指南,希望能对你有些许的帮助。

简单地集成

1. 获取工程

我已经修改过 GitHub 上一个开源的 MPG123 库,去除了无用的其他依赖,仅留下了 MPG123 源码和配置文件,所以你可以直接拉取我的仓库并编译。

当前的仓库地址:rosuH/MPG123-Android

git clone [email protected]:rosuH/MPG123-Android.git

2. 直接使用 so

如果你嫌自己编译麻烦,那么最简单的使用方法就是把 MPG123-Android/libmpg123/src/main/libs 中的所有文件复制到你的项目中。

至于放到哪个目录就因项目而异了,默认情况下是存放在src/main/jniLibs/目录。不过你也该看看你的build.gradle中是否指定了jniLibs.scrDir,因为存放的目录就是由它指定的。

接着要把MPG123-Android/libmpg123/src/main/libs/MPG123.java也复制到你的项目中。要记得 MPG123.java 是要保留包结构的。因为编写 JNI 是按照包名生成的函数名。所以必须要保留包结构。

例如,下面是你原本的包结构:

.
└── com
    └── example
        └── usingmpg123
            └── MainActivity.kt

那么复制进去后的包结构应该为:

.
├── com
│   └── example
│       └── usingmpg123
│           └── MainActivity.kt
└── me
    └── rosuh
        └── libmpg123
            └── MPG123.java

然后就可以开始使用了。

3. 简单编译

如果你现在希望更新 MPG123 的源码以期获得最新的更新,那么你将需要自己简单地编译一下新 so 文件。(当前的版本是 1.25.12,已属于比较新的版本啦)。

一般来说只要 MPG123 没有对 API 做太大的变更,那么这一步你可以放宽心。我们很快就可以搞定。

  • 下载 MPG123 最新发布版

    • 下载的是一个 tar.bz2 压缩包,解压出来就是源码了
  • 进入到现在的项目中:MPG123-Android/libmpg123/src/main/jni/mpg123-1.25.12中,把几个文件复制到新的 MPG123 代码中

    • androidme_rosuh_decoder_MPG123.cme_rosuh_decoder_MPG123.h
    • 当然要注意要保留上述文件的路径和原来的一致
  • 快乐地ndk-build

    • 进到jni,执行ndk-build操作

如无意外,你将会在libs文件夹中获得全新的 MPG123 so 文件。

使用的方式同 2 中所述。

稍微正常的集成

如果你对项目的结构有些奇怪的癖好,想必在你项目里出现别人的包名是一件让你无法容忍的事情。所以我们现在要一步步地编译出自定义包名和so名的 MPG123 库。

这一步要求你要对 NDK-Build 开发有一点了解,当然不了解问题也不大。

声明 Native 方法

我们将从 Java 侧开始,先在 Java 侧定义好 MPG123 可被调用的方法。

比如我们可以创建一个MPG123.java,内容如下:

public class MPG123{
    static {
        System.loadLibrary("libmpg123");
        MPG123.init();
    }

    protected static native int init();
    protected static native long openFile(String filename);
    protected static native void delete(long handle);
    protected static native boolean skipFrame(long handle);
    protected static native int seek(long handle, float offsetInSeconds);
    protected static native float getPosition(long handle);
    protected static native int getNumChannels(long handle);
    protected static native int getRate(long handle);
    protected static native float getDuration(long handle);

    protected static native long openStream();
    protected static native void feed(long handle, byte[] buffer, int count);
    protected static native int readFrame(long handle, short[] buffer);
    protected static native int getSeekFrameOffset(long handle, float position);

    protected boolean _streamComplete = false;

    protected long _handle = 0;
    public MPG123() { _handle = openStream(); }
    public MPG123(String filename) { _handle = openFile(filename); _streamComplete = true; }

    public void close() {
        if (_handle != 0)
            MPG123.delete(_handle);
    }

    public int readFrame(short[] buffer) { return MPG123.readFrame(_handle, buffer); }
    public boolean skipFrame() { return MPG123.skipFrame(_handle); }
    public int seek(float offset) { return MPG123.seek(_handle, offset); }
    public float getPosition() { return MPG123.getPosition(_handle); }
    public int getNumChannels() { return MPG123.getNumChannels(_handle); }
    public int getRate() { return MPG123.getRate(_handle); }
    public float getDuration() { return MPG123.getDuration(_handle); }
    public int getSeekFrameOffset(float position) { return MPG123.getSeekFrameOffset(_handle, position); }
    public void feed(byte[] buffer, int count) { MPG123.feed(_handle, buffer, count); }
    public void completeStream() { _streamComplete = true; }
    public boolean isStreamComplete() { return _streamComplete; }
}

一共才 47 行代码,看起来不多。而且使用native修饰的都是 C 层的代码,我们无需实现,这一步只是在声明。

使用 External Tool

接着我们要根据这个 Java 侧的声明,来生成一个 Native 侧的头文件。也就是 Native 侧的声明。

因为我们使用的是 NDK-build,所以方法比较原始。也就是使用javah这个工具。不过如果你使用的是 Android Studio 的话,我们可以快速地利用 IDE 来搞定这一步。

首先打开:「Setting」--> 「Tools」-->「External Tools」

如果你之前已经添加了,那就不需要加。如果你没有添加,那么这个页面将会是空页面。

接着点击左下角的加号,会出现一个对话框。在 Android Studio 3.5.2 中,我们要输入对应的值如下:

参数
Namejavah(随意)
Program$JDKPath$/bin/javah
Arguments-v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
WorkingDirectory$SourcepathEntry$
  • JDKPath 当然就是你的 JDK 目录了

然后就可以保存了。

生成头文件

万事具备后,我们右击方才创建的MPG123.java,在列表中选择「External Tookls」--> 「javah」。不出意外的话,他会在 jni 的目录下生成对应的.h文件。

例如,我的MPG123.java所在包为me.rosuh.libmpg123,最后生成的头文件名为me_rosuh_libmpg123_MPG123.h。然后把这个头文件放到 mpg123源码的目录中。

为了防止你混淆,我们现了解一下当前的目录结构:

.
├── Android.mk
├── Application.mk
└── mpg123-1.25.12
    ├── AUTHORS
    ├── Android.mk
    ├── COPYING
    ├── ChangeLog
    ├── INSTALL
    ├── Makefile.am
    ├── Makefile.in
    ├── NEWS
    ├── NEWS.libmpg123
    ├── NEWS.libout123
    ├── README
    ├── TODO
    ├── aclocal.m4
    ├── android
    ├── build
    ├── configure
    ├── configure.ac
    ├── doc
    ├── equalize.dat
    ├── libmpg123.pc.in
    ├── libout123.pc.in
    ├── m4
    ├── makedll.sh
    ├── man1
    ├── me_rosuh_decoder_MPG123.c
    ├── me_rosuh_decoder_MPG123.h
    ├── mpg123.spec
    ├── mpg123.spec.in
    ├── ports
    ├── scripts
    ├── src
    └── windows-builds.sh

可以看到在jni根目录下,存放有Android.mkApplication.mk。这两个是给所有 Native 模块使用的。不同的 Native 模块目录下还有他们自己的Android.mk文件。

比如你看到MPG123文件夹根目录下,也有一个Android.mk。这是给此模块使用的。里面保存了对不同架构使用的不同配置。我们当然要把生成的头文件放到mpg123-1.25.12目录中,因为它是属于这个模块的。(我在配置文件中写了)

接着...

这个时候我推荐你不要打开这个头文件,因为 Android Studio 认为这个头文件没有加入到项目中,所以不会给它应用 NDK 的路径。导致它的jni.h是找不到的。不过这不影响我们继续下去。

Native 侧的头文件生成好了,接下来就是对头文件里的方法进行实现。你可以自行实现(自己创建一个 c 文件),也可以参照me_rosuh_libmpg123_MPG123.c去做。如果你已经可以自己实现那些方法了,那相信也不行看下去了。

如果你还没办法实现那些方法,那建议直接复制me_rosuh_libmpg123_MPG123.c里的实现,并把函数名前半部分的包名替换为你自己的包名。

现在你有两个属于你自己的文件:

  • me.example.mpg123.h
  • me.example.mpg123.c

我们需要把他们加到项目中,这样在编译时才会让他们参与编译。

就像我们上面说的,mpg123-1.25.12文件夹中的Android.mk是这个模块的配置文件。所以我们要在此进行修改。打开这个文件,定位到LOCAL_SRC_FILES 的倒数第二行:

LOCAL_SRC_FILES := \
    src/libmpg123/parse.c \
    src/libmpg123/frame.c \
    src/libmpg123/format.c \
    src/libmpg123/dct64.c \
    src/libmpg123/equalizer.c \
    src/libmpg123/id3.c \
    src/libmpg123/optimize.c \
    src/libmpg123/readers.c \
    src/libmpg123/tabinit.c \
    src/libmpg123/libmpg123.c \
    src/libmpg123/index.c \
    src/compat/compat_str.c \
    src/compat/compat.c \
    me_rosuh_libmpg123_MPG123.c \
    $(DECODER_SRC)

这里的关键字是me_rosuh_libmpg123_MPG123.c,相信你已经知道要把这替换为你自己的文件名了。替换的时候请小心,不要增删多余的空格。

到此我们的自定义就差不多完成了。不过你说你还想修改so的名字...

那么你可以修改文件开头的 LOCAL_MODULE配置项。Android 建议 so文件都应该以lib开头。所以我建议使用libmpg123即可。(如果你没有以lib开头,NDK 会自动给你加上lib前缀)

开始编译

现在进到jni的根目录,执行ndk-build即可编译,享受劳动果实🍒。


添加新评论

评论列表