package tw.jwzhuang.ipcam;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
import tw.jwzhuang.ipcam.h264.H264Header;
import tw.jwzhuang.ipcam.h264.H264Protocol;
import tw.jwzhuang.ipcam.h264.ParcelableByteArray;
import tw.jwzhuang.ipcam.server.StreamServer;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.media.MediaRecorder;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.ext.SatelliteMenu;
import android.view.ext.SatelliteMenuItem;
import android.widget.FrameLayout;
public class RecordService extends Service implements SurfaceHolder.Callback, MediaRecorder.OnErrorListener,
MediaRecorder.OnInfoListener{
private WindowManager wm = null;
private WindowManager.LayoutParams wmParams = null;
private View view;
private BroadcastReceiver m_br = null;
private RecordHandler recHandler = new RecordHandler();
private int startId = -1;
private final String TAG = "RecordService";
private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 };
private final byte[] EOF_MARKER = { (byte) 0xFF, (byte) 0xD9 };
private LocalSocket receiver, sender;
private LocalServerSocket lss;
private MediaRecorder mMediaRecorder = null;
private boolean mMediaRecorderRecording = false;
private SurfaceView mSurfaceView = null;
private SurfaceHolder mSurfaceHolder = null;
private boolean startRecording = false;
private List<ParcelableByteArray> bufferList = null;
private Thread t;
private H264Header h264Header = null;
// private int videoWidth = 176;
// private int videoHeight = 144;
private int videoWidth = 320;
private int videoHeight = 240;
private int videoRate = 30; //至少20張解碼才正常
private SharedPreferences sharedPreferences;
private final String mediaShare = "media";
private String fd = Environment.getExternalStorageDirectory()+"/videotest.mp4";
private final int MAXFRAMEBUFFER = 2048*videoRate;//*10;//20K
private ServiceConnection mServiceConn = null;
private StreamServer mStreamServer = null;
private int cacheBufferforStreamServer = 0;
private boolean cacheBufferforWriteFile = false;
private boolean createFile = false;
private Camera camera = null;
private Parameters mParameters = null;
private boolean isLighOn = false;
private WakeLock wl = null;
private FrameLayout fLayout = null;
@Override
public void onCreate() {
super.onCreate();
PowerManager pm = ((PowerManager)getSystemService(Context.POWER_SERVICE));
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BackLight");
sharedPreferences = this.getSharedPreferences(mediaShare, MODE_PRIVATE);
createView();
bufferList = new ArrayList<ParcelableByteArray>();
mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_camera);
SurfaceHolder holder = mSurfaceView.getHolder();
holder.addCallback(this);
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceView.setVisibility(View.VISIBLE);
initializeMenu();
initializeServiceConnection();
if(mStreamServer == null){
bindService(new Intent(this,StreamServer.class), mServiceConn, BIND_AUTO_CREATE);
}
}
private void createView() {
// 获取WindowManager
wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
// 设置LayoutParams(全局变量)相关参数
wmParams = ((MyApplication) getApplication()).getMywmParams();
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
wmParams.flags |= WindowManager.LayoutParams.FORMAT_CHANGED;
wmParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
wmParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
wmParams.gravity = Gravity.CENTER | Gravity.TOP; // 调整悬浮窗口至左上角
// 以屏幕左上角为原点,设置x、y初始值
wmParams.x = 0;
// 设置悬浮窗口长宽数据
wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
wmParams.height = WindowManager.LayoutParams.MATCH_PARENT;
wmParams.format = PixelFormat.RGBA_8888;
view = LayoutInflater.from(this).inflate(R.layout.main, null);
wm.addView(view, wmParams);
view.setVisibility(View.INVISIBLE);
view.setVisibility(View.VISIBLE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(this.startId > -1){
stopSelf(this.startId);
}
this.startId = startId;
_RegisterReceiver();
if(initializeLocalSocket() == false){
this.stopSelf(startId);
}
if (wl != null)
wl.acquire();
return super.onStartCommand(intent, Service.START_STICKY, startId);
}
@Override
public void onDestroy() {
if (wl != null){
wl.release();
}
cacheBufferforStreamServer = 0;
cacheBufferforWriteFile = false;
if(mStreamServer != null){
mStreamServer.exitStreamServer();
mStreamServer = null;
unbindService(mServiceConn);
}
if (mMediaRecorderRecording) {
h264Header = null;
stopVideoRecording();
try {
lss.close();
receiver.close();
sender.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mSurfaceView = null;
mSurfaceHolder = null;
mMediaRecorder = null;
if (t != null) {
startRecording = false;
}
if (m_br != null) {
unregisterReceiver(m_br);
m_br = null;
}
wm.removeView(view);
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 註冊動態Receiver
*/
private void _RegisterReceiver() {
m_br = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String strAction = intent.getAction();
Message msg = Message.obtain();
if(strAction.equals(IntentType.ExitApp)){
msg.obj = CMD.EXITAPP;
}else if(strAction.equals(IntentType.ClientInfo)){
msg.obj = CMD.CLIENTINFO;
}
recHandler.sendMessage(msg);
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(IntentType.ExitApp);
filter.addAction(IntentType.ClientInfo);
registerReceiver(m_br, filter, null, null);
}
private class RecordHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
CMD cmd = (CMD) msg.obj;
switch(cmd){
case EXITAPP:
stopSelf(startId);
break;
case CLIENTINFO:
Intent it = new Intent(IntentType.NewClient);
it.putExtra("clients", cacheBufferforStreamServer);
sendBroadcast(it);
break;
}
}
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
switch (what) {
case MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN:
System.out.println("MEDIA_RECORDER_INFO_UNKNOWN");
break;
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
System.out.println("MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");
break;
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
System.out.println("MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
break;
}
}
@Override
public void onError(MediaRecorder mr, int what, int extra) {
if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
System.out.println("MEDIA_RECORDER_ERROR_UNKNOWN");
this.stopSelf(startId);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// TODO Auto-generated method stub
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurfaceHolder = holder;
if (!mMediaRecorderRecording) {
loadSPSAndPPS();
initializeVideo();
startVideoRecording();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
private void stopVideoRecording() {
System.out.println("stopVideoRecording");
if (mMediaRecorderRecording || mMediaRecorder != null) {
if (t != null)
startRecording = false;
// t.interrupt();
releaseMediaRecorder();
}
}
private void startVideoRecording() {
(t = new Thread() {
public void run() {
try {
if(h264Header == null) {
Log.e(TAG, "Rlease MediaRecorder and get SPS and PPS");
Thread.sleep(3000);
//释放MediaRecorder资源
releaseMediaRecorder();
//从已采集的视频数据中获取SPS和PPS
h264Header = H264Protocol.findSPSAndPPS(fd,videoWidth,videoHeight);
if(h264Header != null){
//记录到xml文件里
String mdatStr = String.format("mdat_%d%d.mdat",videoWidth,videoHeight);
Editor editor = sharedPreferences.edit();
editor.putInt(mdatStr, h264Header.getStartMdatIndex());
editor.commit();
//save ftyp
FileOutputStream file_out = RecordService.this.openFileOutput(
String.format("%d%d.ftyp",videoWidth,videoHeight), Context.MODE_PRIVATE);
file_out.write(h264Header.getFTYP());
file_out.close();
//save sps
file_out = RecordService.this.openFileOutput(
String.format("%d%d.sps",videoWidth,videoHeight), Context.MODE_PRIVATE);
file_out.write(h264Header.getSPS());
file_out.close();
//save pps
file_out = RecordService.this.openFileOutput(
String.format("%d%d.pps",videoWidth,videoHeight), Context.MODE_PRIVATE);
file_out.write(h264Header.getPPS());
file_out.close();
}
//
//找到后重新初始化MediaRecorder
initializeVideo();
}
} catch (Exception e) {
return;
}
startRecording = true;
processData();
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(receiver.getInputStream(),MAXFRAMEBUFFER)); //BufferedInputStream 為了使用mark 與 reset
// dis.read(buffer, 0, 32); //過濾多餘的ftyp
} catch (IOException e1) {
return;
}
while (startRecording) {
try {
int h264length = dis.readInt();
//
if(h264length > 0){
dis.mark(MAXFRAMEBUFFER);
//取得mdat index
int mDatIndex = H264Protocol.findMdatIndex(dis,MAXFRAMEBUFFER);
dis.reset();
dis.skip(mDatIndex);
// byte [] mdat = new byte[4];
// dis.readFully(mdat);
// dis.reset();
dis.skip(4); //Mdat Length
}
while (startRecording && h264length > 0) {
byte [] frameData = H264Protocol.readH264Bytes(dis,MAXFRAMEBUFFER);
if(cacheBufferforStreamServer > 0 || cacheBufferforWriteFile){
if((frameData[0] & 0x1F) == 5){
bufferList = new ArrayList<ParcelableByteArray>();
for(int i=bufferList.size() -1 ;i> 0;i--){
bufferList.remove(i);
}
}
ParcelableByteArray byteArray = new ParcelableByteArray(frameData);
bufferList.add(byteArray);
}
}
} catch (IOException e) {
break;
}
}
}
}).start();
}
private void initializeMenu(){
SatelliteMenu menu = new SatelliteMenu(this);
float distance = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 170, getResources().getDisplayMetrics());
menu.setSatelliteDistance((int) distance);
menu.setExpandDuration(500);
menu.setCloseItemsOnClick(true);
menu.setTotalSpacingDegree(90);
menu.setMainImage(R.drawable.main);
List<SatelliteMenuItem> items = new ArrayList<SatelliteMenuItem>();
items.add(new SatelliteMenuItem(0, R.drawable.ic_1)); //Exit App
items.add(new SatelliteMenuItem(1, R.drawable.ic_3)); //Show Info
items.add(new SatelliteMenuItem(2, R.drawable.ic_4)); //Write File
menu.addItems(items);
menu.setOnItemClickedListener(new MenuClickedListener(this));
menu.setBackgroundColor(Color.TRANSPARENT);
int wh = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
wh, wh);
params.gravity=Gravity.BOTTOM | Gravity.LEFT;
params.leftMargin = 5;
params.bottomMargin = 5;
fLayout = (FrameLayout) view.findViewById(R.id.layout);
fLayout.addView(menu,params);
}
private boolean initializeLocalSocket(){
receiver = new LocalSocket();
try {
lss = new LocalServerSocket("VideoCamera");
receiver.connect(new LocalSocketAddress("VideoCamera"));
receiver.setReceiveBufferSize(500000);
receiver.setSendBufferSize(500000);
sender = lss.accept();
sender.setReceiveBufferSize(500000);
sender.setSendBufferSize(500000);
} catch (IOException e) {
return false;
}
return true;
}
private void initializeServiceConnection(){
mServiceConn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mStreamServer = ((StreamServer.ServiceBinder)binder).getService();
mStreamServer.setWidth(videoWidth);
mStreamServer.setHeight(videoHeight);
mStreamServer.setRate(videoRate);
mStreamServer.setB(RecordService.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, String.format("Stream Service Disconneciton %s", name.getClassName()));
}
};
}
private boolean initializeVideo() {
System.out.println("initializeVideo");
if (mSurfaceHolder == null)
return false;
mMediaRecorderRecording = true;
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
else
mMediaRecorder.reset();
camera = Camera.open();
if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
camera.setDisplayOrientation(90);
else
camera.setDisplayOrientation(0);
camera.unlock();
mMediaRecorder.setCamera(camera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
//mediaRecorder.setProfile(camcorderProfile);//构造CamcorderProfile,使用高质量视频录制
mMediaRecorder.setVideoFrameRate(videoRate);
mMediaRecorder.setVideoSize(videoWidth, videoHeight);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
mMediaRecorder.setMaxDuration(0);
mMediaRecorder.setMaxFileSize(0);
if(h264Header == null)
{
Log.e(TAG, "============== SPS is null!!!!!!!!!!");
try {
File file = new File(fd);
if (file.exists())
file.delete();
} catch (Exception ex) {
Log.v("System.out", ex.toString());
}
mMediaRecorder.setOutputFile(fd);
}
else
{
Log.e(TAG,"=============== SPS have value!!!!!!!");
mMediaRecorder.setOutputFile(sender.getFileDescriptor());
}
try {
mMediaRecorder.setOnInfoListener(this);
mMediaRecorder.setOnErrorListener(this);
mMediaRecorder.prepare();
mMediaRecorder.start();
} catch (IOException exception) {
releaseMediaRecorder();
stopSelf(startId);
return false;
}
return true;
}
private void releaseCamera() throws IOException{
if(camera == null){
return;
}
mParameters = camera.getParameters();
if(mParameters.getFlashMode() == Camera.Parameters.FLASH_MODE_TORCH){
mParameters.setFlashMode(Parameters.FLASH_MODE_OFF);
camera.setParameters(mParameters);
}
camera.reconnect();
camera.stopPreview();
camera.release();
camera = null;
}
private void releaseMediaRecorder() {
System.out.println("Releasing media recorder.");
if (mMediaRecorder != null) {
if (mMediaRecorderRecording) {
try {
System.out.println("Releasing media recorder2");
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setOnInfoListener(null);
mMediaRecorder.stop();
releaseCamera();
} catch (RuntimeException e) {
System.out.println("stop fail: " + e.getMessage());
} catch (IOException e) {
System.out.println("stop fail: " + e.getMessage());
}
mMediaRecorderRecording = false;
}
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
}
}
public byte[] getSPS(){
return h264Header.getSPS();
}
public byte[] getPPS(){
return h264Header.getPPS();
}
// 得到序列参数集SPS和图像参数集PPS,如果已经存储在本地
private void loadSPSAndPPS() {
int mdatIndex = sharedPreferences.getInt(
String.format("mdat_%d%d.mdat", videoWidth, videoHeight), -1);
if (mdatIndex != -1) {
h264Header = new H264Header();
h264Header.setStartMdatIndex(mdatIndex);
byte[] temp = new byte[100];
try {
FileInputStream file_in = openFileInput(String.format("%d%d.ftyp", videoWidth, videoHeight));
int index = 0;
int read = 0;
while (true) {
read = file_in.read(temp, index, 10);
if (read == -1)
break;
else
index += read;
}
Log.e(TAG, "=====get ftyp length:" + index);
h264Header.setFTYP(temp,index);
file_in.close();
file_in = openFileInput(String.format("%d%d.sps", videoWidth, videoHeight));
index = 0;
while (true) {
read = file_in.read(temp, index, 10);
if (read == -1)
break;
else
index += read;
}
Log.e(TAG, "=====get sps length:" + index);
h264Header.setSPS(temp, index);
file_in.close();
index = 0;
// read PPS
file_in = openFileInput(String.format("%d%d.pps", videoWidth, videoHeight));
while (true) {
read = file_in.read(temp, index, 10);
if (read == -1)
break;
else
index += read;
}
Log.e(TAG, "==========get pps length:" + index);
h264Header.setPPS(temp, index);
} catch (FileNotFoundException e) {
Log.e(TAG, e.toString());
} catch (IOException e) {
Log.e(TAG, e.toString());
}
} else {
Log.e(TAG, "==============StartMdatPlace = -1");
h264Header = null;
}
}
public void startCacheBuf_StreamServer(int i){
cacheBufferforStreamServer += i;
if(cacheBufferforStreamServer < 0){
return;
}
Intent it = new Intent(IntentType.NewClient);
it.putExtra("clients", cacheBufferforStreamServer);
sendBroadcast(it);
if(cacheBufferforStreamServer < 1){
return;
}
byte[] dd = ArrayUtils.addAll(SOI_MARKER, null);
dd = ArrayUtils.addAll(dd,h264Header.getHead());
dd = ArrayUtils.addAll(dd,h264Header.getSPS());
dd = ArrayUtils.addAll(dd,EOF_MARKER);
mStreamServer.setBuffer(dd);
dd = ArrayUtils.addAll(SOI_MARKER, null);
dd = ArrayUtils.addAll(dd,h264Header.getHead());
dd = ArrayUtils.addAll(dd,h264Header.getPPS());
dd = ArrayUtils.addAll(dd,EOF_MARKER);
mStreamServer.setBuffer(dd);
}
public void startCacheBuf_WriteFile(boolean b){
cacheBufferforWriteFile = b;
createFile = b;
}
public void processData(){
new Thread(){
@Override
public void run() {
super.run();
try {
FileOutputStream fos = null;
while(startRecording){
if(bufferList.size() > 0){
if(cacheBufferforWriteFile){
if(createFile){
String name = Environment.getExternalStorageDirectory() + "/my.h264";//TODO changefile
fos = new FileOutputStream(name);
// fos.write(h264Header.getFTYP());
fos.write(h264Header.getHead());
fos.write(h264Header.getSPS());
fos.write(h264Header.getHead());
fos.write(h264Header.getPPS());
createFile = false;
}
fos.write(h264Header.getHead());
fos.write(bufferList.get(0).get_byte());
}
if(cacheBufferforStreamServer > 0 && mStreamServer != null){
byte[] dd = ArrayUtils.addAll(SOI_MARKER, null);
dd = ArrayUtils.addAll(dd,h264Header.getHead());
dd = ArrayUtils.addAll(dd,copyLastBufferData());
dd = ArrayUtils.addAll(dd,EOF_MARKER);
mStreamServer.setBuffer(dd);
}
bufferList.remove(0);
// bufferList.clear();
}
}
if(fos != null){
fos.close();
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
} finally{
bufferList.clear();
}
}
}.start();
}
private byte[] copyLastBufferData(){
// return ArrayUtils.addAll(bufferList.get(0).get_byte(), null);
return ArrayUtils.addAll(bufferList.get(0).get_byte(), null);
// return ArrayUtils.addAll(bufferList.get(bufferList.size()-1).get_byte(), null);
}
public void flashLight() {
if (camera == null) {
return;
}
mParameters = camera.getParameters();
if (isLighOn) {
Log.i("info", "torch is turn off!");
mParameters.setFlashMode(Parameters.FLASH_MODE_OFF);
camera.setParameters(mParameters);
isLighOn = false;
} else {
Log.i("info", "torch is turn on1");
mParameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
Log.i("info", "torch is turn on2");
camera.setParameters(mParameters);
Log.i("info", "torch is turn on3");
isLighOn = true;
}
}
}