A complete course on Android App Development using Android NDK
In this course, you are going to learn about android NDK development. After enrolling and watching the whole course you will able to develop android apps with C/C++. I will introduce some famous libraries in this course, like FFMPEG, Google Obeo and Banuba SDK.
So you can develop apps like snap chat, Instagram and tik talk filter features in your app and with the help of FFMPEG, you will be able to develop video and audio editing apps.
Getting Started with NDK
-
1What is NDK and why to use it?
The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input.
But for Android development, Java and Kotlin are the recommended languages. So, why to use native languages in Android? Let' find out the advantages of using native languages in Andoird:
Very FastCode Re-usability
So, whenever you want to make some high-performance applications for great speed or want to use some preexisting code written in some native language then you can use C or C++. Due to the speed factor, most of the game developers use C or C++ for writing the code for their games. Also, it becomes an easier task for them to use their C or C++ code in the Android application.
-
2NDK contents and structure
The NDK contains the APIs, documentation, and sample applications that help you write your native code.
Development tools
The NDK includes a set of cross-toolchains (compilers, linkers, etc..) that can generate native ARM binaries on Linux, OS X, and Windows (with Cygwin) platforms.
It provides a set of system headers for stable native APIs that are guaranteed to be supported in all later releases of the platform:
libc (C library) headers
libm (math library) headers
JNI interface headers
libz (Zlib compression) headers
liblog (Android logging) header
OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers
libjnigraphics (Pixel buffer access) header (for Android 2.2 and above).
A Minimal set of headers for C++ support
OpenSL ES native audio libraries
Android native application APIS
NDK installation simply requires unzipping it to a suitable location.
NDK contains a cross-toolchain for ARM and x86 based CPUs, header files and stable libraries.Followings are involves
1- Build scripts (makefiles, awk scripts etc.)
2- Documentation (HTML)
3- Platforms (header files and stable libraries)
4- Build executables (make, awk, sed, echo)
5- Samples (hello world, JNI example etc.)
6- Source files that can be linked to an application or library
7- Test scripts for automated tests of the NDK
8- ARM Linux and x86 toolchains (compiler, linker etc.)
9- Documentation entry point
10- Makefile for building NDK
11- Build script for building a native application or library
12- Experimental Windows native build script (working?)
13- GDB debug start script
14- Stack trace analysis tool
15- Readme file
16- NDK release identifier
-
3What is CMake Android Build Script
CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.
CMake is an external build tool that works alongside Gradle to build your native library. You do not need this component if you only plan to use ndk-build.
The ndk-build script builds projects that use the NDK's Make-based build system. There is more specific documentation for the Android.mk and Application.mk configuration used by ndk-build.
The Android NDK r4 introduced a new tiny shell script, named 'ndk-build', to simplify building machine code.The script is located at the top-level directory of the NDK, and shall be invoked from the command-line when in your application project directory, or any of its sub-directories.
For example: cd $PROJECT $NDK/ndk-build
-
4What is JNI ( Java Native Interface )?
JNI or Java Native Interface is the interface between your Java/Kotlin and the C/C++ code. It defines a way for the byte code that is generated by the Android to communicate with the native code. Java or Kotlin Code uses JNI to communicate with the C or C++ code.
By programming through the JNI, you can use native methods to:
Create, inspect, and update Java objects (including arrays and strings).
Call Java methods.
Catch and throw exceptions.
Load classes and obtain class information.
Perform runtime type checking.
You can also use the JNI with the Invocation API to enable an arbitrary native application to embed the Java VM. This allows programmers to easily make their existing applications Java-enabled without having to link with the VM source code.
-
5Installing the Native Development Kit (NDK)
Using Android Studio 2.2 and higher, you can use the NDK to compile C and C++ code into a native library and package it into your APK using Gradle, the IDE's integrated build system. Your Java code can then call functions in your native library through the Java Native Interface (JNI) framework.
Download the NDK and ToolsTo compile and debug native code for your app, you need the following components:
The Android Native Development Kit (NDK): a set of tools that allows you to use C and C++ code with Android.
CMake: an external build tool that works alongside Gradle to build your native library. You do not need this component if you only plan to use ndk-build.
LLDB: the debugger Android Studio uses to debug native code.
Download Android Studio - https://developer.android.com/studio
-
6Writing a Hello-world Android NDK Program
What’s the Android NDK?
The Android Native Development Kit (NDK) is a set of tools that lets developers write parts of their apps in native code (C/C++), squeezing more performance out of devices and achieving better app performance.
Why do we need the Android NDK?
The NDK may significantly improve application performance, especially for processor-bound applications. Many multimedia applications and games use native code for processor-intensive tasks. There are three reasons why C/C++ can offer performance improvements:
C/C++ code is compiled to the binary that runs directly on the operating system, while Java code is compiled to Java bytecode and executed by the Java Virtual Machine.
Native code allows developers to make use of certain processor features that are not accessible via the Android SDK.
Critical code can be optimized at the assembly level.
Used in conjunction with the Android SDK, the NDK toolset provides access to platform libraries that app developers can use to manage native activities and access physical device components such as sensors and touch input. It’s also possible to use your own libraries or to use popular C/C++ libraries that have no equivalents in Java (such as the FFmpeg library written in C/C++ to process audio and video or the Unigraphics library to process bitmaps).
FFMPEG with Android
-
7Calling JNI methods in Java
The Java Native Interface (JNI) allows you to call native functions from Java code, and vice versa. This example shows how to load and call a native function via JNI, it does not go into accessing Java methods and fields from native code using JNI functions.
The method signatures in JNI C files contain the package name and class name as well. So you cannot call the same function from different activities/classes.eg.
JNIEXPORT float JNICALL
Java_packageName_methodName(JNIEnv *env, jobject thiz)
{ }
This JNI method 'getPageWidth() 'can be called only from
com.taf.ndk.methodName
-
8NDK Sample App
-
9Passing Data with Android NDK
Accessing Object's Variables and Calling Back Methods
5.1 Accessing Object's Instance Variables
JNI Program - TestJNIInstanceVariable.java
public class TestJNIInstanceVariable {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Instance variables
private int number = 88;
private String message = "Hello from Java";
// Declare a native method that modifies the instance variables
private native void modifyInstanceVariable();
// Test Driver
public static void main(String args[]) {
TestJNIInstanceVariable test = new TestJNIInstanceVariable();
test.modifyInstanceVariable();
System.out.println("In Java, int is " + test.number);
System.out.println("In Java, String is " + test.message);
}
}
The class contains two private instance variables: a primitive int called number and a String called message. It also declares a native method, which could modify the contents of the instance variables.
C Implementation - TestJNIInstanceVariable.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
(JNIEnv *env, jobject thisObj) {
// Get a reference to this object's class
jclass thisClass = (*env)->GetObjectClass(env, thisObj);
// int
// Get the Field ID of the instance variables "number"
jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
if (NULL == fidNumber) return;
// Get the int given the Field ID
jint number = (*env)->GetIntField(env, thisObj, fidNumber);
printf("In C, the int is %dn", number);
// Change the variable
number = 99;
(*env)->SetIntField(env, thisObj, fidNumber, number);
// Get the Field ID of the instance variables "message"
jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
if (NULL == fidMessage) return;
// String
// Get the object given the Field ID
jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
// Create a C-string with the JNI String
const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
if (NULL == cStr) return;
printf("In C, the string is %sn", cStr);
(*env)->ReleaseStringUTFChars(env, message, cStr);
// Create a new C-string and assign to the JNI string
message = (*env)->NewStringUTF(env, "Hello from C");
if (NULL == message) return;
// modify the instance variables
(*env)->SetObjectField(env, thisObj, fidMessage, message);
}
To access the instance variable of an object:
Get a reference to this object's class via GetObjectClass().
Get the Field ID of the instance variable to be accessed via GetFieldID() from the class reference. You need to provide the variable name and its field descriptor (or signature). For a Java class, the field descriptor is in the form of "L<fully-qualified-name>;", with dot replaced by forward slash (/), e.g.,, the class descriptor for String is "Ljava/lang/String;". For primitives, use "I" for int, "B" for byte, "S" for short, "J" for long, "F" for float, "D" for double, "C" for char, and "Z" for boolean. For arrays, include a prefix "[", e.g., "[Ljava/lang/Object;" for an array of Object; "[I" for an array of int.
Based on the Field ID, retrieve the instance variable via GetObjectField() or Get<primitive-type>Field() function.
To update the instance variable, use the SetObjectField() or Set<primitive-type>Field() function, providing the Field ID.
The JNI functions for accessing instance variable are:
jclass GetObjectClass(JNIEnv *env, jobject obj);
// Returns the class of an object.
jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for an instance variable of a class.
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
// Get/Set the value of an instance variable of an object
// <type> includes each of the eight primitive types plus Object.
Android Google Oboe Tutorial
-
10Introduction FFMPEG
FFmpeg is the leading multimedia framework, able to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created. It supports the most obscure ancient formats up to the cutting edge. No matter if they were designed by some standards committee, the community or a corporation. It is also highly portable: FFmpeg compiles, runs, and passes our testing infrastructure FATE across Linux, Mac OS X, Microsoft Windows, the BSDs, Solaris, etc. under a wide variety of build environments, machine architectures, and configurations.
It contains libavcodec, libavutil, libavformat, libavfilter, libavdevice, libswscale and libswresample which can be used by applications. As well as ffmpeg, ffplay and ffprobe which can be used by end users for transcoding and playing. -
11FFMPEG Compression
EXAMPLES
Cut video by time
ffmpeg -i video.mp4 -ss 00:01:11 -t 00:00:08 -async 1 out.mp4
# -t is duration, NOT endtime
Converting a video to images
mkdir frames
ffmpeg -i video.mp4 'frames/frame-%03d.jpg'
# change %03 to %0N for N digits in the filename
Converting a video to a .gif
mkdir frames
ffmpeg -i video.mp4 -r 20 'frames/frame-%03d.jpg'
# -r [N] for [N] fps
convert -resize 768x576 -delay 5 -loop 0 frames/*.jpg video.gif
# -resize is useful for reducing filesize
# -delay [N] with [N] = 100/fps
# -loop 0 to loop forever
Converting frames to video
ffmpeg -framerate 30 -pattern_type glob -i 'frames/*.jpg' -c:v libx264 -pix_fmt yuv420p out.mp4
Scale a video
ffmpeg -i input.avi -vf scale=852:480 output.avi
# scale=852:-1 to preserve the aspect ratio
Rotate a video
ffmpeg -i in.mov -vf "transpose=1" out.mov
# transpose:
# 0 = 90CounterCLockwise and Vertical Flip (default)
# 1 = 90Clockwise
# 2 = 90CounterClockwise
# 3 = 90Clockwise and Vertical Flip
Speed up / Slow down a video
ffmpeg -i input.mkv -filter:v "setpts=0.5*PTS" -an output.mkv
# this multiplies all timestamps by 0.5, making the video
# 2x as fast, without interpolation.
# frames are skipped in order to keep framerate.
# -an disables the audio stream
NOTES
Experimental codecs
# Getting this kind of error?
# "The encoder 'aac' is experimental but experimental codecs
# are not enabled, add '-strict -2' if you want to use it."
# -strict -2 needs to be added right before the last parameter
ffmpeg -i infile -strict -2 outfile
-
12FFMPEG Conversion Video to Audio
-
13FFMPEG Video Stacking
-
14FFMPEG WaterMark Effect
-
15FFMPEG Effects (Fade in and Fade out)