移植AAC解码库FAAD2到android

时间:2023-01-11 13:00:50

aac是针对音频 进行压缩的一种算法 该编码优点很多。音质好 支持的采样率 声道都很多。在android上使用非常常见


之前使用ffmepeg 1.1的decode_audio4这个函数 解码有误 双声道16位 居然解码成单声道32位 (nb_sample_fm=8) 

没找到好的解决方法 (据说换老版本的ffmpeg0.5可以解决 )

之后有尝试提炼opcore 中的aac decode 发现太复杂

再之后尝试使用OPSLes 但是 居然在源代码里面 有个结构体定义的bug 导致无法编译 不得不移植faad2


整个过程:1:下载源代码

2:使用NDK编译

3:编写JNI测试函数,使用aac文件测试效果。




1:http://www.audiocoding.com/downloads.html 下载2.7

FAAD2 Source Version 2.7 ZIP Package
到一个android工程下新建一个jni目录  解压到此处

目录结构

---jni

    |---faad2

             |-----aacDe

             |----libfaad

            .

            .

            .

    我们之关心libfaad 和include这两个文件夹


2:编写mk脚本

在jni目录下编写Android.mk文件,内容如下

LOCAL_PATH := $(call my-dir)
FAAD2_TOP := $(LOCAL_PATH)/faad2
include $(CLEAR_VARS)
include $(FAAD2_TOP)/libfaad/Android.mk

LOCAL_C_INCLUDES := \
$(LOCAL_PATH)\
$(FAAD2_TOP)/android\
$(FAAD2_TOP)/include\
$(LOCAL_PATH)/codebook
在/jni/faad2/libfaad/编写Android.mk 内容如下

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:=bits.c \
cfft.c \
decoder.c \
drc.c \
drm_dec.c \
error.c \
filtbank.c \
ic_predict.c \
is.c \
lt_predict.c \
mdct.c \
mp4.c \
ms.c \
output.c \
pns.c \
ps_dec.c \
ps_syntax.c \
pulse.c \
specrec.c \
syntax.c \
tns.c \
hcr.c \
huffman.c \
rvlc.c \
ssr.c \
ssr_fb.c \
ssr_ipqf.c \
common.c \
sbr_dct.c \
sbr_e_nf.c \
sbr_fbt.c \
sbr_hfadj.c \
sbr_hfgen.c \
sbr_huff.c \
sbr_qmf.c \
sbr_syntax.c \
sbr_tf_grid.c \
sbr_dec.c


LOCAL_MODULE:=faad
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)\
$(FAAD2_TOP)/android\
$(FAAD2_TOP)/include\
$(LOCAL_PATH)/codebook

LOCAL_CFLAGS:=\
-DHAVE_CONFIG_H

include $(BUILD_SHARED_LIBRARY)

最后在/jnifaad/下面编写config.h文件

/* config.h.  Generated from config.h.in by configure.  */
/* config.h.in. Generated from configure.in by autoheader. */

/* Define if you want to use libfaad together with Digital Radio Mondiale
(DRM) */
/* #undef DRM */

/* Define if you want support for Digital Radio Mondiale (DRM) parametric
stereo */
/* #undef DRM_PS */

/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1

/* Define to 1 if you have the <errno.h> header file. */
#define HAVE_ERRNO_H 1

/* Define if needed */
/* #undef HAVE_FLOAT32_T */

/* Define to 1 if you have the <float.h> header file. */
#define HAVE_FLOAT_H 1

/* Define to 1 if you have the `getpwuid' function. */
#define HAVE_GETPWUID 1

/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1

/* Define if you have the IOKit API */
/* #undef HAVE_IOKIT_IOKITLIB_H */

/* Define to 1 if you have the <limits.h> header file. */
#define HAVE_LIMITS_H 1

/* Define if you have C99's lrintf function. */
#define HAVE_LRINTF 1

/* Define to 1 if you have the <mathf.h> header file. */
/* #undef HAVE_MATHF_H */

/* Define to 1 if you have the `memcpy' function. */
#define HAVE_MEMCPY 1

/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1

