您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
实现将普通AndroidStudio4.1.2项目增加对c++函数调用的支持
发布时间:2021-06-16 15:45:57编辑:雪饮阅读()
如果知道自己的android studio项目会用到c++/c时候则会在建立Android studio项目的时候直接选择c++的模板。
但是有时候项目写着写着才发现需要c++的支持。
这里以我这个项目要依赖ffmpeg这个c写的轮子为例。
在app/build.gradle:
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.penguinvideoedit"
minSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
ndk{
abiFilters "arm64-v8a"
}
}
sourceSets{
main{
jniLibs.srcDirs=['libs']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
ndkVersion '17.2.4988734'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.github.bumptech.glide:glide:4.0.0'
}
这里指定了c++标准,我这里用c++11
ndk的abi过滤,这里我只编译了ffmpeg的arm64-v8a。所以就只指定这一个
定义jni的库路径为app/libs
定义了cmake版本以及cmake编译脚本路径为src/main/cpp/CMakeLists.txt。
然后在要使用到c++的地方,引入一个c++程序路径为app\src\main\cpp\native-lib(相对于cmake脚本的路径),以及声明一个本地函数,该本地函数由c++提供,并将这个来自c++的函数结果输出到日志中。
如app\src\main\java\com\example\penguinvideoedit\MainActivity3.java:
package com.example.penguinvideoedit;
import android.app.Activity;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity3 extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
private final static int requestCode_to_video_select_list=1;
private SurfaceView sv;
private SurfaceHolder holder;
MediaPlayer mediaPlayer;
String selectVideo_AbsolutePath=null;
Boolean ispause=false;
private Button bt_pause,bt_stop,bt_play;
private LinearLayout video_control_buttons,seekbar_linearLayout,time_zone;
private SeekBar seekbar;
Timer timer;
TextView seekbar_linearLayout_t1,seekbar_linearLayout_t2;
EditText s_editTextNumber_h,s_editTextNumber_m,s_editTextNumber_s,s_editTextNumber_ss;
EditText e_editTextNumber_h,e_editTextNumber_m,e_editTextNumber_s,e_editTextNumber_ss;
int VideoDuration;
boolean videoTimeGetOver=false;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
String CurrentPlayTime = (String) msg.obj;
seekbar_linearLayout_t1.setText(CurrentPlayTime);
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
sv = (SurfaceView) this.findViewById(R.id.sv);
bt_pause=this.findViewById(R.id.bt_pause);
bt_stop=this.findViewById(R.id.bt_stop);
bt_play=this.findViewById(R.id.bt_play);
seekbar_linearLayout_t1=this.findViewById(R.id.seekbar_linearLayout_t1);
seekbar_linearLayout_t2=this.findViewById(R.id.seekbar_linearLayout_t2);
bt_pause=this.findViewById(R.id.bt_pause);
time_zone=this.findViewById(R.id.time_zone);
video_control_buttons=this.findViewById(R.id.video_control_buttons);
seekbar_linearLayout=this.findViewById(R.id.seekbar_linearLayout);
seekbar=this.findViewById(R.id.seekbar);
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// txt_cur.setText("当前进度值:" + progress + " / 100 ");
}
//进度条拖拽开始
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Toast.makeText(mContext, "触碰SeekBar", Toast.LENGTH_SHORT).show();
}
//进度跳拖拽结束
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int Position= seekbar.getProgress();
if(mediaPlayer!=null){
mediaPlayer.seekTo(Position);
}
}
});
Intent intent = getIntent();
selectVideo_AbsolutePath = intent.getStringExtra("selectVideo_AbsolutePath");
holder = sv.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new MyHolderCallback());
s_editTextNumber_h=this.findViewById(R.id.s_editTextNumber_h);
s_editTextNumber_m=this.findViewById(R.id.s_editTextNumber_m);
s_editTextNumber_s=this.findViewById(R.id.s_editTextNumber_s);
s_editTextNumber_ss=this.findViewById(R.id.s_editTextNumber_ss);
e_editTextNumber_h=this.findViewById(R.id.e_editTextNumber_h);
e_editTextNumber_m=this.findViewById(R.id.e_editTextNumber_m);
e_editTextNumber_s=this.findViewById(R.id.e_editTextNumber_s);
e_editTextNumber_ss=this.findViewById(R.id.e_editTextNumber_ss);
s_editTextNumber_h.setText(intent.getStringExtra("s_editTextNumber_h"));
s_editTextNumber_m.setText(intent.getStringExtra("s_editTextNumber_m"));
s_editTextNumber_s.setText(intent.getStringExtra("s_editTextNumber_s"));
s_editTextNumber_ss.setText(intent.getStringExtra("s_editTextNumber_ss"));
e_editTextNumber_h.setText(intent.getStringExtra("e_editTextNumber_h"));
e_editTextNumber_m.setText(intent.getStringExtra("e_editTextNumber_m"));
e_editTextNumber_s.setText(intent.getStringExtra("e_editTextNumber_s"));
e_editTextNumber_ss.setText(intent.getStringExtra("e_editTextNumber_ss"));
}
@Override
public void onBackPressed() {
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.release();
}
finish();
}
public void videoInit() throws IOException {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(selectVideo_AbsolutePath);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDisplay(holder);
mediaPlayer.prepare();
}
public void play(View view) throws Exception {
mediaPlayer.release();
videoInit();
// 播放视频之前必须要初始化 视频的播放器
//mediaPlayer.prepare();
play(selectVideo_AbsolutePath,0);
bt_pause.setVisibility(View.VISIBLE);
bt_pause.setText("暂停");
ispause=false;
bt_play.setVisibility(View.GONE);
bt_stop.setVisibility(View.VISIBLE);
}
private class MyHolderCallback implements SurfaceHolder.Callback {
public void surfaceCreated(SurfaceHolder holder) {
//System.out.println("holder 被创建 ");
// 当视频播放最小化之后 下一次 必须在holder被创建后 再去播放视频
/*
if (position > 0) {
String path = et_path.getText().toString().trim();
try {
// 重新设置显示的holder
MainActivity.this.holder = holder;
play(path, position);
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "播放出现一次", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
*/
if(selectVideo_AbsolutePath!=null){
try {
video_control_buttons.setVisibility(View.VISIBLE);
bt_pause.setText("暂停");
videoInit();
//显示进度条
VideoDuration=mediaPlayer.getDuration();
videoTimeGetOver=true;
seekbar_linearLayout.setVisibility(View.VISIBLE);
seekbar.setMax(VideoDuration);
//设置进度条左右时间样式
String end_time_style=formatDateTime(VideoDuration);
System.out.println("毫秒:"+VideoDuration);
System.out.println("end_time_style:"+end_time_style);
seekbar_linearLayout_t1.setText("00:00:00.00");
seekbar_linearLayout_t2.setText(end_time_style);
play(selectVideo_AbsolutePath,0);
//显示时间区间编辑区域
time_zone.setVisibility(View.VISIBLE);
timer = new Timer();//时间监听器
timer.schedule(new TimerTask() {
@Override
public void run() {
try{
if(mediaPlayer!=null){
if(mediaPlayer.isPlaying()){
String current_time_style=formatDateTime(mediaPlayer.getCurrentPosition());
//updateCurrentPlayTimeUi(current_time_style);
seekbar.setProgress(mediaPlayer.getCurrentPosition());
Message msg = new Message();
msg.obj = current_time_style;
handler.sendMessage(msg);
}
}
}
catch(IllegalStateException e){
e.printStackTrace();
}
}
},0,500);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
//实现SurfaceHolder.Callback接口,则surfaceChanged不实现不行,实际上这个方法可以根据情况编写一些逻辑
//这里暂时用不上
// System.out.println("holder 改变:format"+format+"width:"+width+"height:"+height);
}
public void surfaceDestroyed(SurfaceHolder holder) {
//一般如按home键最小化后会触发这个方法
//System.out.println("holder 被销毁 ");
// 记录最小化之后播放器播放的位置
/*
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
position = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
*/
}
}
public static String formatDateTime(long milliseconds) {
StringBuilder sb = new StringBuilder();
long hours = (long)Math.floor(milliseconds/1000/3600);
long minutes =(long)Math.floor((milliseconds-hours*3600*1000)/1000/60);
long seconds =(long)Math.floor((milliseconds-hours*3600*1000-minutes*60*1000)/1000);
long ms =milliseconds-hours*3600*1000-minutes*60*1000-seconds*1000;
DecimalFormat format = new DecimalFormat("00");
sb.append(format.format(hours)).append(":").append(format.format(minutes)).append(":").append(format.format(seconds)).append(":").append(format.format(ms));
return sb.toString();
}
public void pause(View view) {
bt_play.setVisibility(View.GONE);
bt_stop.setVisibility(View.VISIBLE);
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
bt_pause.setText("播放");
ispause=true;
}
else if (mediaPlayer != null && ispause==true) {
mediaPlayer.start();
bt_pause.setText("暂停");
ispause = false;
}
}
public void stop(View view) {
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.release();
bt_pause.setVisibility(View.GONE);
bt_stop.setVisibility(View.GONE);
bt_play.setVisibility(View.VISIBLE);
}
}
private void play(String path, int position) throws Exception {
File file = new File(path);
if (file.exists()) {
mediaPlayer.start();
mediaPlayer.seekTo(position);
//播放完成
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
// TODO Auto-generated method stub
mediaPlayer.release();
mediaPlayer = null;
String CurrentPlayTime= formatDateTime(VideoDuration);
seekbar_linearLayout_t1.setText(CurrentPlayTime);
}
});
// 当播放出现错误的时候的回调方法
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.release();
mediaPlayer = null;
return false;
}
});
} else {
Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
}
public void video_cut(View view) {
int s_editTextNumber_h_v=Integer.parseInt(s_editTextNumber_h.getText().toString());
int s_editTextNumber_m_v=Integer.parseInt(s_editTextNumber_m.getText().toString());
int s_editTextNumber_s_v=Integer.parseInt(s_editTextNumber_s.getText().toString());
int s_editTextNumber_ss_v=Integer.parseInt(s_editTextNumber_ss.getText().toString());
int e_editTextNumber_h_v=Integer.parseInt(e_editTextNumber_h.getText().toString());
int e_editTextNumber_m_v=Integer.parseInt(e_editTextNumber_m.getText().toString());
int e_editTextNumber_s_v=Integer.parseInt(e_editTextNumber_s.getText().toString());
int e_editTextNumber_ss_v=Integer.parseInt(e_editTextNumber_ss.getText().toString());
int s_time=s_editTextNumber_h_v*3600000+s_editTextNumber_m_v*60000+s_editTextNumber_s_v*1000+s_editTextNumber_ss_v;
int e_time=e_editTextNumber_h_v*3600000+e_editTextNumber_m_v*60000+e_editTextNumber_s_v*1000+e_editTextNumber_ss_v;
if(videoTimeGetOver==false){
Toast.makeText(this, "视频暂未载入完成", Toast.LENGTH_SHORT).show();
return;
}
else if(s_time>=VideoDuration){
Toast.makeText(this, "开始时间不得大于等于视频总时长", Toast.LENGTH_SHORT).show();
return;
}
else if(s_time>=e_time){
Log.i("MainActivity3","s_time:"+s_time+",e_time:"+e_time+",e_editTextNumber_m_v:"+e_editTextNumber_m_v);
Toast.makeText(this, "开始时间不得大于等于结束时间", Toast.LENGTH_SHORT).show();
return;
}
else if(e_time>VideoDuration){
Log.i("MainActivity3","e_time:"+e_time+",VideoDuration:"+VideoDuration);
Toast.makeText(this, "结束时间不得大于视频总时长", Toast.LENGTH_SHORT).show();
return;
}
Log.i("MainActivity3","来自c++:"+stringFromJNI());
//ffmpeg -i blmsyb.mp4 -ss 00:00:01.10 -t 00:00:03.50 -c copy blmsyb2.mp4
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==requestCode_to_video_select_list){
if(resultCode == Activity.RESULT_OK){
selectVideo_AbsolutePath = data.getData().getPath();
}
}
}
}
在app中建立include目录用来存放ffmpeg编译出来的头文件.h
D:\penguinVideoEdit\app\include>tree /F
卷 软件 的文件夹 PATH 列表
卷序列号为 0592-36B3
D:.
├─libavcodec
│ ac3_parser.h
│ adts_parser.h
│ avcodec.h
│ avdct.h
│ avfft.h
│ bsf.h
│ codec.h
│ codec_desc.h
│ codec_id.h
│ codec_par.h
│ d3d11va.h
│ dirac.h
│ dv_profile.h
│ dxva2.h
│ jni.h
│ mediacodec.h
│ packet.h
│ qsv.h
│ vaapi.h
│ vdpau.h
│ version.h
│ videotoolbox.h
│ vorbis_parser.h
│ xvmc.h
│
├─libavfilter
│ avfilter.h
│ buffersink.h
│ buffersrc.h
│ version.h
│
├─libavformat
│ avformat.h
│ avio.h
│ version.h
…………………………………….
然后建立app/libs目录用来存放ffmpeg编译出来的动态链接库:
D:\penguinVideoEdit\app\libs>tree /F
卷 软件 的文件夹 PATH 列表
卷序列号为 0592-36B3
D:.
└─arm64-v8a
libavcodec.so
libavfilter.so
libavformat.so
libavutil.so
libswresample.so
libswscale.so
然后建立cmake编译脚本于app\src\main\cpp\ CMakeLists.txt:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("penguinvideoedit")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#添加頭文件路徑(括號中的include是相對於本文件路徑)
include_directories(../../../include)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp
)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
android
# Links the target library to the log library
# included in the NDK.
${log-lib} )
这里project指定Androidstudio项目中包名的最后一级名称。
include_directories添加头文件路径(相对于cmake脚本路径)。
target_link_libraries中的android看情况,一般都是会用到的。
然后创建一个给前面java程序用的c++程序native-lib.cpp于app\src\main\cpp中:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_penguinvideoedit_MainActivity3_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_penguinvideoedit_MainActivity3_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
这里函数的格式是将包名一直到最后那个java程序文件的类名的”.”连字符替换为”_”连字符的格式。
然后运行在如魅族16T中
可以看到成功调用了c++的程序。
关键字词:AndroidStudio4.1.2,c++