Saturday, May 10, 2014

HEVC/H.265 Video Decoding on Android

HEVC Decoding on Android - Porting lib265 to Android using NDK - Part I


libde265 is an open source implementation of the h.265 video codec. It is written from scratch in plain C for simplicity and efficiency. Its simple API makes it easy to integrate it into other software.

Before jumping into the build using NDK, we will understand the build procedure for lib265. 


Install the pre-requisites (Ubuntu 12.04) -

sudo apt-get install autotools-dev
sudo apt-get install automake
sudo apt-get install libtool


Download the code from the location:

https://github.com/strukturag/libde265

As per the documentation, the script "configure" must be generated using the 
script "autogen.sh". As we are interested in the static library, we will disable the
build of other programs.

--disable-dec265        # Do not dec265 decoder program.
--disable-sherlock265   # Do not build sherlock265 visual inspection program.

Many of the existing open source libraries can be built with the shell command 
"./configure; make; make install". In our sample project, we wrote a build_android.sh 
script to execute the three steps with the Android NDK cross compiler.


Define variables:

As the installation paths may be too long, we can define variables and use those variables 
rather than the absolute paths. Here we define a variable NDK specifying the path where 
our NDK is installed.

NDK=/home/ramesh/Android/Google/NDK/android-ndk-r9


Select the appropriate toolchain:

Based on the CPU architecture (ARM, x86 or MIPS) of our targeted devices, you need to 
choose the corresponding toolchain. The toolchains are available under the toolchains 
folder of Android NDK r8d. As we want to build for ARM, we will use 
"arm-linux-androideabi-4.8" toolchain. We will specify this as-

TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64

$NDK refers to the Android NDK root folder.


Select the sysroot: 

Based on the Android native API level and CPU architecture we want to 
target, you will need to choose the appropriate sysroot. The compiler will look for 
headers and libraries under the sysroot directory at compilation.

The path to sysroot follows this format: 

$NDK/platforms/android-<level>/arch-<arch>/

$NDK refers to the Android NDK root folder, <level> refers to the Android API level, 
and <arch> indicates the CPU architecture.

Based on my environment the SYSROOT is:

SYSROOT=$NDK/platforms/android-8/arch-arm/

The sysroot option "--sysroot" would be specified along with the cross compiler.


Specify the cross compiler:

The library's existing build system usually has a way for us to specify the cross 
compiler. It is usually through a configuration option or an environment variable.

In libde265, we can enter the "./configure --help" command to see how to set the compiler. 
The compiler command is specified through the environment variable CC, while the 
environment variables CFLAGS and LDFLAGS are used to specify the compiler flags and 
linker flags. In our build_android.sh script, the CC environment variables is set 
as follows:

export CC="$TOOLCHAIN/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT"



Specify the output locations for the header files and library binary: 

The output would be generated in the folder ./android/arm
It would contain two folders, "lib" and "include" containing the library and header file
respectively. We specify this by passing the following options to configure the script:

--prefix=$PREFIX

where

CPU=arm
PREFIX=$(pwd)/android/$CPU

Specify the system types:
The following System types are available:
--build=BUILD     configure for building on BUILD [guessed]
--host=HOST       cross-compile to build programs to run on HOST [BUILD]
--target=TARGET   configure for building compilers for TARGET [HOST]
  
We specify the values as-
--build=x86_64-unknown-linux-gnu
--host=arm-linux-androideabi
--target=arm-linux-androideabi


Make and install the library: 

You can simply execute "make; make install;" to build and install the library.

The complete build_android.sh is as shown below-

#!/bin/bash
NDK=/home/ramesh/Android/Google/NDK/android-ndk-r9
SYSROOT=$NDK/platforms/android-8/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
export CC="$TOOLCHAIN/bin/arm-linux-androideabi-gcc --sysroot=$SYSROOT"
function build_one
{
./autogen.sh
./configure \
--disable-dec265 \
--disable-sherlock265 \
--prefix=$PREFIX \
--build=x86_64-unknown-linux-gnu \
--host=arm-linux-androideabi \
--target=arm-linux-androideabi \
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
build_one


Building the library:

To build the library, do the following steps-

1. Save the build_android.sh file to the libde265 folder
2. Add the execute permission to the build_android.sh file with the following command:

   $ sudo chmod +x build_android.sh

3. At a command line shell, go to the libde265 directory, and enter the following
command to build the library:

   $ ./build_android.sh

Once the build completes successfully, check the android folder in lib265. The following
files should be present-

android/arm/lib/libde265.a
android/arm/lib/libde265.so

android/arm/include/libde265/de265.h
android/arm/include/libde265/de265-version.h

Other options can also be specified which I will cover in other post.



Ref: 

1. Android NDK Cookbook
2. https://github.com/strukturag/libde265

Tuesday, June 4, 2013

Webview in Dialog

If you want to show webview in a dialog, use the following code snippet-


private void showWebviewInDialog(){
  Context mContext = getActivity();
  AlertDialog.Builder alert = new AlertDialog.Builder(mContext);

  alert.setTitle("Pay your premium");
  WebView wv = new WebView(mContext);

                  //Required else page would open in browser
  wv.setWebViewClient(new MyWebViewClient());

                  //To load HTML
  //String html = "<html><body>some html here</body></html>";
                  //wv.loadData(html, "text/html", "UTF-8");

                  //To load URL
  wv.loadUrl("https://www.billjunction.com");
  alert.setView(wv);
  alert.setIcon(R.drawable.icon);
  alert.setPositiveButton("Pay", new DialogInterface.OnClickListener(){
    public void onClick(DialogInterface dialog, int id){
        Toast.makeText(getActivity(), "Success", Toast.LENGTH_SHORT).show();
    }
  });
  alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
    public void onClick(DialogInterface dialog, int id){
       Toast.makeText(getActivity(), "Fail", Toast.LENGTH_SHORT).show();
    }
  });
  alert.show();
}

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
       
        return false;
    }
}