/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the `strchr' function. */
#define HAVE_STRCHR 1

/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1

/* Define to 1 if you have the `strsep' function. */
#define HAVE_STRSEP 1

/* Define to 1 if you have the <sysfs/libsysfs.h> header file. */
/* #undef HAVE_SYSFS_LIBSYSFS_H */

/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H 1

/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1

/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1

/* Define to 1 if your C compiler doesn't accept -c and -o together. */
/* #undef NO_MINUS_C_MINUS_O */

/* Name of package */
#define PACKAGE "faad2"

/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT ""

/* Define to the full name of this package. */
#define PACKAGE_NAME ""

/* Define to the full name and version of this package. */
#define PACKAGE_STRING ""

/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME ""

/* Define to the version of this package. */
#define PACKAGE_VERSION ""

/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1

/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
#define TIME_WITH_SYS_TIME 1

/* Version number of package */
#define VERSION "2.7.0"

/* Define to 1 if your processor stores words with the most significant byte
first (like Motorola and SPARC, unlike Intel and VAX). */
/* #undef WORDS_BIGENDIAN */

/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
/* #undef inline */
#endif

/* Define to `long int' if <sys/types.h> does not define. */
/* #undef off_t */

使用NDK 命令 ndk-build(我的版本为ndk-linux-x64-r8)

如果没有问题 会在/libs/下面生成一个libfaad2.a文件  没错 就是一个静态库 为什么是静态库 看下面

如果运行NDK编译命令没有动静 则在/jni/目录下编译个Application.mk文件

APP_STL:= gnustl_static
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14
APP_MODULES := libfaad

上面语句的意思 

第一行 用GNU库
第二行 针对arme-v7a的CPU

第三行  针对设备的版本 4.0.3

最后一行 静态库的名称

能用的库就出来了 然后怎么调用它呢 ? 

 JNI技术


修改jni/Android.mk如下 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := libfaad
LOCAL_SRC_FILES := lib/libfaad.a
include $(PREBUILT_STATIC_LIBRARY)

LOCAL_C_INCLUDES := $(LOCAL_PATH)/ \
$(call include-path-for, wilhelm)
LOCAL_MODULE := pcmNativePlayer

LOCAL_LDLIBS := -L$(NDK_PLATFORMS_ROOT)/$(TARGET_PLATFORM)/arch-arm/usr/lib -L$(LOCAL_PATH)
LOCAL_LDLIBS += -lOpenSLES -llog -lz -ldl
#-ljnigraphics -cpu -lgcc
LOCAL_SRC_FILES := decode_aac_adts.cpp
#native-audio-jni.cppLOCAL_STATIC_LIBRARIES:= libfaadLOCAL_SHARED_LIBRARIES := \libutils \libOpenSLESinclude $(BUILD_SHARED_LIBRARY)

然后编写一个/jni/decode_aac_adts.cpp

/**
* faaddec.c
* use faad library to decode AAC, only can decode frame with ADTS head
*/
#include <stdio.h>
#include <memory.h>

extern "C"{
#include "faad.h"
#include "neaacdec.h"
}
#include <jni.h>

//#include "native-audio-jni.h"
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#define LOG_TAG "faad2_decode_aac_adts"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define FRAME_MAX_LEN 1024*5
#define BUFFER_MAX_LEN 1024*1024
void show_usage()
{
LOGI("usage\nfaaddec src_file dst_file");
}

/**
* fetch one ADTS frame
*/
int get_one_ADTS_frame(unsigned char* buffer, size_t buf_size,
unsigned char* data, size_t* data_size)
{
size_t size = 0;

if (!buffer || !data || !data_size)
{
return -1;
}

while (1)
{
if (buf_size < 7)
{
return -1;
}

if ((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0))
{
size |= ((buffer[3] & 0x03) << 11); //high 2 bit
size |= buffer[4] << 3; //middle 8 bit
size |= ((buffer[5] & 0xe0) >> 5); //low 3bit
break;
}
--buf_size;
++buffer;
}

if (buf_size < size)
{
return -1;
}

memcpy(data, buffer, size);
*data_size = size;

return 0;
}

