Android向けタイムラプスビデオの撮影

画像 タイムラプステクニックを使用して独自のフィルムを作成するプログラムを作成しましょう。 ここでISSからこのテクニックの魅惑的なビデオショットを見ることができます、説明されたプログラムを使用することができるより手頃なオプションはここにあります



このプログラムには、シンプルなインターフェースとシンプルな操作原理があります。

•ユーザーは、内蔵カメラで画像の周波数(10秒など)と生成されたビデオの目的のフレームレート(25フレーム/秒など)を設定します。

•「開始」ボタンを押した後、プログラムは10秒ごとに写真を撮影し、jpgファイルをSDカードに書き込みます。

•「停止」および「ビデオの作成」ボタンが押されるまで手順が繰り返されます。その後、写真のシーケンスがモーションJPEGビデオファイルに変換され、イベントの実際の速度よりも250倍(25 * 10)速く映像が表示されます。



このプログラムには、ユーザーと画像の蓄積に関与するMainActivityと 、画像のシーケンスをビデオファイルに変換するMJPEGGeneratorの 2つの主要なクラスがあります。



code.google.comから取得した MJPEGGeneratorクラスは、Android Javaにjava.awtパッケージがないためにわずかにやり直されました。



カメラを操作する手順は、主にAndroidのカメラの操作の資料から取られました。Androidでは、 stackoverflowのおかげで、Androidデバイスのロック/ロック解除後にカメラが「貼り付く」問題がよく説明されています。



このプログラムは、Android 4.0のPrestigio MultiPad 7.0 Primeタブレットでデバッグされました。





画像








カメラ自体に関係のない個々のプログラムコンポーネントの動作をさらに詳しく考えてみましょう。



撮影中に画面がオフにならないように、 PowerManager.WakeLockメカニズムが適用されました。



private PowerManager.WakeLock wl; public void onCreate(Bundle savedInstanceState) { ... PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen"); }
      
      







カメラを起動する頻度は、タイマーによって調整されます。



 Timer updateTimer = new Timer(); ... updateTimer = new Timer(); updateTimer.scheduleAtFixedRate(new TimerTask() { public void run() { if ((camera != null) && (workMode == 1)) { camera.takePicture(null, null, null, MainActivity.this); }} }, 0, capturePeriod * 1000);
      
      







作業を開始する前に、SDカードの存在が確認されます。



 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) Toast.makeText(MainActivity.this, "Please mount SD card", Toast.LENGTH_LONG).show();
      
      







ユーザーが入力したデータは前処理されます。小数点として入力されたコンマはピリオドに置き換えられます。 非デジタル値がチェックされます。 入力された期間とフレームレートの値は、許容範囲に入るためにチェックされます。



 periodEditText.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { if (periodEditText.getText().toString().length() == 0) capturePeriod = 0; else { if (isNum(periodEditText.getText().toString().replace(',', '.'))) { float a = Float.valueOf(periodEditText.getText().toString().replace(',', '.')); capturePeriod = (int) a; } else Toast.makeText(MainActivity.this, periodEditText.getText().toString() + " - not a digit.", Toast.LENGTH_LONG).show(); }} ... if ((fps < FPSMIN) || (fps > FPSMAX)) Toast.makeText(MainActivity.this, "FPS should be " + FPSMIN + " to " + FPSMAX + " frames per second", Toast.LENGTH_LONG).show();
      
      







作業の開始時に、前のセッション後に残っているすべての* .jpgファイルがプログラムフォルダーから削除されます。



 String sdPath = Environment.getExternalStorageDirectory().getPath() + "/TimeLapseFolder/"; File saveDir = new File(sdPath); if (saveDir.isDirectory()) { String[] children = saveDir.list(); for (int i = 0; i < children.length; i++) { if (children[i].endsWith(".jpg")) new File(saveDir, children[i]).delete(); }} saveDir.delete();
      
      







次のフレームを撮影した後、ユーザーにはマップ上の残りの場所が表示されます。



 modeText.setText("Work mode: capturing, " + String.valueOf(roundOneDecimal(megAvailable)) + " Mbyte available on SD card");
      
      







実際のビデオ生成:



 generator = new MJPEGGenerator(videofile, aviWidth, aviHeight, fps, lastPicture); for (int addpic = 1; addpic <= lastPicture; addpic++) { String numWithZeroes = intToString(addpic, 7); String curjpg = sdPath + numWithZeroes + ".jpg"; publishProgress(numWithZeroes); if (DEBUG) Log.v(TAG, "Rendering jpg sdPath = " + curjpg); Bitmap bmp = BitmapFactory.decodeFile(curjpg); generator.addImage(bmp); }
      
      







