• NDK学习笔记-使用现有so动态库


    前面将的都是如何使用C/C++文件生成so动态库,那么在使用别人的so动态库的时候应该怎么做呢?这篇文章就是使用一个变声功能的动态库,完成对于以有so动态库的说明。

    动态库来源

    • 在互联网中,有着许许多多动态库,很多厂商也对外提供动态库供开发者调用,例如高德地图的动态库,做地图开发的时候还是很方便的

    • 本文主要讲一个可以使声音改变的动态库,这个动态库主要用于游戏中,游戏引擎中有使用到

    • 这就是fmod动态库,首先我们要去下载其动态库文件
      官网地址
      先要注册才能下载其文件,按照步骤来就好

    • 在其下载界面,有FMOD Studio API,这里可以选择版本下载,我写这篇博客的时候,最新版本是1.10.07,在这里就不下载最新的了,我选择的是1.08.28,也就是1.08的最后一个版本。

    添加到项目中

    • 解压下载的文件,发现在api文件夹下有三个目录:fsbanklowlevelstudio

    • 这里选择lowlevel,这是基于普遍使用选择的,也可以选择studio,其功能更为强大,不过相对地也更需要运算性能

    • 在Android项目中新建libs目录,将fmod.jar拷贝至libs目录

    • 新建jni目录,将armeabiarmeabi-v7a下的so文件拷贝至jni目录,将lowlevel目录下的inc头文件拷贝至jni文件夹,在这里先实现原声播放的功能,故在lowlevel下的examples下找到play_sound.cpp源文件,将其放在jni目录下,打开文件得知,其依赖的common.h头文件并不在inc中,找到common.h并拷贝至jni中,逐步寻找缺失的依赖文件,导入到jni中,整理完成后的jni文件目录如下:

    │  Android.mk
    │  common.cpp
    │  common.h
    │  common_platform.cpp
    │  common_platform.h
    │  libfmod.so
    │  libfmodL.so
    │  play_sound.cpp
    └─inc
            fmod.h
            fmod.hpp
            fmod_codec.h
            fmod_common.h
            fmod_dsp.h
            fmod_dsp_effects.h
            fmod_errors.h
            fmod_output.h
    

    修改文件使其能够调用

    • lowlevel目录下,有Java的调用示例,在这里直接使用这个MainActivity.java进行修改调用

    • 阅读MainActivity.java源代码,发现其使用的是动态获取权限,为方便使用,直接在清单文件中生命其权限,将其动态申请注释掉,在动态库加载时候,发现加载了一些没有的动态库,将没有的动态库去掉,加上自己的动态库,注意到jni中的调用方法和现有包名不统一,修改之,并将清单文件中的启动活动包名也修改

    package org.fmod.example;
    
    import android.os.Build;
    import android.os.Bundle;
    import android.app.Activity;
    import android.graphics.Typeface;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnTouchListener;
    import android.widget.LinearLayout;
    import android.widget.LinearLayout.LayoutParams;
    import android.widget.TextView;
    import android.widget.Button;
    import android.content.pm.PackageManager;
    
    public class MainActivity extends Activity implements OnTouchListener, Runnable
    {
    	private TextView mTxtScreen;
    	private Thread mThread;
    	
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
        	super.onCreate(savedInstanceState);
    
        	// Create the text area
        	mTxtScreen = new TextView(this);
        	mTxtScreen.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10.0f);
        	mTxtScreen.setTypeface(Typeface.MONOSPACE);
    
            // Create the buttons
            Button[] buttons = new Button[9];
            for (int i = 0; i < buttons.length; i++)
            {
            	buttons[i] = new Button(this);
            	buttons[i].setText(getButtonLabel(i));
            	buttons[i].setOnTouchListener(this);
            	buttons[i].setId(i);
            }
            
            // Create the button row layouts
            LinearLayout llTopRowButtons = new LinearLayout(this);
            llTopRowButtons.setOrientation(LinearLayout.HORIZONTAL);
            LinearLayout llMiddleRowButtons = new LinearLayout(this);
            llMiddleRowButtons.setOrientation(LinearLayout.HORIZONTAL);
            LinearLayout llBottomRowButtons = new LinearLayout(this);
            llBottomRowButtons.setOrientation(LinearLayout.HORIZONTAL);
            
            // Create the main view layout
            LinearLayout llView = new LinearLayout(this);
            llView.setOrientation(LinearLayout.VERTICAL);       
    
            // Create layout parameters
            LinearLayout.LayoutParams lpLayout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
            
            // Set up the view hierarchy
            llTopRowButtons.addView(buttons[0], lpLayout);
            llTopRowButtons.addView(buttons[6], lpLayout);
            llTopRowButtons.addView(buttons[1], lpLayout);
            llMiddleRowButtons.addView(buttons[4], lpLayout);
            llMiddleRowButtons.addView(buttons[8], lpLayout);
            llMiddleRowButtons.addView(buttons[5], lpLayout);
            llBottomRowButtons.addView(buttons[2], lpLayout);
            llBottomRowButtons.addView(buttons[7], lpLayout);
            llBottomRowButtons.addView(buttons[3], lpLayout);
            llView.addView(mTxtScreen, lpLayout);
            llView.addView(llTopRowButtons);
            llView.addView(llMiddleRowButtons);
            llView.addView(llBottomRowButtons);
            
            setContentView(llView);
    
            // Request necessary permissions
    //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
    //        {
    //            String[] perms = { "android.permission.RECORD_AUDIO", "android.permission.WRITE_EXTERNAL_STORAGE" };
    //            if (checkSelfPermission(perms[0]) == PackageManager.PERMISSION_DENIED ||
    //                checkSelfPermission(perms[1]) == PackageManager.PERMISSION_DENIED)
    //            {
    //                requestPermissions(perms, 200);
    //            }
    //        }
    
            org.fmod.FMOD.init(this);
            
            mThread = new Thread(this, "Example Main");
            mThread.start();
            
            setStateCreate();
        }
    	
        @Override
        protected void onStart()
        {
        	super.onStart();
        	setStateStart();
        }
        
        @Override
        protected void onStop()
        {
        	setStateStop();
        	super.onStop();
        }
        
        @Override
        protected void onDestroy()
        {
        	setStateDestroy();
        	
        	try
        	{
        		mThread.join();
        	}
        	catch (InterruptedException e) { }
        	
        	org.fmod.FMOD.close();
        	
        	super.onDestroy();
        }
        
    	@Override
    	public boolean onTouch(View view, MotionEvent motionEvent)
    	{
    		if (motionEvent.getAction() == MotionEvent.ACTION_DOWN)
    		{
    			buttonDown(view.getId());	
    		}
    		else if (motionEvent.getAction() == MotionEvent.ACTION_UP)
    		{
    			buttonUp(view.getId());	
    		}			
    	    
    		return true;
    	}
    
    	@Override
    	public void run()
    	{
            main();
    	}
    	
    	public void updateScreen(final String text)
    	{
    		runOnUiThread(new Runnable()
    		{
    	        @Override
    	        public void run()
    	        {
    	            mTxtScreen.setText(text);
    	        }
    		});
    	}
    	
    	private native String getButtonLabel(int index);
    	private native void buttonDown(int index);
    	private native void buttonUp(int index);
    	private native void setStateCreate();
    	private native void setStateStart();
    	private native void setStateStop();
    	private native void setStateDestroy();
    	private native void main();
    	
        static 
        {
        	/*
        	 * To simplify our examples we try to load all possible FMOD
        	 * libraries, the Android.mk will copy in the correct ones
        	 * for each example. For real products you would just load
        	 * 'fmod' and if you use the FMOD Studio tool you would also
        	 * load 'fmodstudio'.
        	 */
    
    //    	// Try debug libraries...
    //    	try { System.loadLibrary("fmodD");
    //    		  System.loadLibrary("fmodstudioD"); }
    //    	catch (UnsatisfiedLinkError e) { }
    //    	// Try logging libraries...
    //    	try { System.loadLibrary("fmodL");
    //    		  System.loadLibrary("fmodstudioL"); }
    //    	catch (UnsatisfiedLinkError e) { }
    //		// Try release libraries...
    //		try { System.loadLibrary("fmod");
    //		      System.loadLibrary("fmodstudio"); }
    //		catch (UnsatisfiedLinkError e) { }
    //    	
    //    	System.loadLibrary("stlport_shared");
    //        System.loadLibrary("example");
        	
        	try { 
        		System.loadLibrary("fmod");
        		System.loadLibrary("fmodL"); 
        	} catch (UnsatisfiedLinkError e) {
        		e.printStackTrace();
        	}
        	System.loadLibrary("VoiceChangeTest");
        }	
    }
    
    • 使用Android Tools添加本地支持

    • 修改Android.mk文件,并记录动态库的文件名,将其加载至MainActivity.java

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE    := VoiceChangeTest
    LOCAL_SRC_FILES := play_sound.cpp
    
    include $(BUILD_SHARED_LIBRARY)
    

    编译项目

    • 此时便可以编译项目了,编译时候会提示有些文件找不到,那是因为包含文件路径不对造成的,此时修改包含文件路径即可

    • 文件包含错误解决以后,再次编译,发现很多函数找不到,此时是因为编译时候那些函数的实现没有编译到项目,修改Android.mk文件,加入依赖实现

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE := fmod
    LOCAL_SRC_FILES := libfmod.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE := fmodL
    LOCAL_SRC_FILES := libfmodL.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE    := VoiceChangeTest
    LOCAL_SRC_FILES := play_sound.cpp 
    			common_platform.cpp 
    			common.cpp
    LOCAL_SHARED_LIBRARIES := fmod fmodL
    include $(BUILD_SHARED_LIBRARY)
    
    • 另外,由于用到了STL库,需要在配置里说明,在jni下新建Application.mk文件,写入以下配置
    APP_STL := gnustl_static
    
    • 至此,项目修改完毕,便可以生成apk了。运行界面如下:
      运行播放

    仿QQ变声效果实现

    在大致了解fmod以后,就可以做一些基于fmod的项目了,正好QQ有一个变声的功能,这里就使用fmod去实现

    采集素材

    直接将QQ安装包解压,就可以得到图片素材,将其加入到素材中

    编辑界面

    编写界面,这里直接采用他人布局好的文件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="vertical"
            android:background="#FFF"
            android:paddingBottom="20dp" >
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_marginTop="20dp">
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_normal"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/record"
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="原声"/>
                </LinearLayout>
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_luoli"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/luoli"
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="萝莉"/>
                </LinearLayout>
                
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_dashu"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/dashu"
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="大叔"/>
                </LinearLayout>
            </LinearLayout>
            
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_marginTop="20dp">
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_jingsong"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/jingsong"
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="惊悚"/>
                </LinearLayout>
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_gaoguai"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/gaoguai"
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="搞怪"/>
                </LinearLayout>
    
                <LinearLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                    <ImageView
                        android:id="@+id/btn_kongling"
    	                style="@style/AudioImgStyle"
    	                android:src="@drawable/kongling" 
    	                android:onClick="mFix"/>
                    <TextView 
                        style="@style/AudioTextStyle"
                        android:text="空灵"/>
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    
    </RelativeLayout>
    

    style文件

    <resources>
        <style name="AppBaseTheme" parent="android:Theme.Light">
            <!--
                Theme customizations available in newer API levels can go in
                res/values-vXX/styles.xml, while customizations related to
                backward-compatibility can go here.
            -->
        </style>
    
        <!-- Application theme. -->
        <style name="AppTheme" parent="AppBaseTheme">
            <!-- All customizations that are NOT specific to a particular API-level can go here. -->
        </style>
    
        <style name="AudioImgStyle">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:layout_marginLeft">25dp</item>
            <item name="android:layout_marginRight">25dp</item>
        </style>
        
        <style name="AudioTextStyle">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:layout_marginTop">5dp</item>
            <item name="android:layout_gravity">center_horizontal</item>
        </style>
    </resources>
    

    编辑java代码

    主活动

    package org.fmod.example;
    
    import java.io.File;
    
    import org.fmod.FMOD;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Environment;
    import android.util.Log;
    import android.view.View;
    
    public class QQActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		FMOD.init(this);
    		setContentView(R.layout.activity_main);
    	}
    	
    	public void mFix(View v) {
    		String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "test.wav";
    		Log.e("java cj5785", path);
    		switch (v.getId()) {
    		case R.id.btn_normal:
    			Log.d("cj5785", "normal");
    			QQVoice.fix(path, QQVoice.MODE_NORMAL);
    			break;
    		case R.id.btn_luoli:
    			Log.d("cj5785", "luoli");
    			QQVoice.fix(path, QQVoice.MODE_LUOLI);
    			break;
    		case R.id.btn_dashu:
    			Log.d("cj5785", "dashu");
    			QQVoice.fix(path, QQVoice.MODE_DASHU);
    			break;
    		case R.id.btn_jingsong:
    			Log.d("cj5785", "jingsong");
    			QQVoice.fix(path, QQVoice.MODE_JINGSONG);
    			break;
    		case R.id.btn_gaoguai:
    			Log.d("cj5785", "gaoguai");
    			QQVoice.fix(path, QQVoice.MODE_GAOGUAI);
    			break;
    		case R.id.btn_kongling:
    			Log.d("cj5785", "kongling");
    			QQVoice.fix(path, QQVoice.MODE_KONGLING);
    			break;
    		}
    	}
    	
    	@Override
    	protected void onDestroy() {
    		super.onDestroy();
    		FMOD.close();
    	}
    }
    

    工具类

    package org.fmod.example;
    
    public class QQVoice {
    
    	//音效的类型
    	public static final int MODE_NORMAL = 0;
    	public static final int MODE_LUOLI = 1;
    	public static final int MODE_DASHU = 2;
    	public static final int MODE_JINGSONG = 3;
    	public static final int MODE_GAOGUAI = 4;
    	public static final int MODE_KONGLING = 5;
    	
    	public native static void fix(String path, int type);
    	static {
    		System.loadLibrary("fmod");
    		System.loadLibrary("fmodL");
    		System.loadLibrary("QQVoice");
    	}
    }
    

    native方法实现

    #include <stdlib.h>
    #include <unistd.h>
    
    #include "inc/fmod.hpp"
    #include "org_fmod_example_QQVoice.h"
    
    #include <android/log.h>
    #define LOGD(FORMAT,...) __android_log_print(ANDROID_LOG_DEBUG,"cj5785",FORMAT,##__VA_ARGS__);
    #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"cj5785",FORMAT,##__VA_ARGS__);
    
    #define MODE_NORMAL 0
    #define MODE_LUOLI 1
    #define MODE_DASHU 2
    #define MODE_JINGSONG 3
    #define MODE_GAOGUAI 4
    #define MODE_KONGLING 5
    
    using namespace FMOD;
    
    JNIEXPORT void JNICALL Java_org_fmod_example_QQVoice_fix
      (JNIEnv *env, jclass jcls, jstring jstr_path, jint type)
    {
    	System *system;
    	Sound *sound;
    	Channel *channel;
    	DSP *dsp;
    	bool playing = true;
    	float frequency = 0.0F;
    
    	const char *path_cstr = env->GetStringUTFChars(jstr_path, NULL);
    	try{
    		//初始化
    		System_Create(&system);
    		system->init(32, FMOD_INIT_NORMAL, NULL);
    
    		//创建声音
    		system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
    		switch (type)
    		{
    			//原声
    			case MODE_NORMAL:
    				LOGD("%s", "NORMAL");
    				system->playSound(sound, 0, false, &channel);
    				break;
    			//萝莉
    			case MODE_LUOLI:
    				LOGD("%s", "LUOLI");
    				system->playSound(sound, 0, false, &channel);
    				system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
    				//设置音调
    				dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 2.5);
    				//添加至channel
    				channel->addDSP(0, dsp);
    				break;
    			//大叔
    			case MODE_DASHU:
    				LOGD("%s", "DASHU");
    				system->playSound(sound, 0, false, &channel);
    				system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
    				//设置音调
    				dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.7);
    				//添加至channel
    				channel->addDSP(0, dsp);
    				break;
    			//惊悚
    			case MODE_JINGSONG:
    				LOGD("%s", "JINGSONG");
    				system->playSound(sound, 0, false, &channel);
    				system->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
    				dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.5);
    				channel->addDSP(0, dsp);
    				break;
    			//搞怪
    			case MODE_GAOGUAI:
    				LOGD("%s", "GAOGUAI");
    				system->playSound(sound, 0, false, &channel);
    				//提高说话速度
    				channel->getFrequency(&frequency);
    				frequency *= 2.3;
    				channel->setFrequency(frequency);
    				break;
    			//空灵
    			case MODE_KONGLING:
    				LOGD("%s", "KONGLING");
    				system->playSound(sound, 0, false, &channel);
    				system->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
    				dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
    				dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 50);
    				channel->addDSP(0, dsp);
    				break;
    			default:
    				break;
    		}
    	}catch(...){
    		LOGE("s", "异常状况发生");
    		goto End;
    	}
    
    	system->update();
    
    	while(playing)
    	{
    		channel->isPlaying(&playing);
    		usleep(1000 * 1000);
    	}
    	goto End;
    End:
    	//释放资源
    	env->ReleaseStringUTFChars(jstr_path, path_cstr);
    	sound->release();
    	system->close();
    	system->release();
    }
    

    mk文件修改

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE := fmod
    LOCAL_SRC_FILES := libfmod.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE := fmodL
    LOCAL_SRC_FILES := libfmodL.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE    := QQVoice
    LOCAL_SRC_FILES := QQVoice.cpp
    LOCAL_SHARED_LIBRARIES := fmod fmodL
    LOCAL_LDLIBS := -llog
    LOCAL_CPP_FEATURES := exceptions
    
    include $(BUILD_SHARED_LIBRARY)
    

    jni目录结构

    │  Android.mk
    │  Application.mk
    │  common.cpp
    │  common.h
    │  common_platform.cpp
    │  common_platform.h
    │  effects.cpp
    │  libfmod.so
    │  libfmodL.so
    │  org_fmod_example_QQVoice.h
    │  QQVoice.cpp
    └─inc
            fmod.h
            fmod.hpp
            fmod_codec.h
            fmod_common.h
            fmod_dsp.h
            fmod_dsp_effects.h
            fmod_errors.h
            fmod_output.h
    
  • 相关阅读:
    牛客挑战赛48C铬合金之声【Prufer序列】
    Java 基础 反射
    MDX Query mdx的基本语法和概念
    Maven 深入理解maven构建生命周期和各种plugin插件
    Java基础 String,StringBuilder,StringBuffer三者的区别
    Java基础 Java 抽象类 抽象方法
    Java 基础 final vs static
    JMX JMX(Java Management Extensions)定义
    Java 多线程 生产者消费者问题
    Java 基础 如何重写equals()
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664667.html
Copyright © 2020-2023  润新知