【问题标题】:Android Studio - libmp3lame NDKAndroid Studio - libmp3lame NDK
【发布时间】:2026-02-10 21:25:02
【问题描述】:

我目前正在尝试在我的 Android 应用程序中实现 libmp3lame,以便将 MP3 数据解码为 PCM。

要使用 libmp3lame,我需要使用 JNI/NDK 实现,要将 MP3 解码为 PCM,我需要使用 libmp3lame 中的 hip_decode() 函数。

这个函数在“lame.h”文件中是这样实例化的:

int CDECL hip_decode( hip_t           gfp
                , unsigned char * mp3buf
                , size_t          len
                , short           pcm_l[]
                , short           pcm_r[]
                );

我的应用程序是这样工作的: 我从 WebSocket 收到一个单声道 MP3 样本。我需要将此样本从 MP3 解码为 PCM,然后将其写入我的 AudioTrack 以播放它。它是实时流,所以我需要尽可能低的延迟。我正在使用 JLayer,我可以完全理解说话者在说什么,但我有一个“散列”问题/机器人声音。似乎我在每个样本的开头都有一些 0 值,这造成了一些奇怪的效果。现在我需要对 libmp3lame 做同样的事情。所以我想做的是以下。每次调用我的 WebSocket 甚至接收时,我都需要接收接收到的字节数组(包含音频数据)并将其解码为 PCM。我需要在 PCM 中有 byte[] 或 short[],然后在音轨中播放。问题是我不确定如何使用 hip_decode 来做到这一点。我真的不熟悉 C 编程,所以可能有一种非常简单的方法可以做到这一点,但我就是做不到。现在在我的 wrapper.c 我有这个:

JNIEXPORT void JNICALL Java_com_example_jneb_myapplication_MainActivity_decoderInit(JNIEnv *env, jobject jobj,
    ) {
    hip = hip_decode_init();

}

我不确定使用 hip_decode 函数所需的 pcm_l 和 pcm_r 是什么。以下是有关该功能的更多信息:

/*********************************************************************
* input 1 mp3 frame, output (maybe) pcm data.
*
*  nout = hip_decode(hip, mp3buf,len,pcm_l,pcm_r);
*
* input:
*    len          :  number of bytes of mp3 data in mp3buf
*    mp3buf[len]  :  mp3 data to be decoded
*
* output:
*    nout:  -1    : decoding error
*           0    : need more data before we can complete the decode
*           >0    : returned 'nout' samples worth of data in pcm_l,pcm_r
*    pcm_l[nout]  : left channel data
*    pcm_r[nout]  : right channel data
*
*********************************************************************/

编辑: 感谢 bukkojot 的回答,我已经能够理解 pcm_l 和 pcm_r 的用途。

这是我的代码的更新:

JNIEXPORT jshortArray JNICALL Java_com_example_jneb_myapplication_AudioTrackClass_decoderInit(JNIEnv *env, jobject jobj,
                                                                                     jbyteArray data, jint size) {
jsize mp3Len =  (*env)->GetArrayLength(env, data);
// Print the data.length = 96
LOGI("JNI integer: %d", mp3Len);
// mp3 contains all 96 values
jbyte *mp3 = (*env)->GetByteArrayElements(env, data, 0);

// Trying to decode mp3 into PCM
int x = hip_decode(hip, (unsigned char*) mp3, (size_t) mp3Len, pcm_l, pcm_r);

jshortArray pcmBuffer;
pcmBuffer = (*env)->NewShortArray(env, mp3Len);
(*env)->SetShortArrayRegion(env, pcmBuffer, 0, mp3Len, pcm_l);
// Releasing byte array
(*env)->ReleaseByteArrayElements(env, data, mp3, 0);

// Returning
return pcmBuffer;

}

目前 pcmBuffer 只返回 0 值,hip_decode 也只返回 0 值。文档说如果 hip_decode 返回 0 那么 hip_decode 函数“需要更多数据才能完成解码”。我已经为函数提供了我拥有的所有数据。 hip_decode 函数我做错了什么?

【问题讨论】:

    标签: android android-ndk java-native-interface mp3


    【解决方案1】:

    '不确定使用 hip_decode 所需的 pcm_l 和 pcm_r 是什么 功能。

    这是指向缓冲区的指针,解码后的 PCM 将被写入其中。

    为未压缩的声音分配足够的内存,例如:

    signed short *pcm_l=malloc(1000000); // make sure it's enough
    signed short *pcm_r=malloc(1000000);
    

    然后将它们传递给解码函数。函数将返回有用样本的数量。将此数据传递给 Java 部分并写入 AudioTrack。

    【讨论】:

    • 好的,我试试这个。我的缓冲区需要那么大吗?这个函数会为每一小块mp3数据调用,这不会导致内存问题吗?
    • 如果你只在 init 中分配内存,你将只使用 2mb 的内存。这在我们这个时代非常小。如果您想要更精确,请计算可以解压缩的音频的最大大小,例如,5 秒 * 48000 采样率 * 每个采样 2 字节 = 每个通道 480000 字节。
    • 感谢您的回答,让我明白我应该做什么。我编辑了这个问题,因为我仍然面临一些问题。
    • 首先,如果要返回指针,必须return pcm_l;,而不是return *pcm_l;。其次,您必须在其他地方定义分配,否则您很快就会“内存不足”,只能在 init 函数中执行。最后,阅读有关将数据复制到 Java 世界的内容。
    • 非常感谢您的耐心等待。 “将数据复制到 Java 世界”是什么意思?我想读一下,但我真的不知道你指的是什么。