MJPEGGeneratorは、次のパラメーターで始まります。

videofile-新しいビデオファイル名が取得されるたびに、TimeLapseMovieXXX.aviマスクによって番号付けされ、以前にキャプチャされたファイルを保存します。

aviWidth、aviHeight-カメラのプロパティから取得。

fps-ユーザー定義;

lastPicture-最後に撮影した写真の番号。



ユーザーインターフェイスを中断しないために、ビデオ生成はonProgressUpdateを介してGUIと対話する別のAsyncTaskスレッドで起動されます。



キーファイルの内容を以下に示します。プロジェクトアーカイブはsourceforge.netからダウンロードできます。



MainActivity.java
 package com.sample.timelapse; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Point; import android.hardware.Camera; import android.hardware.Camera.Size; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.PowerManager; import android.os.StatFs; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity implements SurfaceHolder.Callback, View.OnClickListener, Camera.PictureCallback, Camera.PreviewCallback { private MJPEGGenerator generator; Timer updateTimer = new Timer(); // Main shoot timer private static final int PERIODMIN = 2; // Seconds private static final int PERIODMAX = 1000; // Seconds private static final int FPSMIN = 2; private static final int FPSMAX = 30; int aviHeight = 0; // Dimensions of final video int aviWidth = 0; // Work mode // 0: Ready to start // 1: Capturing photos // 2: Ready to create video // 3: Create video private int workMode = 0; private int capturePeriod = 0; private int fps = 0; private Camera camera; private SurfaceHolder surfaceHolder; private SurfaceView preview; private static int LOGLEVEL = 2; // Set logging level private static boolean DEBUG = LOGLEVEL > 1; @SuppressWarnings("unused") private static boolean WARNING = LOGLEVEL > 0; public static final String PREFS_NAME = "MyPrefsFile"; // For save and restore preferences private static final String TAG = "MainActivity"; // Set logging tag int lastPicture = 0; // Current picture counter int lastVideo = 0; // Current video file counter int sWidth = 0; // Screen width int sHeight = 0; // Screen height int prevsWidth = 1; // Previous screen width (after previous onWindowFocusChanged) int prevsHeight = 1; // Previous screen height (after previous onWindowFocusChanged) int commentTextBottom = 0; int oldLandCommentTextBottom = 0; private TextView periodText; private TextView framerateText; private TextView totalsnapshotsText; private Button startButton; // "Start capture" private Button createButton; // "Create video" int nativeButtonColor = 0; private EditText periodEditText; // Period private TextView secondsText; private EditText fpsEditText; // Frame rate private TextView fpsText; private TextView modeText; // Show comments float roundOneDecimal(float toround) { DecimalFormat twoDForm = new DecimalFormat("#.#"); return Float.valueOf(twoDForm.format(toround)); } static String intToString(int num, int digits) { assert digits > 0 : "Invalid number of digits"; char[] zeros = new char[digits]; // Create variable length array of zeros Arrays.fill(zeros, '0'); DecimalFormat df = new DecimalFormat(String.valueOf(zeros)); // Format number as String return df.format(num); } public boolean isNum(String s) { try { Double.parseDouble(s); } catch (NumberFormatException e) { return false; } return true; } private PowerManager.WakeLock wl; // Stop screen from dimming by enforcing wake lock @Override protected void onPause() { super.onPause(); // onPause method in the parent class if (DEBUG) Log.v(TAG, "onPause"); surfaceHolder.removeCallback(this); if (camera != null) { camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); camera = null; } preview.setVisibility(View.GONE); wl.release(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // onCreate method in the parent class if (DEBUG) Log.v(TAG, "onCreate"); requestWindowFeature(Window.FEATURE_NO_TITLE); // App without a title getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // App without a status bar setContentView(R.layout.activity_main); // Set user interface PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen"); periodText = (TextView) findViewById(R.id.periodText); // Text fields framerateText = (TextView) findViewById(R.id.framerateText); totalsnapshotsText = (TextView) findViewById(R.id.totalsnapshotsText); startButton = (Button) findViewById(R.id.startButton); // Start capture button startButton.setOnClickListener(this); createButton = (Button) findViewById(R.id.createButton); // Create video button createButton.setOnClickListener(this); nativeButtonColor = createButton.getCurrentTextColor(); createButton.setTextColor(Color.GRAY); periodEditText = (EditText) findViewById(R.id.periodEditText); // Period secondsText = (TextView) findViewById(R.id.secondsText); periodEditText.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { if (periodEditText.getText().toString().length() == 0) capturePeriod = 0; else { if (isNum(periodEditText.getText().toString().replace(',', '.'))) { float a = Float.valueOf(periodEditText.getText().toString().replace(',', '.')); capturePeriod = (int) a; } else Toast.makeText(MainActivity.this, periodEditText.getText().toString() + " - not a digit.", Toast.LENGTH_LONG).show(); } } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } }); fpsEditText = (EditText) findViewById(R.id.fpsEditText); // fps EditText fpsText = (TextView) findViewById(R.id.fpsText); fpsEditText.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (!hasFocus) { // Hide soft keyboard after input InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(fpsEditText.getWindowToken(), 0); } } }); fpsEditText.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { if (fpsEditText.getText().toString().length() == 0) fps = 0; else { if (isNum(fpsEditText.getText().toString().replace(',', '.'))) { float a = Float.valueOf(fpsEditText.getText().toString().replace(',', '.')); fps = (int) a; } else Toast.makeText(MainActivity.this, fpsEditText.getText().toString() + " - not a digit.", Toast.LENGTH_LONG).show(); } } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } }); modeText = (TextView) findViewById(R.id.modeText); // Show comments SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); // Restore preferences oldLandCommentTextBottom = settings.getInt("oldLandCommentTextBottom", 0); } @Override protected void onResume() { super.onResume(); // onResume method in the parent class if (DEBUG) Log.v(TAG, "onResume"); preview = (SurfaceView) findViewById(R.id.mSurfaceView); if (camera == null) { camera = Camera.open(); camera.startPreview(); } surfaceHolder = preview.getHolder(); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); surfaceHolder.setSizeFromLayout(); surfaceHolder.addCallback(this); preview.setVisibility(View.VISIBLE); wl.acquire(); Size previewSize = camera.getParameters().getPreviewSize(); aviHeight = previewSize.height; aviWidth = previewSize.width; modeText.setFocusableInTouchMode(true); // Set focus (and hide soft keyboard) modeText.requestFocus(); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (DEBUG) Log.v(TAG, "surfaceChanged"); try { camera.setPreviewDisplay(surfaceHolder); } catch (IOException e) { Toast.makeText(MainActivity.this, "Error 1: " + e.toString(), Toast.LENGTH_LONG).show(); } camera.startPreview(); } public void surfaceCreated(SurfaceHolder holder) { if (DEBUG) Log.v(TAG, "surfaceCreated"); try { camera.setPreviewDisplay(holder); camera.setPreviewCallback(this); } catch (IOException e) { Toast.makeText(MainActivity.this, "Error 2: " + e.toString(), Toast.LENGTH_LONG).show(); camera.release(); camera = null; } Size previewSize = camera.getParameters().getPreviewSize(); float aspect = (float) previewSize.width / previewSize.height; int previewSurfaceWidth = preview.getWidth(); LayoutParams lp = preview.getLayoutParams(); //     preview   ,     // camera.setDisplayOrientation(0); lp.width = previewSurfaceWidth; lp.height = (int) (previewSurfaceWidth / aspect); preview.setLayoutParams(lp); camera.startPreview(); } public void surfaceDestroyed(SurfaceHolder holder) { if (DEBUG) Log.v(TAG, "surfaceDestroyed"); } @SuppressLint("NewApi") @SuppressWarnings("deprecation") void getDisplaySize() { try { if (Build.VERSION.SDK_INT >= 13) { Display display = getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); sWidth = size.x; sHeight = size.y; } else { Display display = getWindowManager().getDefaultDisplay(); sWidth = display.getWidth(); sHeight = display.getHeight(); } } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 3: " + e.toString(), Toast.LENGTH_LONG).show(); } } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { getDisplaySize(); if ((prevsWidth != sWidth) || (prevsHeight != sHeight)) { // If orientation changed commentTextBottom = modeText.getTop() + modeText.getHeight(); // Calculate magnification factor float heightRatio = 0; // Landscape heightRatio = (float) sHeight / (float) commentTextBottom; oldLandCommentTextBottom = commentTextBottom; if (heightRatio > 1) heightRatio = 0.7f * heightRatio; else heightRatio = heightRatio / 0.7f; // Adjust fonts periodText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * periodText.getTextSize()); periodEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * periodEditText.getTextSize()); secondsText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * secondsText.getTextSize()); framerateText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * framerateText.getTextSize()); fpsEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * fpsEditText.getTextSize()); fpsText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * fpsText.getTextSize()); totalsnapshotsText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * totalsnapshotsText.getTextSize()); modeText.setTextSize(TypedValue.COMPLEX_UNIT_PX, heightRatio * modeText.getTextSize()); // Some components have text size a little less startButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0.8f * heightRatio * startButton.getTextSize()); createButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0.8f * heightRatio * createButton.getTextSize()); // If user comment string not formed if (modeText.getText().equals(getResources().getString(R.string.longestComment))) modeText.setText(getString(R.string.modeText)); } prevsWidth = sWidth; prevsHeight = sHeight; } } public void onClick(View v) { if (v == startButton) { if (workMode == 0) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) Toast.makeText(MainActivity.this, "Please mount SD card", Toast.LENGTH_LONG).show(); else if ((capturePeriod < PERIODMIN) || (capturePeriod > PERIODMAX)) Toast.makeText(MainActivity.this, "Snapshots period should be " + PERIODMIN + " to " + PERIODMAX + " seconds", Toast.LENGTH_LONG) .show(); else if ((fps < FPSMIN) || (fps > FPSMAX)) Toast.makeText(MainActivity.this, "FPS should be " + FPSMIN + " to " + FPSMAX + " frames per second", Toast.LENGTH_LONG).show(); else { if (updateTimer != null) updateTimer.cancel(); try { updateTimer = new Timer(); updateTimer.scheduleAtFixedRate(new TimerTask() { public void run() { if ((camera != null) && (workMode == 1)) { camera.takePicture(null, null, null, MainActivity.this); } } }, 0, capturePeriod * 1000); } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 4: " + e.toString(), Toast.LENGTH_LONG).show(); } // Delete all jpg's try { String sdPath = Environment.getExternalStorageDirectory().getPath() + "/TimeLapseFolder/"; if (DEBUG) Log.v(TAG, "Delete jpg's sdPath = " + sdPath); File saveDir = new File(sdPath); if (saveDir.isDirectory()) { String[] children = saveDir.list(); for (int i = 0; i < children.length; i++) { if (children[i].endsWith(".jpg")) new File(saveDir, children[i]).delete(); } } saveDir.delete(); } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 5: " + e.toString(), Toast.LENGTH_LONG).show(); } lastPicture = 0; workMode = 1; startButton.setText("Stop capture"); modeText.setText("Work mode: capturing"); totalsnapshotsText.setText("Total snapshots: " + String.valueOf(lastPicture)); } } else if (workMode == 1) { workMode = 2; createButton.setTextColor(nativeButtonColor); startButton.setText("Start capture"); startButton.setTextColor(Color.GRAY); modeText.setText("Work mode: ready to start"); } } if (v == createButton) { if (workMode == 2) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) Toast.makeText(MainActivity.this, "Please mount SD card", Toast.LENGTH_LONG).show(); else if ((capturePeriod < PERIODMIN) || (capturePeriod > PERIODMAX)) Toast.makeText(MainActivity.this, "Snapshots period should be " + PERIODMIN + " to " + PERIODMAX + " seconds", Toast.LENGTH_LONG) .show(); else if ((fps < FPSMIN) || (fps > FPSMAX)) Toast.makeText(MainActivity.this, "FPS should be " + FPSMIN + " to " + FPSMAX + " frames per second", Toast.LENGTH_LONG).show(); else { workMode = 3; createButton.setTextColor(Color.GRAY); startButton.setTextColor(Color.GRAY); modeText.setText("Work mode: create video file, please wait"); new CreateMovieInBackground().execute(); } } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // onSaveInstanceState method in the parent class if (DEBUG) Log.v(TAG, "onSaveInstanceState"); SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); SharedPreferences.Editor editor = settings.edit(); editor.putInt("oldLandCommentTextBottom", oldLandCommentTextBottom); editor.commit(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // onRestoreInstanceState method in the parent class if (DEBUG) Log.v(TAG, "onRestoreInstanceState"); } public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera) { new SaveInBackground().execute(paramArrayOfByte); if (DEBUG) Log.v(TAG, "onPictureTaken"); //  ,   ,   .    paramCamera.startPreview(); totalsnapshotsText.setText("Total snapshots: " + String.valueOf(lastPicture)); StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); long bytesAvailable = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks(); float megAvailable = bytesAvailable / (1024.f * 1024.f); modeText.setText("Work mode: capturing, " + String.valueOf(roundOneDecimal(megAvailable)) + " Mbyte available on SD card"); } class SaveInBackground extends AsyncTask<byte[], String, String> { @Override protected String doInBackground(byte[]... arrayOfByte) { try { String sdPath = Environment.getExternalStorageDirectory().getPath() + "/TimeLapseFolder/"; File saveDir = new File(sdPath); if (!saveDir.exists()) saveDir.mkdirs(); lastPicture++; String numWithZeroes = intToString(lastPicture, 7); String curjpg = sdPath + numWithZeroes + ".jpg"; if (DEBUG) Log.v(TAG, "Save jpg sdPath = " + curjpg); FileOutputStream os = new FileOutputStream(curjpg); os.write(arrayOfByte[0]); os.close(); } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 6: " + e.toString(), Toast.LENGTH_LONG).show(); } return (null); } } class CreateMovieInBackground extends AsyncTask<byte[], String, String> { protected void onProgressUpdate(String... values) { modeText.setText("Work mode: rendering " + values[0] + ".jpg"); } protected void onPostExecute(String result) { workMode = 0; totalsnapshotsText.setText("Total snapshots: 0"); lastPicture = 0; String sdPath = Environment.getExternalStorageDirectory().getPath() + "/TimeLapseFolder/"; modeText.setText("Work mode:" + sdPath + "TimeLapseMovie" + intToString(lastVideo, 3) + ".avi is rendered"); Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { modeText.setText("Work mode: ready to start"); startButton.setTextColor(nativeButtonColor); } }, 5000); } @Override protected String doInBackground(byte[]... arrayOfByte) { try { File videofile = null; String sdPath = Environment.getExternalStorageDirectory().getPath() + "/TimeLapseFolder/"; // Choosing a name for the file do { lastVideo++; String curavi = sdPath + "TimeLapseMovie" + intToString(lastVideo, 3) + ".avi"; if (DEBUG) Log.v(TAG, "AVI name = " + curavi); videofile = new File(curavi); } while (videofile.exists()); generator = new MJPEGGenerator(videofile, aviWidth, aviHeight, fps, lastPicture); for (int addpic = 1; addpic <= lastPicture; addpic++) { String numWithZeroes = intToString(addpic, 7); String curjpg = sdPath + numWithZeroes + ".jpg"; publishProgress(numWithZeroes); if (DEBUG) Log.v(TAG, "Rendering jpg sdPath = " + curjpg); Bitmap bmp = BitmapFactory.decodeFile(curjpg); generator.addImage(bmp); } // Delete all jpg's try { if (DEBUG) Log.v(TAG, "Delete jpg's sdPath = " + sdPath); File saveDir = new File(sdPath); if (saveDir.isDirectory()) { String[] children = saveDir.list(); for (int i = 0; i < children.length; i++) { if (children[i].endsWith(".jpg")) new File(saveDir, children[i]).delete(); } } saveDir.delete(); } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 7: " + e.toString(), Toast.LENGTH_LONG).show(); } generator.finishAVI(); } catch (Exception e) { Toast.makeText(MainActivity.this, "Error 8: " + e.toString(), Toast.LENGTH_LONG).show(); } return "OK"; } } public void onPreviewFrame(byte[] paramArrayOfByte, Camera paramCamera) { } }
      
      









MJPEGGenerator.java
 package com.sample.timelapse; // // MJPEGGenerator.java // // Created on April 17, 2006, 11:48 PM // // To change this template, choose Tools | Options and locate the template under // the Source Creation and Management node. Right-click the template and choose // Open. You can then make changes to the template in the Source Editor. // import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.graphics.Bitmap; // // // @author monceaux // public class MJPEGGenerator { // // Info needed for MJPEG AVI // // - size of file minus "RIFF & 4 byte file size" // int width = 0; int height = 0; double framerate = 0; int numFrames = 0; File aviFile = null; FileOutputStream aviOutput = null; FileChannel aviChannel = null; long riffOffset = 0; long aviMovieOffset = 0; AVIIndexList indexlist = null; // Creates a new instance of MJPEGGenerator public MJPEGGenerator(File aviFile, int width, int height, double framerate, int numFrames) throws Exception { this.aviFile = aviFile; this.width = width; this.height = height; this.framerate = framerate; this.numFrames = numFrames; aviOutput = new FileOutputStream(aviFile); aviChannel = aviOutput.getChannel(); RIFFHeader rh = new RIFFHeader(); aviOutput.write(rh.toBytes()); aviOutput.write(new AVIMainHeader().toBytes()); aviOutput.write(new AVIStreamList().toBytes()); aviOutput.write(new AVIStreamHeader().toBytes()); aviOutput.write(new AVIStreamFormat().toBytes()); aviOutput.write(new AVIJunk().toBytes()); aviMovieOffset = aviChannel.position(); aviOutput.write(new AVIMovieList().toBytes()); indexlist = new AVIIndexList(); } public void addImage(Bitmap image) throws Exception { byte[] fcc = new byte[] { '0', '0', 'd', 'b' }; byte[] imagedata = writeImageToBytes(image); int useLength = imagedata.length; long position = aviChannel.position(); int extra = (useLength + (int) position) % 4; if (extra > 0) useLength = useLength + extra; indexlist.addAVIIndex((int) position, useLength); aviOutput.write(fcc); aviOutput.write(intBytes(swapInt(useLength))); aviOutput.write(imagedata); if (extra > 0) { for (int i = 0; i < extra; i++) aviOutput.write(0); } imagedata = null; } public void finishAVI() throws Exception { byte[] indexlistBytes = indexlist.toBytes(); aviOutput.write(indexlistBytes); aviOutput.close(); long size = aviFile.length(); RandomAccessFile raf = new RandomAccessFile(aviFile, "rw"); raf.seek(4); raf.write(intBytes(swapInt((int) size - 8))); raf.seek(aviMovieOffset + 4); raf.write(intBytes(swapInt((int) (size - 8 - aviMovieOffset - indexlistBytes.length)))); raf.close(); } public static int swapInt(int v) { return (v >>> 24) | (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00); } public static short swapShort(short v) { return (short) ((v >>> 8) | (v << 8)); } public static byte[] intBytes(int i) { byte[] b = new byte[4]; b[0] = (byte) (i >>> 24); b[1] = (byte) ((i >>> 16) & 0x000000FF); b[2] = (byte) ((i >>> 8) & 0x000000FF); b[3] = (byte) (i & 0x000000FF); return b; } public static byte[] shortBytes(short i) { byte[] b = new byte[2]; b[0] = (byte) (i >>> 8); b[1] = (byte) (i & 0x000000FF); return b; } private class RIFFHeader { public byte[] fcc = new byte[] { 'R', 'I', 'F', 'F' }; public int fileSize = 0; public byte[] fcc2 = new byte[] { 'A', 'V', 'I', ' ' }; public byte[] fcc3 = new byte[] { 'L', 'I', 'S', 'T' }; public int listSize = 200; public byte[] fcc4 = new byte[] { 'h', 'd', 'r', 'l' }; public RIFFHeader() { } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(fileSize))); baos.write(fcc2); baos.write(fcc3); baos.write(intBytes(swapInt(listSize))); baos.write(fcc4); baos.close(); return baos.toByteArray(); } } private class AVIMainHeader { // // // FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD // dwFlags; DWORD dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD dwSuggestedBufferSize; DWORD // dwWidth; DWORD dwHeight; DWORD dwReserved[4]; // public byte[] fcc = new byte[] { 'a', 'v', 'i', 'h' }; public int cb = 56; public int dwMicroSecPerFrame = 0; // (1 // / // frames // per // sec) // * // 1,000,000 public int dwMaxBytesPerSec = 10000000; public int dwPaddingGranularity = 0; public int dwFlags = 65552; public int dwTotalFrames = 0; // replace // with // correct // value public int dwInitialFrames = 0; public int dwStreams = 1; public int dwSuggestedBufferSize = 0; public int dwWidth = 0; // replace // with // correct // value public int dwHeight = 0; // replace // with // correct // value public int[] dwReserved = new int[4]; public AVIMainHeader() { dwMicroSecPerFrame = (int) ((1.0 / framerate) * 1000000.0); dwWidth = width; dwHeight = height; dwTotalFrames = numFrames; } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); baos.write(intBytes(swapInt(dwMicroSecPerFrame))); baos.write(intBytes(swapInt(dwMaxBytesPerSec))); baos.write(intBytes(swapInt(dwPaddingGranularity))); baos.write(intBytes(swapInt(dwFlags))); baos.write(intBytes(swapInt(dwTotalFrames))); baos.write(intBytes(swapInt(dwInitialFrames))); baos.write(intBytes(swapInt(dwStreams))); baos.write(intBytes(swapInt(dwSuggestedBufferSize))); baos.write(intBytes(swapInt(dwWidth))); baos.write(intBytes(swapInt(dwHeight))); baos.write(intBytes(swapInt(dwReserved[0]))); baos.write(intBytes(swapInt(dwReserved[1]))); baos.write(intBytes(swapInt(dwReserved[2]))); baos.write(intBytes(swapInt(dwReserved[3]))); baos.close(); return baos.toByteArray(); } } private class AVIStreamList { public byte[] fcc = new byte[] { 'L', 'I', 'S', 'T' }; public int size = 124; public byte[] fcc2 = new byte[] { 's', 't', 'r', 'l' }; public AVIStreamList() { } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(size))); baos.write(fcc2); baos.close(); return baos.toByteArray(); } } private class AVIStreamHeader { // // FOURCC fcc; DWORD cb; FOURCC fccType; FOURCC fccHandler; DWORD dwFlags; WORD wPriority; WORD wLanguage; DWORD // dwInitialFrames; DWORD dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD dwSuggestedBufferSize; // DWORD dwQuality; DWORD dwSampleSize; struct { short int left; short int top; short int right; short int // bottom; } rcFrame; // public byte[] fcc = new byte[] { 's', 't', 'r', 'h' }; public int cb = 64; public byte[] fccType = new byte[] { 'v', 'i', 'd', 's' }; public byte[] fccHandler = new byte[] { 'M', 'J', 'P', 'G' }; public int dwFlags = 0; public short wPriority = 0; public short wLanguage = 0; public int dwInitialFrames = 0; public int dwScale = 0; // microseconds // per // frame public int dwRate = 1000000; // dwRate // / // dwScale // = // frame // rate public int dwStart = 0; public int dwLength = 0; // num // frames public int dwSuggestedBufferSize = 0; public int dwQuality = -1; public int dwSampleSize = 0; public int left = 0; public int top = 0; public int right = 0; public int bottom = 0; public AVIStreamHeader() { dwScale = (int) ((1.0 / framerate) * 1000000.0); dwLength = numFrames; } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); baos.write(fccType); baos.write(fccHandler); baos.write(intBytes(swapInt(dwFlags))); baos.write(shortBytes(swapShort(wPriority))); baos.write(shortBytes(swapShort(wLanguage))); baos.write(intBytes(swapInt(dwInitialFrames))); baos.write(intBytes(swapInt(dwScale))); baos.write(intBytes(swapInt(dwRate))); baos.write(intBytes(swapInt(dwStart))); baos.write(intBytes(swapInt(dwLength))); baos.write(intBytes(swapInt(dwSuggestedBufferSize))); baos.write(intBytes(swapInt(dwQuality))); baos.write(intBytes(swapInt(dwSampleSize))); baos.write(intBytes(swapInt(left))); baos.write(intBytes(swapInt(top))); baos.write(intBytes(swapInt(right))); baos.write(intBytes(swapInt(bottom))); baos.close(); return baos.toByteArray(); } } private class AVIStreamFormat { // // FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD // biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD // biClrImportant; // public byte[] fcc = new byte[] { 's', 't', 'r', 'f' }; public int cb = 40; public int biSize = 40; // same // as // cb public int biWidth = 0; public int biHeight = 0; public short biPlanes = 1; public short biBitCount = 24; public byte[] biCompression = new byte[] { 'M', 'J', 'P', 'G' }; public int biSizeImage = 0; // width // x // height // in // pixels public int biXPelsPerMeter = 0; public int biYPelsPerMeter = 0; public int biClrUsed = 0; public int biClrImportant = 0; public AVIStreamFormat() { biWidth = width; biHeight = height; biSizeImage = width * height; } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); baos.write(intBytes(swapInt(biSize))); baos.write(intBytes(swapInt(biWidth))); baos.write(intBytes(swapInt(biHeight))); baos.write(shortBytes(swapShort(biPlanes))); baos.write(shortBytes(swapShort(biBitCount))); baos.write(biCompression); baos.write(intBytes(swapInt(biSizeImage))); baos.write(intBytes(swapInt(biXPelsPerMeter))); baos.write(intBytes(swapInt(biYPelsPerMeter))); baos.write(intBytes(swapInt(biClrUsed))); baos.write(intBytes(swapInt(biClrImportant))); baos.close(); return baos.toByteArray(); } } private class AVIMovieList { public byte[] fcc = new byte[] { 'L', 'I', 'S', 'T' }; public int listSize = 0; public byte[] fcc2 = new byte[] { 'm', 'o', 'v', 'i' }; // 00db size jpg image data ... public AVIMovieList() { } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(listSize))); baos.write(fcc2); baos.close(); return baos.toByteArray(); } } private class AVIIndexList { public byte[] fcc = new byte[] { 'i', 'd', 'x', '1' }; public int cb = 0; public List ind = new ArrayList(); public AVIIndexList() { } @SuppressWarnings("unused") public void addAVIIndex(AVIIndex ai) { ind.add(ai); } public void addAVIIndex(int dwOffset, int dwSize) { ind.add(new AVIIndex(dwOffset, dwSize)); } public byte[] toBytes() throws Exception { cb = 16 * ind.size(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); for (int i = 0; i < ind.size(); i++) { AVIIndex in = (AVIIndex) ind.get(i); baos.write(in.toBytes()); } baos.close(); return baos.toByteArray(); } } private class AVIIndex { public byte[] fcc = new byte[] { '0', '0', 'd', 'b' }; public int dwFlags = 16; public int dwOffset = 0; public int dwSize = 0; public AVIIndex(int dwOffset, int dwSize) { this.dwOffset = dwOffset; this.dwSize = dwSize; } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(dwFlags))); baos.write(intBytes(swapInt(dwOffset))); baos.write(intBytes(swapInt(dwSize))); baos.close(); return baos.toByteArray(); } } private class AVIJunk { public byte[] fcc = new byte[] { 'J', 'U', 'N', 'K' }; public int size = 1808; public byte[] data = new byte[size]; public AVIJunk() { Arrays.fill(data, (byte) 0); } public byte[] toBytes() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(size))); baos.write(data); baos.close(); return baos.toByteArray(); } } private byte[] writeImageToBytes(Bitmap image) throws Exception { ByteArrayOutputStream stream = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, stream); stream.close(); return stream.toByteArray(); } }
      
      









activity_main.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/appBackgroundColor" > <TextView android:id="@+id/centerEmptyText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <SurfaceView android:id="@+id/mSurfaceView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_toRightOf="@+id/centerEmptyText" > </SurfaceView> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginRight="@dimen/baseui_horizontal_margin" android:layout_toLeftOf="@+id/centerEmptyText" android:orientation="vertical" > <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TableRow android:id="@+id/periodRow" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:id="@+id/periodText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:focusable="true" android:focusableInTouchMode="true" android:text="@string/periodText" > <requestFocus /> </TextView> <EditText android:id="@+id/periodEditText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:ems="2" android:inputType="numberDecimal" android:singleLine="true" /> <TextView android:id="@+id/secondsText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:text="@string/seconds" /> </TableRow> <TableRow android:id="@+id/fpsRow" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:id="@+id/framerateText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:focusable="true" android:focusableInTouchMode="true" android:text="@string/framerateText" /> <EditText android:id="@+id/fpsEditText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:ems="2" android:inputType="numberDecimal" android:singleLine="true" /> <TextView android:id="@+id/fpsText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:text="@string/fps" /> </TableRow> </TableLayout> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/startButton" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:layout_weight="1" android:text="@string/startButtonText" /> <Button android:id="@+id/createButton" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:layout_weight="1" android:text="@string/createButtonText" /> </LinearLayout> <TableRow android:id="@+id/totalsnapshotsRow" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:id="@+id/totalsnapshotsText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:text="@string/totalsnapshotsText" /> </TableRow> </TableLayout> <TextView android:id="@+id/modeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/baseui_horizontal_margin" android:text="@string/longestComment" /> </LinearLayout> </RelativeLayout>
      
      









AndroidManifest.xml
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sample.timelapse" android:installLocation="preferExternal" android:versionCode="8" android:versionName="0.8" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="there.isnt.a.vibrate.feature" android:required="false" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".MainActivity" android:label="@string/title_activity_main" android:screenOrientation="landscape" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
      
      









UPDDevAndrewは、プロジェクトをコンパイルするにはij.jarライブラリが必要であることに気付きました(ImageJ、MJPEGGeneratorが動作するために必要)。ライブラリは、sourceforgeのプロジェクトファイルに追加されます。



All Articles