/* * AnBox, and an Android Blackbox application for the have-not-so-much-money's * Copyright (C) 2010 Yoonsoo Kim, Heekuk Lee, Heejin Sohn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.ivehicle.AnBox; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; import com.ivehicle.util.Log; import android.app.Service; import android.content.ContentValues; import android.content.Intent; import android.net.Uri; import android.os.IBinder; import android.provider.MediaStore; import android.provider.MediaStore.Video; import android.text.format.DateFormat; public class RecordingServer extends Service { public static final String TAG = "RECSRV"; public static final String RECORDING_SERVICE = "com.ivehicle.AnBox.RecordingServer.SERVICE"; public static final String IPADDR = "127.0.0.1"; public static final int PORT = 12345; private ServerSocket sock = null; private String videoFileName = null; private ContentValues videoValues = null; private long recordingStarted = 0; private static final int NOT_MATCHING = 0; private static final int PAT_MATCHING = 1; private static final int PAT_MATCHED = 2; private static final byte[] ftypePat = { 0x00, 0x00, 0x00, 0x1c, 0x66, 0x74, 0x79, 0x70 }; // private static final byte[] moviePat = // { 0x6d, 0x6f, 0x6f, 0x76 }; private static String createName(long dateTaken) { return DateFormat.format(Config.FILE_NAME_FORMAT, dateTaken).toString(); } private void createVideoPath() { recordingStarted = System.currentTimeMillis(); String title = createName(recordingStarted); String displayName = title + ".3gp"; File cameraDir = new File(Config.getDataDir()); cameraDir.mkdirs(); SimpleDateFormat dateFormat = new SimpleDateFormat(Config.FILE_NAME_FORMAT); Date date = new Date(recordingStarted); String filepart = dateFormat.format(date); String filename = Config.getDataDir() + "/" + filepart + ".3gp"; ContentValues values = new ContentValues(8); values.put(Video.Media.TITLE, title); values.put(Video.Media.DISPLAY_NAME, displayName); values.put(Video.Media.DATE_TAKEN, recordingStarted); values.put(Video.Media.MIME_TYPE, "video/3gpp"); values.put(Video.Media.DATA, filename); videoFileName = filename; Log.v(TAG, "Current camera video filename: " + videoFileName); videoValues = values; } private class DataHandler implements Runnable { public void run() { while (true) { Socket acceptedSock = null; try { acceptedSock = sock.accept(); } catch (Exception e) { Log.e(TAG, e.toString()); } createVideoPath(); storeData(acceptedSock); } } private int searchPattern(byte[] buf, int startPos, int len, byte[] pat) { int posBuf = startPos; int posPat = 0; int start = 0; int state = NOT_MATCHING; while (posBuf < len - 1) { if (buf[posBuf] == pat[posPat]) { ++posPat; switch (state) { case NOT_MATCHING: start = posBuf; state = PAT_MATCHING; break; case PAT_MATCHING: if (posPat == pat.length) state = PAT_MATCHED; break; } } else { posPat = 0; start = 0; state = NOT_MATCHING; } if (state == PAT_MATCHED) { return start; } ++posBuf; } return -1; } private byte[] ftypeHeader = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; private void rememberFtypeHeader(byte[] buf, int pos) { int cnt = 0; while (cnt < 28) { ftypeHeader[cnt] = buf[pos + cnt]; ++cnt; } ftypeHeader[cnt++] = buf[pos-4]; ftypeHeader[cnt++] = buf[pos-3]; ftypeHeader[cnt++] = buf[pos-2]; ftypeHeader[cnt] = buf[pos-1]; } private void storeData(Socket s) { File store = null; FileOutputStream fos = null; InputStream is = null; try { store = new File(videoFileName); fos = new FileOutputStream(store); is = s.getInputStream(); byte[] buf = new byte[4096]; int len = 0; while ((len = is.read(buf)) != -1) { int ftypeHeaderOffset = -1; Log.v(TAG, "data received = " + len + " bytes"); if ((ftypeHeaderOffset = searchPattern(buf, 0, len, ftypePat)) >= 0) { Log.d(TAG, "ftypePat found at pos " + ftypeHeaderOffset); rememberFtypeHeader(buf, ftypeHeaderOffset); } if (ftypeHeaderOffset >= 0) { fos.write(buf, 0, ftypeHeaderOffset - 4); fos.write(buf, ftypeHeaderOffset + 28, len - ftypeHeaderOffset - 28); } else { fos.write(buf, 0, len); } fos.flush(); Log.v(TAG, "data written"); } fos.flush(); fos.close(); s.close(); Log.v(TAG, "constructing header info..."); RandomAccessFile raf = new RandomAccessFile(store, "rw"); raf.seek(0); raf.write(ftypeHeader); Log.d(TAG, "wrote ftype header = " + ftypeHeader.toString()); raf.close(); raf = null; videoValues.put(Video.Media.DURATION, System.currentTimeMillis() - recordingStarted); videoValues.put(Video.Media.SIZE, new File(videoFileName).length()); Uri videoUri = getContentResolver().insert( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, videoValues ); if (videoUri == null) { Log.d(TAG, "Content resolver failed"); return; } Log.d(TAG, "Video URI = " + videoUri.getPath()); videoValues = null; // Force Media scanner to refresh now. Technically, this is // unnecessary, as the media scanner will run periodically but // helpful for testing. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, videoUri)); } catch (IOException e) { Log.e(TAG, e.toString()); } finally { if (fos != null) fos = null; if (store != null) store = null; if (s != null) s = null; } } } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } @Override public void onCreate() { super.onCreate(); try { sock = new ServerSocket(PORT); } catch (Exception e) { Log.e(TAG, e.toString()); } } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); new Thread(new DataHandler()).start(); } @Override public void onDestroy() { try { sock.close(); } catch (IOException e) { Log.e(TAG, e.toString()); } super.onDestroy(); } }