Hi! Hello... Oh well this is the first post so noone probably read this anyway =)
Anyway, let's get down to business ;)
I am working on a small project with a friend and we hit a small huddle- converting series of JPEG to movie...
So of course, the first logical thing one would think to make a movie out of JPEGs images is to somehow stitch them one by one after each other for each frame. For that, QuickTime Mov is the easiest way to get things done.
I have found many libraries or other people's code but to be honest, I didn't really know or understand what i was looking at so i read up a bit on the mov file structure.
This Url will explain to you exactly what a mov file contains:
http://wiki.multimedia.cx/index.php?title=QuickTime_container
Easy enough? Now let's have a look at a simple mov file that was generated by someone else's code:
After looking at the hexdump of this mov file and others with more than 1 frame, i have noted down the data atoms that are the same and the one that changed such as frame count, information regarding start position of each frame, size of each frame, etc... So I put together the following ugly code as a Proof of Concept code to make a mov file out of jpegs. I'm not a programmer and more of a script-coder so this haxor job seems a bit ugly but it may help you to understand how to create a mov file out of series of JPEGs. Next is to convert this into proper classes with proper functions. plz excuse my poor coding =)
package bomblah.imageToMov;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.os.Environment;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
public class ImageToMovActivity extends Activity {
/** Called when the activity is first created. */
public ByteArrayOutputStream returnStream = new ByteArrayOutputStream();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String rootPath = Environment.getExternalStorageDirectory().toString();
int frameCount = 1;
byte startOfMov[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x14, (byte)0x66, (byte)0x74,
(byte)0x79, (byte)0x70, (byte)0x71, (byte)0x74, (byte)0x20, (byte)0x20,
(byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00, (byte)0x71, (byte)0x74,
(byte)0x20, (byte)0x20, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08,
(byte)0x77, (byte)0x69, (byte)0x64, (byte)0x65
};
int sizeOfMdata= 0;//SIZE of mdata (Size of all JPEG + 8)
String mdat = "mdat";
//JPEG1
//JPEG2
int sizeOfMoov = 0;
String moov = "moov";
byte mvhd[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6C, (byte)0x6D, (byte)0x76,
(byte)0x68, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x45, (byte)0xD8, (byte)0x56, (byte)0x11, (byte)0x45, (byte)0xD8,
(byte)0x56, (byte)0x11, (byte)0x00, (byte)0x00, (byte)0x03, (byte)0xE8,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x64, (byte)0x00, (byte)0x01,
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02
};
int sizeOfTrak = 0;
String trak = "trak";
byte sizeOfhtkd[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x5C
};
String tkhd = "tkhd";
byte dateTime[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x45, (byte)0xD8,
(byte)0x56, (byte)0x11, (byte)0x45, (byte)0xD8, (byte)0x56, (byte)0x11,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00
};
int duration = frameCount*100;
byte continueOfTkhd[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x0A, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x40, (byte)0x00
};
int movieWidth = 0;
int movieHeight = 0;
byte restOfTkhd[] = {
(byte)0x00, (byte)0x00
};
int sizeOfMdia = 0;
String mdia = "mdia";
int sizeOfMdhd = 32;
String mdhd = "mdhd";
byte beginMdhd[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x45, (byte)0xD8,
(byte)0x56, (byte)0x11, (byte)0x45, (byte)0xD8, (byte)0x56, (byte)0x11,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0A
};
//frameCount here
byte restOfMdhd[] = {
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00
};
int sizeOfHdlr = 44;
String hdlr = "hdlr";
byte restOfHdlr[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6D, (byte)0x68,
(byte)0x6C, (byte)0x72, (byte)0x76, (byte)0x69, (byte)0x64, (byte)0x65,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x56, (byte)0x69, (byte)0x64, (byte)0x65, (byte)0x6F, (byte)0x48,
(byte)0x61, (byte)0x6E, (byte)0x64, (byte)0x6C, (byte)0x65, (byte)0x72
};
int sizeOfMinf = 0;
String minf = "minf";
byte beginOfMinf[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x14, (byte)0x76, (byte)0x6D,
(byte)0x68, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x2C,
(byte)0x68, (byte)0x64, (byte)0x6C, (byte)0x72, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x6D, (byte)0x68, (byte)0x6C, (byte)0x72,
(byte)0x76, (byte)0x69, (byte)0x64, (byte)0x65, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x56, (byte)0x69,
(byte)0x64, (byte)0x65, (byte)0x6F, (byte)0x48, (byte)0x61, (byte)0x6E,
(byte)0x64, (byte)0x6C, (byte)0x65, (byte)0x72, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x28, (byte)0x64, (byte)0x69, (byte)0x6E, (byte)0x66,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x20, (byte)0x64, (byte)0x72,
(byte)0x65, (byte)0x66, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x10, (byte)0x75, (byte)0x72, (byte)0x6C, (byte)0x20,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00
};
int sizeOfStbl = 0;
String stbl = "stbl";
byte beginOfStbl[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x66, (byte)0x73, (byte)0x74,
(byte)0x73, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x56, (byte)0x6A, (byte)0x70, (byte)0x65, (byte)0x67,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x63, (byte)0x68, (byte)0x61, (byte)0x64, (byte)0x00, (byte)0x00,
(byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00,
(byte)0x00, (byte)0xB0, (byte)0x00, (byte)0x90, (byte)0x00, (byte)0x48,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x48, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00
};
//frameCount here
byte continueOfStbl1[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x18, (byte)0xFF, (byte)0xFF,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x18, (byte)0x73, (byte)0x74,
(byte)0x74, (byte)0x73, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01
};
//frameCount here
byte continueOfStbl2[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x1C, (byte)0x73, (byte)0x74, (byte)0x73, (byte)0x63,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x01
};
int SizeOfStsz = 0;
String stsz = "stsz";
byte beginOfStsz[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00
};
//frameCount here
//sizeOfJPEG1
//sizeOfJPEG2
//sizeOfJPEG3
int sizeOfStco = 0;
String stco = "stco";
byte beginOfStco[] = {
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
};
//frameCount
int firstOffset = 36;
//for next offset = 28+sizeOfJPEG1;
int totalSizeOfJPEG = 0;
String[] files = new String[3];
files[0]="/mnt/sdcard/freezmo/20111211012629.jpg";
files[1]="/mnt/sdcard/freezmo/20111211012639.jpg";
files[2]="/mnt/sdcard/freezmo/20111211012644.jpg";
frameCount = 2;
try{
ByteArrayOutputStream[] tempStream = new ByteArrayOutputStream[frameCount];
int[] sizeStream = new int[frameCount];
// Bitmap[] bitmapArray = new Bitmap[frameCount];
for (int i = 0; i<frameCount;i++){
tempStream[i]=new ByteArrayOutputStream(); //initialise tempStreams
// bitmapArray[i]=new Bitmap();
Bitmap a= BitmapFactory.decodeFile(files[i]);
movieWidth = a.getWidth();
movieHeight = a.getHeight();
a.compress(Bitmap.CompressFormat.JPEG, 100, tempStream[i]);
int len = getStreamLength(tempStream[i]);
sizeStream[i] = len;
totalSizeOfJPEG += len;
}
// printf("totalSizeOfJPEG="+Integer.toString(totalSizeOfJPEG),this);
returnStream.write(startOfMov);
returnStream.write(intToByteArray(totalSizeOfJPEG+8));
returnStream.write(mdat.getBytes());
for (int i=0;i<frameCount;i++){
returnStream.write(tempStream[i].toByteArray());
}
//recursive would be nice here. Will do it later =p This is just a haxor job to test
ByteArrayOutputStream moovStream = new ByteArrayOutputStream();
moovStream.write(moov.getBytes());
moovStream.write(mvhd);
ByteArrayOutputStream trakStream = new ByteArrayOutputStream();
trakStream.write(trak.getBytes());
trakStream.write(intToByteArray(92));
trakStream.write(tkhd.getBytes());
trakStream.write(dateTime);
trakStream.write(intToByteArray(frameCount*100));
trakStream.write(continueOfTkhd);
trakStream.write(intToByteArray(movieWidth));
trakStream.write(intToByteArray(movieHeight));
trakStream.write(restOfTkhd);
ByteArrayOutputStream mdiaStream = new ByteArrayOutputStream();
mdiaStream.write(mdia.getBytes());
mdiaStream.write(intToByteArray(sizeOfMdhd));
mdiaStream.write(mdhd.getBytes());
mdiaStream.write(beginMdhd);
mdiaStream.write(intToByteArray(frameCount));
mdiaStream.write(restOfMdhd);
mdiaStream.write(intToByteArray(sizeOfHdlr));
mdiaStream.write(hdlr.getBytes());
mdiaStream.write(restOfHdlr);
ByteArrayOutputStream minfStream = new ByteArrayOutputStream();
minfStream.write(minf.getBytes());
minfStream.write(beginOfMinf);
ByteArrayOutputStream stblStream = new ByteArrayOutputStream();
stblStream.write(stbl.getBytes());
stblStream.write(beginOfStbl);
stblStream.write(intToByteArray(frameCount));
stblStream.write(continueOfStbl1);
stblStream.write(intToByteArray(frameCount));
stblStream.write(continueOfStbl2);
ByteArrayOutputStream stszStream = new ByteArrayOutputStream();
stszStream.write(stsz.getBytes());
stszStream.write(beginOfStsz);
stszStream.write(intToByteArray(frameCount));
for (int i=0;i<frameCount;i++){
stszStream.write(intToByteArray(sizeStream[i]));
}
stblStream.write(intToByteArray(getStreamLength(stszStream)+4));
stblStream.write(stszStream.toByteArray());
ByteArrayOutputStream stcoStream = new ByteArrayOutputStream();
stcoStream.write(stco.getBytes());
stcoStream.write(beginOfStco);
stcoStream.write(intToByteArray(frameCount));
stcoStream.write(intToByteArray(firstOffset));
int offSet=firstOffset;
for (int i=1;i<frameCount;i++){
stcoStream.write(intToByteArray(offSet+sizeStream[i]));
offSet+=sizeStream[i];
}
stblStream.write(intToByteArray(getStreamLength(stcoStream)+4));
stblStream.write(stcoStream.toByteArray());
minfStream.write(intToByteArray(getStreamLength(stblStream)+4));
minfStream.write(stblStream.toByteArray());
//writeSizeOfMinf
mdiaStream.write(intToByteArray(getStreamLength(minfStream)+4));
mdiaStream.write(minfStream.toByteArray());
trakStream.write(intToByteArray(getStreamLength(mdiaStream)+4));
trakStream.write(mdiaStream.toByteArray());
moovStream.write(intToByteArray(getStreamLength(trakStream)+4));
moovStream.write(trakStream.toByteArray());
returnStream.write(intToByteArray(getStreamLength(moovStream)+4));
returnStream.write(moovStream.toByteArray());
// bitmapArray[0] = BitmapFactory.decodeFile("/mnt/sdcard/freezmo/19700101100000.jpg");
//new File(rootPath + "/freezmo").mkdirs();
printf("finished returnStream",this);
File file = new File(rootPath, "/StopMotion/freezmo.mov");
OutputStream outStream = new FileOutputStream(file);
outStream.write(returnStream.toByteArray());
outStream.flush();
outStream.close();
}catch (Exception e) {
}
}
public byte[] intToByteArray(int value) {
return new byte[]{
(byte)(value >>> 24), (byte)(value >> 16 & 0xff), (byte)(value >> 8 & 0xff), (byte)(value & 0xff) };
}
public int getStreamLength(ByteArrayOutputStream stream){
return stream.toByteArray().length;
}
public void printf(String alertText, Activity activity) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(alertText).setNeutralButton("Close", null);
AlertDialog alert = builder.create();
alert.show();
}
}

No comments:
Post a Comment