I am trying to build ffmpeg for android. I want to achieve two things with it.
1. Rotate video
2. Join two or more videos.
There are two approaches for having ffmpeg in my application.
1. Having ffmpeg executable, copying it to /data/package/ and executing ffmpeg commands.
2. Build ffmpeg library .so file with ndk and write jni code etc.
Which approach is best according to my needs? And can I have some code snippets that follows those approaches?
You can achieve it by two ways, I would do it with the first one:
Place your ffmpeg file into you raw folder.
You need to use the ffmpeg executable file using commands, but you'll need to place the file into a file-system folder and change the permissions of the file, so use this code:
public static void installBinaryFromRaw(Context context, int resId, File file) {
final InputStream rawStream = context.getResources().openRawResource(resId);
final OutputStream binStream = getFileOutputStream(file);
if (rawStream != null && binStream != null) {
pipeStreams(rawStream, binStream);
try {
rawStream.close();
binStream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close streams!", e);
}
doChmod(file, 777);
}
}
public static OutputStream getFileOutputStream(File file) {
try {
return new FileOutputStream(file);
} catch (FileNotFoundException e) {
Log.e(TAG, "File not found attempting to stream file.", e);
}
return null;
}
public static void pipeStreams(InputStream is, OutputStream os) {
byte[] buffer = new byte[IO_BUFFER_SIZE];
int count;
try {
while ((count = is.read(buffer)) > 0) {
os.write(buffer, 0, count);
}
} catch (IOException e) {
Log.e(TAG, "Error writing stream.", e);
}
}
public static void doChmod(File file, int chmodValue) {
final StringBuilder sb = new StringBuilder();
sb.append("chmod");
sb.append(' ');
sb.append(chmodValue);
sb.append(' ');
sb.append(file.getAbsolutePath());
try {
Runtime.getRuntime().exec(sb.toString());
} catch (IOException e) {
Log.e(TAG, "Error performing chmod", e);
}
}
Call this method:
private void installFfmpeg() {
File ffmpegFile = new File(getCacheDir(), "ffmpeg");
String mFfmpegInstallPath = ffmpegFile.toString();
Log.d(TAG, "ffmpeg install path: " + mFfmpegInstallPath);
if (!ffmpegFile.exists()) {
try {
ffmpegFile.createNewFile();
} catch (IOException e) {
Log.e(TAG, "Failed to create new file!", e);
}
Utils.installBinaryFromRaw(this, R.raw.ffmpeg, ffmpegFile);
}else{
Log.d(TAG, "It was installed");
}
ffmpegFile.setExecutable(true);
}
Then, you will have your ffmpeg file ready to use by commands. (This way works for me but there are some people that says that it doesn't work, I don't know why, hope it isn't your case). Then, we use the ffmpeg with this code:
String command = "data/data/YOUR_PACKAGE/cache/ffmpeg" + THE_REST_OF_YOUR_COMMAND;
try {
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
Log.d(TAG, "Process finished");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
As I said, you have to use the ffmpeg file by commands, so you should search on Internet and choose the command you want to use, then, add it into the command string. If the command fails, you won't be alerted by any log, so you should try your command with a terminal emulator and be sure that it works. If it doesn´t work, you won't see any result.
Hope it's useful!!
The advantage of library approach is that you have better control over the progress of your conversion, and can tune it in the middle. One the other hand, operating the executable is a bit easier. Finally, you can simply install the ffmpeg4android app and work with their API.
Related
in the app when receiving an intent which was created from other app and has a file path, it can access the file's content using the file path.
the question is if that path (call it as 'link-path') is a 'hard link' to the original file, is it possible to find the original file through this 'link-path'?
Searched and find some post like:
https://unix.stackexchange.com/questions/122333/how-to-tell-which-file-is-original-if-hard-link-is-created
they show some unix shell command. Not sure if there is some android file system support for this, anyone having suggestion?
You can use this code I made, based on this post. It will return the target path of any path. If path is not a symbolic link, it will return itself. If path doesn't exist it returns null.
public static String findLinkTarget(String path) {
try {
Process findTarget = Runtime.getRuntime().exec("readlink -f " + path);
BufferedReader br = new BufferedReader(new InputStreamReader(findTarget.getInputStream()));
return br.readLine();
} catch (IOException e) {
Log.w(TAG, "Couldn't find target file for link: " + path, e);
}
}
The code wasn't tested, but I tested the command on Termux and it worked.
EDIT: Try calling getCanonicalPath() on your file, I think it resolves the symlink.
find a way by comparing the inode, in api >21 android has Os to get it, otherwise using the command "ls -i" to get the inode. One issue though, tested on api<=18 the "ls -i" does not return any thing (tested on emulator), in that case maybe fallback to compare the file's size and timestamp.
static String getFileInode(File file) {
String inode = "-1";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
StructStat st = null;
try {
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.parseMode("r"));
st = Os.fstat (pfd.getFileDescriptor());
if (st != null) {
inode = ""+st.st_ino;
}
} catch (Exception e) {
Log.e(TAG, "fstat() failed”+ e.getMessage());
}
} else {
BufferedReader reader = null;
try {
Process process = Runtime.getRuntime().exec(("ls -il " + path));
reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
int read;
char[] buffer = new char[4096];
StringBuffer output = new StringBuffer();
while ((read = reader.read(buffer)) > 0) {
output.append(buffer, 0, read);
}
process.waitFor();
String ret = output.toString();
if (!TextUtils.isEmpty(ret)) {
ret = ret.trim();
String[] splitArr = ret.split("\\s+");
if (splitArr.length>0) {
inode = splitArr[0];
}
}
} catch(Exception e) {
Log.e(TAG, "!!! Runtime.getRuntime().exec() exception, cmd:”+cmd);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {}
}
}
}
return inode;
}
I've been working with Azure on the Android OS and I managed to upload my video file (.mp4) to a Container I had already prepared for it.
I did this by getting a Shared Access Signature (SAS) first, which provided me with:
a temporary key
the name of the container to where I want to send the files
the server URI
Then, I started an AsyncTask to send the file to the container using the "upload".
I checked the container, and the file gets uploaded perfectly, no problems on that end.
My question is regarding the progress of the upload. Is it possible to track it? I would like to have an upload bar to give a better UX.
P.S - I'm using the Azure Mobile SDK
Here's my code:
private void uploadFile(String filename){
mFileTransferInProgress = true;
try {
Log.d("Funky Stuff", "Blob Azure Config");
final String gFilename = filename;
File file = new File(filename); // File path
String blobUri = blobServerURL + sharedAccessSignature.replaceAll("\"", "");
StorageUri storage = new StorageUri(URI.create(blobUri));
CloudBlobClient blobCLient = new CloudBlobClient(storage);
//Container name here
CloudBlobContainer container = blobCLient.getContainerReference(blobContainer);
blob = container.getBlockBlobReference(file.getName());
//fileToByteConverter is a method to convert files to a byte[]
byte[] buffer = fileToByteConverter(file);
ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer);
if (blob != null) {
new UploadFileToAzure().execute(inputStream);
}
} catch (StorageException e) {
Log.d("Funky Stuff", "StorageException: " + e.toString());
e.printStackTrace();
} catch (IOException e) {
Log.d("Funky Stuff", "IOException: " + e.toString());
e.printStackTrace();
} catch (Exception e) {
Log.d("Funky Stuff", "Exception: " + e.toString());
e.printStackTrace();
}
mFileTransferInProgress = false;
//TODO: Missing ProgressChanged method from AWS
}
private class UploadFileToAzure extends
AsyncTask <ByteArrayInputStream, Void, Void>
{
#Override
protected Void doInBackground(ByteArrayInputStream... params) {
try {
Log.d("Funky Stuff", "Entered UploadFileToAzure Async" + uploadEvent.mFilename);
//Method to upload, takes an InputStream and a size
blob.upload(params[0], params[0].available());
params[0].close();
} catch (StorageException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Thanks!
You can split your file and send its part using Block, there is a good example of your case in this link but it used C# so you should find the corresponding function in the android library reference.
Basically instead of sending you file as one big file, you split it to multiple files (bytes) and send it to azure so you can track the progress on how many bytes that already sent to azure
I want to be able to take a video recorded with an Android device and encode it to a new Resolution and Frame Rate using my app. The purpose is to upload a much smaller version of the original video (in size), since this will be videos 30 min long or more.
So far, I've read of people saying FFmpeg is they way to go. However, the documentation seems to be lacking.
I have also considered using http opencv http://opencv.org/platforms/android.html
Considering I need to manipulate the video resolution and frame rate, which tool do you think can do such things better? Are there any other technologies to consider?
An important question is, since this will be long videos, is it reasonable to do the encoding in an android device (Consider power resources, time, etc.)
Thanks in advance!
I decided to use ffmpeg to tackle this project. After much researching and trials, I was not able to build ffmpeg for library (using Ubuntu 14.04 LTS.)
However, I used this excellent library https://github.com/guardianproject/android-ffmpeg-java
I just created a project and added that library and it works like a charm. No need to build your own files or mess with the Android NDK. Of course you would still need to build the library yourself if you want to customize it. But it has everything I need.
Here is an example of how I used to lower a video resolution and change the frame rate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// input source
final Clip clip_in = new Clip("/storage/emulated/0/Developer/test.mp4");
Activity activity = (Activity) MainActivity.this;
File fileTmp = activity.getCacheDir();
File fileAppRoot = new File(activity.getApplicationInfo().dataDir);
final Clip clip_out = new Clip("/storage/emulated/0/Developer/result2.mp4");
//put flags in clip
clip_out.videoFps = "30";
clip_out.width = 480;
clip_out.height = 320;
clip_out.videoCodec = "libx264";
clip_out.audioCodec = "copy";
try {
FfmpegController fc = new FfmpegController(fileTmp, fileAppRoot);
fc.processVideo(clip_in, clip_out, false, new ShellUtils.ShellCallback() {
#Override
public void shellOut(String shellLine) {
System.out.println("MIX> " + shellLine);
}
#Override
public void processComplete(int exitValue) {
if (exitValue != 0) {
System.err.println("concat non-zero exit: " + exitValue);
Log.d("ffmpeg","Compilation error. FFmpeg failed");
Toast.makeText(MainActivity.this, "result: ffmpeg failed", Toast.LENGTH_LONG).show();
} else {
if(new File( "/storage/emulated/0/Developer/result2.mp4").exists()) {
Log.d("ffmpeg","Success file:"+ "/storage/emulated/0/Developer/result2.mp4");
}
}
}
});
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// automated try and catch
setContentView(R.layout.activity_main);
}
}
The function processVideo produces a command similar to ffmpeg -i input -s 480X320 -r 30 -vcodec libx264 -acodec copy output
This a very simple example, but it outputted the same kind of conversion done by ffmpeg desktop. This codes needs lots of work! I hope it helps anyone.
On a rooted android device, I tried to run a cat command that read kernel log, as follow:
Process p = Runtime.getRuntime().exec("su");
p = Runtime.getRuntime().exec("/system/bin/cat /proc/kmsg");
The su command was successfully executed but not the cat.
I tried to read the output of the command using getInputStream() but nothing was there, as follow:
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((read = err.read(buffer)) > 0)
{ //read error to buffer
catOutput.append(buffer, 0, read);
}
in.close();
I used the same code with ls command instead of displaying the kernel log, it worked just fine and show me the result.
I wonder if what error I am getting and wantted to see the error message on the shell when executing the cat command. Tried the p.getErrorStream() but it doesn't give me any result.
Could any one help me with this ? Thanks.
Here's a comprehensive example on how to do this - note that I got the idea from this answer:
public void catKmsg() {
Runtime runtime = Runtime.getRuntime();
Process proc = null;
OutputStreamWriter osw = null;
StringBuilder sbstdOut = new StringBuilder();
StringBuilder sbstdErr = new StringBuilder();
String command="/system/bin/cat /proc/kmsg";
try { // Run Script
proc = runtime.exec("su");
osw = new OutputStreamWriter(proc.getOutputStream());
osw.write(command);
osw.flush();
osw.close();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (osw != null) {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
if (proc != null) {
proc.waitFor();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
sbstdOut.append(ReadBufferedReader(new InputStreamReader
(proc.getInputStream())));
sbstdErr.append(ReadBufferedReader(new InputStreamReader
(proc.getErrorStream())));
if (proc.exitValue() != 0) {
}
}
I finally found the solution for the problem by using RootTools library.
Recently released (few months after my question was asked), RootTools provides a easy-to-use tool set that helps running commands that required root privilege. I created a wrapper to check if root access is available before executing shell command:
void testRootToolsCommand(String command){
if (RootTools.isRootAvailable())
toastMessage("Root is available !!!");
else {
toastMessage("NO ROOT !!! ");
return;
}
int timeOut = 1000;
try {
List<String> output = RootTools.sendShell(command,timeOut);
toastMessage("OUTPUT of the command \n" + output.toString());
} catch (RootToolsException re) {
toastMessage("Funny thing happened with RootTools!!! ");
} catch (TimeoutException te)
{
toastMessage("Timeout exception - Increase timeout !!! !!! ");
}
catch (Exception e) {
toastMessage(e.getMessage().toString());
}
}
An example of a function call is:
testRootToolsCommand("cat /proc/kmsg > /sdcard/jun11_4h51.txt");
Note: The Tool also support running multiple commands at once.
I create data file in android for my application in the app's data directory. The write is successful with no exceptions but file contents are not complete. It truncates at 90112 bytes. Any idea what is going on ? Is there a limit ?
Here is the snippet
try {
fos = parentActivity.openFileOutput(mmCacheFName,
Context.MODE_PRIVATE | Context.MODE_APPEND);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bosw = new BufferedWriter(osw);
int indx = lastIndx - 10;
while (indx >= 0) {
IEventHolder ev = deltaList.get(indx);
bosw.write(ev.getRawData() + "\n");
indx--;
}
osw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Throwable t) {
t.printStackTrace()
} finally {
try {
if (fos != null)
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
There might be lots of reasons. If you send your code, it's easier to track the problem. But you could check:
you have closed your Stream when writing to the file (It is recommended that you do it in a finally block) like this:
try{
....
write the data to the file with some outputStream
}finally{
outputStream.close();
}
If you want to read the data before closing the stream, make sure your output-stream does not buffer the output. if it does, before reading the data, flush the output to the file:
outputStream.flush();
Check for any exception that might be caught, but not logged, some code like this:
try{
...
}catch(IOException ex){
// here must log the exception.
}
}
Thanks for your responses. I figured out what the issue was, I should be flushing and closing the BufferedWriter instead of OutputStreamWriter. Thanks again