Monday, September 10, 2012

FTP Client for Android

If you are looking for a FTP client for Android, then the following would work for you-

1. Use Apache FTP client
http://commons.apache.org/net/download_net.cgi

you will also need
http://commons.apache.org/io/download_io.cgi

2. FTP client used in Android
A nicely written class with provision to show progress.

http://code.google.com/p/andro-ftp/source/browse/trunk/src/net/abachar/androftp/transfers/manager/FTPTransferTask.java?r=32

I am pasting the class here in case it disappears from the source-

package net.abachar.androftp.transfers.manager;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.SocketException;
import java.util.List;

import net.abachar.androftp.MainApplication;
import net.abachar.androftp.filelist.manager.FTPFileManager;
import net.abachar.androftp.servers.Logontype;

import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

/**
 *
 * @author abachar
 */
public class FTPTransferTask extends TransferTask {

        /** */
        private FTPClient mFTPClient;

        /**
         *
         */
        public FTPTransferTask(TransferTaskProgressListener progressListener, List<Transfer> transferList) {
                super(progressListener, transferList);
        }

        /**
         * @see android.os.AsyncTask#doInBackground(Params[])
         */
        @Override
        protected String doInBackground(Transfer... transfers) {

                // Create ftp client
                mFTPClient = new FTPClient();
                mFTPClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));

                String retval = super.doInBackground(transfers);

                // Disconnect ftp client
                if ((mFTPClient != null) && mFTPClient.isConnected()) {
                        try {
                                mFTPClient.logout();
                                mFTPClient.disconnect();
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
                }

                return retval;
        }

        /**
         * @see net.abachar.androftp.transfers.manager.TransferTask#doInBackgroundDownload()
         */
        @Override
        protected void doInBackgroundDownload() {

                try {
                        // Connect it if disconnected
                        if (!mFTPClient.isConnected()) {
                                connect();
                        }

                        // if (binaryTransfer) {
                        mFTPClient.setFileType(FTP.BINARY_FILE_TYPE);
                        // }

                        // Go to directory
                        if (!mFTPClient.printWorkingDirectory().equals(mCurrentTransfer.getSourcePath())) {
                                mFTPClient.changeWorkingDirectory(mCurrentTransfer.getSourcePath());
                        }

                        // Open local file
                        FileOutputStream fos = new FileOutputStream(mCurrentTransfer.getFullDestinationPath());
                        CountingOutputStream cos = new CountingOutputStream(fos) {
                                protected void beforeWrite(int n) {
                                        super.beforeWrite(n);

                                        int progress = Math.round((getCount() * 100) / mCurrentTransfer.getFileSize());
                                        mCurrentTransfer.setProgress(progress);
                                        publishProgress(mCurrentTransfer.getId(), progress);
                                }
                        };

                        // Download file
                        mFTPClient.retrieveFile(mCurrentTransfer.getName(), cos);

                        // Close local file
                        fos.close();

                        // End of transfer
                        publishProgress(mCurrentTransfer.getId(), 101);

                } catch (SocketException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        /**
         * @see net.abachar.androftp.transfers.manager.TransferTask#doInBackgroundUpload()
         */
        @Override
        protected void doInBackgroundUpload() {

                try {
                        // Connect it if disconnected
                        if (!mFTPClient.isConnected()) {
                                connect();
                        }

                        // if (binaryTransfer) {
                        mFTPClient.setFileType(FTP.BINARY_FILE_TYPE);
                        // }

                        // Open local file
                        FileInputStream fis = new FileInputStream(mCurrentTransfer.getFullSourcePath());
                        CountingInputStream cis = new CountingInputStream(fis) {
                                protected void afterRead(int n) {
                                        super.afterRead(n);

                                        int progress = Math.round((getCount() * 100) / mCurrentTransfer.getFileSize());
                                        mCurrentTransfer.setProgress(progress);
                                        publishProgress(mCurrentTransfer.getId(), progress);
                                }
                        };

                        // Go to directory
                        if (!mFTPClient.printWorkingDirectory().equals(mCurrentTransfer.getDestinationPath())) {
                                mFTPClient.changeWorkingDirectory(mCurrentTransfer.getDestinationPath());
                        }

                        // Upload file
                        mFTPClient.storeFile(mCurrentTransfer.getName(), cis);

                        // Close local file
                        fis.close();

                        // End of transfer
                        publishProgress(mCurrentTransfer.getId(), 101);

                } catch (SocketException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        /**
         * @throws IOException
         * @throws SocketException
         *
         */
        private void connect() throws SocketException, IOException {

                FTPFileManager fileManager = (FTPFileManager) MainApplication.getInstance().getServerFileManager();

                // Connect to server
                mFTPClient.connect(fileManager.getHost(), fileManager.getPort());

                // Check the reply code to verify success.
                int reply = mFTPClient.getReplyCode();
                if (!FTPReply.isPositiveCompletion(reply)) {
                        return;
                }

                if (fileManager.getLogontype() == Logontype.NORMAL) {
                        if (!mFTPClient.login(fileManager.getUsername(), fileManager.getPassword())) {
                                mFTPClient.logout();
                                return;
                        }
                }

                // Use passive mode as default because most of us are
                // behind firewalls these days.
                mFTPClient.enterLocalPassiveMode();
        }
}