int main2(int argc, char* argv[])
{
static unsigned char frame[FRAME_MAX_LEN];
static unsigned char buffer[BUFFER_MAX_LEN] =
{ 0 };

char src_file[128] =
{ 0 };
char dst_file[128] =
{ 0 };
FILE* ifile = NULL;
FILE* ofile = NULL;

unsigned long samplerate;
unsigned char channels;
NeAACDecHandle decoder = 0;

size_t data_size = 0;
size_t size = 0;

NeAACDecFrameInfo frame_info;
unsigned char* input_data = buffer;
unsigned char* pcm_data = NULL;

//analyse parameter
if (argc < 3)
{
show_usage();
return -1;
}
sscanf(argv[1], "%s", src_file);
sscanf(argv[2], "%s", dst_file);
LOGI("source file is null%s",argv[1]);
LOGI("dst file is null%s",argv[2]);
ifile = fopen(src_file, "rb");
ofile = fopen(dst_file, "wb");
if (!ifile || !ofile)
{
LOGI("source or destination file is null");
return -1;
}

data_size = fread(buffer, 1, BUFFER_MAX_LEN, ifile);

//open decoder
decoder = NeAACDecOpen();
if (get_one_ADTS_frame(buffer, data_size, frame, &size) < 0)
{
return -1;
}

//initialize decoder
NeAACDecInit(decoder, frame, size, &samplerate, &channels);
LOGI("samplerate %d, channels %d\n", samplerate, channels);

while (get_one_ADTS_frame(input_data, data_size, frame, &size) == 0)
{
// LOGI("frame size %d\n", size);

//decode ADTS frame
pcm_data = (unsigned char*) NeAACDecDecode(decoder, &frame_info, frame,
size);

if (frame_info.error > 0)
{
LOGI("%s\n", NeAACDecGetErrorMessage(frame_info.error));

}
else if (pcm_data && frame_info.samples > 0)
{
LOGI(
"frame info: bytesconsumed %d, channels %d, header_type %d\
object_type %d, samples %d, samplerate %d\n",
frame_info.bytesconsumed, frame_info.channels,
frame_info.header_type, frame_info.object_type,
frame_info.samples, frame_info.samplerate);
//put openSL queue to render
//updateAudioData(pcm_data,frame_info.samples * frame_info.channels,0);
fwrite(pcm_data, 1, frame_info.samples * frame_info.channels,
ofile); //2个通道
fflush(ofile);
}
data_size -= size;
input_data += size;
}

NeAACDecClose(decoder);

fclose(ifile);
fclose(ofile);
return 0;
}
extern "C"
{
jint Java_org_gl_jni_JNI_fileBackPlay(JNIEnv* env, jobject thiz,
jstring filepath, jint fileRealLength)
{
//if (stats == stats_ING)
//{
//LOGE( "is playing");
//return stats;
//}
char * fileName = (char*) (env)->GetStringUTFChars(filepath, NULL);
LOGI( "fileName:%s", fileName);
char* files[3] ;
files[0] = "0";
files[1] = fileName;
files[2] = "/sdcard/pcm_faac_out";
main2(4,files);
env->ReleaseStringUTFChars(filepath, fileName);
return 0;
}
}
将位于/libs/faad2.a移动到/jni/lib/下面
再一次运行ndk-build编译出一个动态库 faad2.so

编译工作完成



编写测试函数 

细节不多说

java代码 src/org/gl/jni/JNI.java

package org.gl.jni;

public class JNI {

static {
System.loadLibrary("faad");
}

public static native int fileBackPlay(String fileName);
}
调用代码如下 
new Thread() {public void run() {try {JNI.fileBackPlay("/sdcard/test1.aac");} catch (Exception e) {// TODO: handle exception}};}.start();

完成后会在sdcard下面产生一个PCM格式的输出文件 
/sdcard/pcm_faac_out

拿出来在windows下面 用cooledit播放一下。具体参数跟 下面调用函数中的文件 的编码设置有关

AAC格式编码介绍:AAC编码就是将PCMGE格式的音频信号压缩 有利于网络传输和存储

pcm:声音模拟信号数字化后的数据