IP камера из мобильного телефона

С переходом на более современные телефоны у пользователей остаются без дела великолепные аппараты с поддержкой j2me. В данной статье я хочу предложить один из способов сделать их ещё весьма полезными.


Наступил сезон отпусков, будучи в отъезде было бы неплохо контролировать целостность своей квартиры. Или оставив машину на ночь возле дома хотелось бы иметь снимки негодяев посягнувших на Ваше имущество.

Суть задачи такова: необходимо чтобы камера мобильного телефона с определённым интервалом делала снимки и сохраняла их на веб сервере, куда пользователь по логину-паролю мог войти с любого устройства подключенного к Интернету. У плохих людей в этом случае не остаётся шанса, т.к. даже если устройство вместе с объектом будут уничтожены снимки (маркированные по времени) сохранятся на сервере и могут быть предоставлены компетентным органам.

Фактически телефон превращается в IP камеру. Данное решение состоит из двух частей : программы для мобильного телефона(J2ME) и программы на сервере(PHP MySQL). Телефонная часть рассчитана на телефоны поддерживающие J2ME. При написании использовал NetBeanse IDE.

Рассмотрим программу для телефона. программа состоит из 4 основных классов:

  • securitylCamera - основной мидлет класс
  • Fotocamera - класс работы с камерой телефона
  • MailRecord - класс работы с хранилищем
  • MessageBox - класс вывода сообщений

После старта мидлета, класс securitylCamera, программа последовательно Открывает хранилище

 mail_rec=new MailRecord();
 
MailRecord.Rec_name="my_foto";//name
 

И считывает, если есть, ID пользователя. После этого отобразает экран выбора ID или регистрации

 listRev.setCommandListener(this);

Пункты Меню позволяют добавить, удалить и исправить ID сохранённые в телефоне. В случае выбора регистрации будет запущен браузер телефона по ссылке на Ваш веб ресурс

platformRequest("http://magdelphi.comyr.com/covertcam/");

На которой будет предложено авторизироваться или пройти регистрацию. Я использовал простейшие php скрипты в связке с MySQL.

В случае регистрации на веб ресурсе будет создан временный файл cod.dat в котором прописан сгенерированный ID пользователя, для переноса которого в память хранилища телефона необходимо повторно задать регистрацию.

String scod= Dowload_Cod("http://magdelphi.comyr.com/covertcam/cod.dat");

После выбора Вашего ID откроется экран выбора интервала между снимками

 String[] stringArray={"1 сек","30 сек","1 мин","3 мин"};//, "10 мин", "30 мин"
groups=new ChoiceGroup("Задержка", ChoiceGroup.EXCLUSIVE, stringArray,null);
// list of choice groups выбора интервала
 
mMainForm=new Form("Интервал между снимками");
mMainForm.addCommand(mExitCommand);
mMainForm.append(groups);//textFieldmessage
mMainForm.append("интервал времени между снимками без учёта времени
отправки на сервер. Выбери и нажми <Далее>");
mMainForm.addCommand(selectcam);
mMainForm.setCommandListener(this);
 
 

Нажатие "Далее" выведет список режимов работы камеры.

 listRev=newList("Выбери режим фотокамеры ",List.IMPLICIT);
который заполнен значениями полученными
videoEncodings=System.getProperty("video.snapshot.encodings");
mDisplay.setCurrent(listRev);// вывести режимы съёки
Выбрав режим съёмки включаем камеру
camera=new Fotocamera();// создаём экземпляр
camera.startcamera();
camera.start();
camera.display= Display.getDisplay(this);
Display.getDisplay(this).setCurrent(camera);
 

Обращу внимание на вызов функции startcamera() класса Fotocamera

public void startcamera(){
try{
cameraParam=getType();
//определяет параметр при Manager.createPlayer, у Nokia это capture:
//image , для //остальных capture://video
mPlayer= Manager.createPlayer(cameraParam);
mPlayer.realize();
mVideoControl=(VideoControl) mPlayer.getControl("VideoControl");
mVideoControl.initDisplayMode(VideoControl.USE_DIRECT_VIDEO, this);
mVideoControl.setVisible(true);//определяет отображать или нет на экране
setFullScreenMode(true);// полноэкранный режим
mPlayer.start();
 
} catch(IOException ioe){
} catch(MediaException me){
}
}
 
public void startcamera(){
try{
cameraParam=getType();
 
// определяет параметр при Manager.createPlayer, у Nokia это capture
//image , для // остальных capture://video
mPlayer= Manager.createPlayer(cameraParam);
mPlayer.realize();
mVideoControl=(VideoControl) mPlayer.getControl("VideoControl");
mVideoControl.initDisplayMode(VideoControl.USE_DIRECT_VIDEO, this);
mVideoControl.setVisible(true);// определяет отображать или нет на экране
setFullScreenMode(true);// полноэкранный режим
mPlayer.start();
 
 
} catch(IOException ioe){
} catch(MediaException me){
}
}
 

Дальнейшая обработка событий клавиатуру происходит в функции keyPressed класса Fotocamera Нажатие "Старт" вызывается функция SendCapture(); В которой создаётся поток который через заданные промежутки времени будет делать снимки и пересылать их на сервер

//** съемка в отдельном потоке **
public void SendCapture(){
 
try{
treadCap tt=new treadCap();// создаём поток для загрузки
 
tt.setPriority(Thread.MIN_PRIORITY);
tt.start();
Thread.yield();
 
} catch(java.lang.ArithmeticException axz){
return;
}
}
 
 
 
//-------------поток для загрузки картинок --------------------------
privateclass treadCapextends Thread{
 
 
private treadCap(){
}
///******сама съёмка фото и отправка *****************
 
public void capture(){
while(bolStart){
if(initialized){
initialized=false;
try{
action=2;
byte[] raw= mVideoControl.getSnapshot(SnapshotParam);
//"encoding=" resolution SnapshotParam
 
i_proc=0;
size_image= raw.length;
procesbar=true;
mVideoControl.setDisplayLocation(1,20);
SendImag(raw,"ttt",0);//передать
 
fotocount;
} catch(MediaException me){
// System.out.println(me.toString());
}
}else{
 
sleep(50);
dt_imag= size_image messize;//размер файла картинки
 
 
if(i_proc>= dt_imag){
sleep(rtime.timefoto*1000);
initialized=true;
}
}
}
}

Сама передача снятого изображения осуществляется в отдельном потоке

//*** Выгрузка в отдельном потоке ***
public void SendImag(byte[] imag, String messag, int midlstart){
 
try{
treadIm tt=new treadIm(imag, messag, midlstart);
// создаём поток для загрузки
 
tt.setPriority(Thread.MAX_PRIORITY);
tt.start();
Thread.yield();
 
} catch(java.lang.ArithmeticException axz){
return;
}
}
//---поток для загрузки картинок ---
privateclass treadImextends Thread{
 
private String im_messag;
private byte[] treadimag;
private int starthttp;
 
private treadIm(byte[] imag, String messag, int midlstart){
treadimag= imag;
im_messag= messag;
starthttp= midlstart;
 
}----------------------
private int sendLastImage(byte[] bufer){
intcount= bufer.length;
// System.out.println(Integer.toString(count));
int ncount=(count/ dt_size);
byte[] sendIm;
for(int i=0; i< ncount1; i){
int dsize= dt_size;
if(i< ncount){
sendIm=new byte[dt_size];
}else{
dsize=count-(ncount* dt_size);
sendIm=new byte[dsize];
}
for(int n=0; n< dsize; n){
int offset=(i* dt_size) n;
sendIm[n]= bufer[offset];
}
countfile= i;
int repit=0;
boolean resout=true;
while(resout){
repit;
if(repit>3){
return1;
}
resout= sendBuffer(sendIm,
URLMail"/covertcam/" rtime.fotodir"/save.php?name=" String.valueOf(i));
}
i_proc= i_proc sendIm.length;
 
}
 
byte[] sendphp= toAnsi(mailMessage);
messize= sendphp.length;
 
sendBuffer(sendphp,
URLMail"/covertcam/" rtime.fotodir"/images/multiSave.php?tomail=" mailAddress);
i_proc= i_proc sendphp.length;
 
return0;
 
}
//--------перекодировка юникод в Ansi ---------
public byte[] toAnsi(String s){
byte[]array= s.getBytes();
 
for(int i=0, n= s.length(); i< n; i){
char c;
 
switch(c= s.charAt(i)){
case1025:
array[i]=-88;
break;
case1105:
array[i]=-72;
break;
case1168:
array[i]=-91;
break;
case1028:
array[i]=-86;
break;
case1031:
array[i]=-81;
break;
case1030:
array[i]=-78;
break;
case1110:
array[i]=-77;
break;
case1169:
array[i]=-76;
break;
case1108:
array[i]=-70;
break;
case1111:
array[i]=-65;
break;
default:
if(c>='u0410'&amp;&amp; c<='u044F'){
array[i]=(byte)((c-1040)192);
}
}
}
returnarray;
}
 
public void sendStart(){
byte[] bb=new byte[120];
sendBuffer(bb, URLMail"/covertcam/newImage.php?IdImage=" rtime.fotodir);
}
 
public void sendStop(){
byte[] bb=new byte[120];
sendBuffer(bb, URLMail"/covertcam/deleteDir.php?imagid=" rtime.fotodir);
}
//-----------------------------------------------------------------
public boolean sendBuffer(byte[] rawImag, String url){
HttpConnection connection=null;
DataOutputStream dout=null;
OutputStream os=new DataOutputStream(null);
int reps=0;
try{
connection=(HttpConnection) Connector.open(url);
if(connection==null){
returntrue;
}
connection.setRequestMethod(HttpConnection.POST);
connection.setRequestProperty("User-Agent",
"Profile/MIDP-2.0 Configuration/CLDC-1.0");
connection.setRequestProperty("Content-Type",
"application/octet-stream");
connection.setRequestProperty("Content-Length",
Integer.toString(rawImag.length));
 
dout=new DataOutputStream(connection.openDataOutputStream());
dout.write(rawImag);
 
reps= connection.getResponseCode();
// System.out.println(reps);
dout.close();
if(connection!=null){
connection.close();
}
 
if(reps!=200){
System.gc();
returntrue;
}
 
} catch(IOException e){
dout=null;
connection=null;
 
System.gc();
// System.out.println(reps);
returntrue;
// System.out.println("***********" e.getMessage());
}
System.gc();
returnfalse;
}
///------------------------------------
public synchronized void run(){
switch(starthttp){
case0:{
int ret= sendLastImage(treadimag);
if(ret==1){
MessageBox mess=new MessageBox("Ошибка",
"Отправка данных не удалась n проверь настройки GPRS n или
выбери меньшее разрешение", AlertType.ERROR);
rtime.addScreen();
}
break;
}
case1:{
sendStart();
break;
}
case2:{
sendStop();
break;
}
 
}
}
}//end tread



В приведенном коде достаточно комментариев, единственное на что хочу обратить внимание это ограничение сотовых операторов на размер передаваемого файла. В литературе говорится о 2кб.

Исходя из этого я применил метод передачи файла частями, размер задан в public static int dt_size = 1784; с последующей сборкой этих частей на сервере, с помощью скриптов на PHP.

Для поддержания неразрывного соединения для экономии трафика в классе securitylCamera определён дополнительный поток в котором с интервалом 150 сек. выполняется функция

private void secondConnect(){
SocketConnection sc=null;
try{
// os=null;
sc=null;
System.gc();
sc=(SocketConnection) Connector.open("socket://169.187.0.255:6969");
// os = sc.openOutputStream();
} catch(IOException x){
x.printStackTrace();
}
}
 
public synchronized void run(){
// secondConnect();
while(true){
timecon;
if((timecon>150)&amp;(timefoto<1800)){
timecon=0;
secondConnect();
}
timework;
try{
Thread.sleep(1000);
Thread.yield();
} catch(java.lang.InterruptedException zxz){
}
}
}


Эта функция выполняет попытку сокет соединения, которое не даёт сотовому оператору разорвать GPRS сессию, иначе при разрыве соединения оператор округляет трафик до 100 кб независимо от того сколько Вы использовали. Описывать работу серверных сценариев опишу в следующей статье, пока можете попробовать с моим веб сервисом, адреса прописаны в исходниках.

Исходнтки к этой статье прилагаю.

Часть 2. Серверная


Серверная часть комплекса написана на PHP в связке с MySQL. Для выполнения программы на сервере Вам необходимо внести изменения в файл mysql.php:

mysql_connect("сервер","имя","пароль") ordie(mysql_error());
mysql_select_db("база данных") ordie(mysql_error());


И в Вашей базе данных создать таблицу используя SQL запрос:

CREATE TABLE `users` (
`id` smallint(8) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(50) COLLATE latin1_general_ci NOT NULL DEFAULT '',
`password` varchar(32) COLLATE latin1_general_ci NOT NULL DEFAULT '',
`salt` char(3) COLLATE latin1_general_ci NOT NULL DEFAULT '',
`dir` char(6) COLLATE latin1_general_ci NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `login` (`login`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci AUTO_INCREMENT=7 ;

После запуска мидлета программа выводит на экран список (объект nameList) ваших учётных записей хранимых в памяти телефона или ссылку на сайт для регистрации или просмотра снятого (запускается браузер телефона). Если Вы выбрали ссылку на сайт, то на сервере выполняется index.php

<?php
/*
приложение к ФАКу на ПЫХЕ
http://forum.pyha.ru/
*/
 
session_start();
 
include('./auth/mysql.php');
?>
<html>
<META content="text/html; charset=UTF-8" http-equiv=Content-Type>
<head>
<title>Регистрация</title>
</head>
<body>
<?php
// если пользователь не авторизован
 
if(!isset($_SESSION['id']))
{
// то проверяем его куки
// вдруг там есть логин и пароль к нашему скрипту
 
if(isset($_COOKIE['login'])&amp;&amp;isset($_COOKIE['password']))
{
// если же такие имеются
// то пробуем авторизовать пользователя по этим логину и паролю
$login=mysql_escape_string($_COOKIE['login']);
$password=mysql_escape_string($_COOKIE['password']);
 
// и по аналогии с авторизацией через форму:
 
// делаем запрос к БД
// и ищем юзера с таким логином и паролем
 
$query="SELECT `id`
FROM `users`
WHERE `login`='{$login}' AND `password`='{$password}'
LIMIT 1";
$sql=mysql_query($query) ordie(mysql_error());
 
// если такой пользователь нашелся
if(mysql_num_rows($sql)==1)
{
// то мы ставим об этом метку в сессии (допустим мы будем ставить ID пользователя)
 
$row=mysql_fetch_assoc($sql);
$_SESSION['user_id']=$row['id'];
 
// не забываем, что для работы с сессионными данными, у нас в каждом
//скрипте должно присутствовать session_start();
}
}
}
 
 
if(isset($_SESSION['user_id']))
{
$query="SELECT `login`
FROM `users`
WHERE `id`='{$_SESSION['user_id']}'
LIMIT 1";
$sql=mysql_query($query) ordie(mysql_error());
 
// если нету такой записи с пользователем
// ну вдруг удалили его пока он лазил по сайту.. =)
// то надо ему убить ID, установленный в сессии, чтобы он был гостем
if(mysql_num_rows($sql)!=1)
{
header('Location: ./auth/login.php?logout');
exit;
}
 
$row=mysql_fetch_assoc($sql);
 
$welcome=$row['login'];
}
else
{
$welcome='гость';
}
 
print'<h3>Приветствую, '.$welcome.'.</h3>';
 
//<a href="closed.php">Закрытая страница</a><br />';
 
 
if(!isset($_SESSION['user_id']))
{
print'<a href="./auth/login.php">Авторизация</a><br />';
print'<a href="./auth/register.php">Регистрация</a><br />';
}
else
{
print'<a href="./auth/login.php?logout">Выход</a><br />';
}
if(file_exists("cod.dat"))//если cod.dat существует
{
unlink("cod.dat");//удалить файл
}
//print '<small>* Для авторизации использовать логин:
<b>md5</b> и пароль:<b>password</b></small></p>';
 
?>
</body>
 

Если Вы ещё не имеете учетной записи, то после регистрации в базу данных заносится Ваш логин и пароль и создаётся временный файл cod.dat с Вашим 6-значным идентификатором, который считывается и записывается в память телефона, после чего этот файл уничтожается. Так же Вы можете вручную внести этот идентификатор, пункт меню «добавить».

После выбора идентификатора и интервала времени между снимками выполняется функция sendStart() которая выполняет POST запрос newImage.php?IdImage=идентификатор

На сервере выполняется newImage.php который создаёт директорию под именем идентификатора и копирует туда необходимые файлы и создаёт директорию «images» для хранения Ваших снимков. В дальнейшем программа вызывает и отправляет файлы только в директорию под именем Вашего идентификатора. Тем самым обеспечивая должный уровень конфиденциальности, и возможность одновременной работы нескольких IP камер. Сборку переданных по кускам снимков выполняет multiSave.php

<?php
if(isset($GLOBALS["HTTP_RAW_POST_DATA"])){
$message=$GLOBALS["HTTP_RAW_POST_DATA"];
$tomail=$_GET["tomail"];
}
 
$buffer="";
$fototime=mktime();
for($i=0;$i<4000;++$i)
{
// Генерируем имя файла
$filename="n.part".$i;
// Если такой файл существует,
// добавляем его содержимое к $buffer
if(file_exists($filename))
{
$fp=fopen($filename,"r");
$buffer.=fread($fp,filesize($filename));
fclose($fp);
}
else
{
// Если файла с таким именем не
// существует, выходим из цикла
break;
}
// Склеенные в переменной $bufer
// части помещаем в конечный файл
$imagfile="imag".$fototime.".jpg";// ". $fototime ."
$fp=fopen($imagfile,"w");
fwrite($fp,$buffer);
fclose($fp);
}
 
/// очистка
for($i=0;$i<4000;++$i)
{
// Генерируем имя файла
$filename="n.part".$i;
// Если такой файл существует,
//удаляем его
if(file_exists($filename))
{
unlink($filename);
}
 
}
?>
 

Хочу обратить внимание на переменную которая маркирует снимки по времени $fototime = mktime(); Надо учитывать что это время на сервере, возможно несоответствие на несколько часов. Всё остальное висходниках, разбирайтесь и пользуйтесь.

2011 Альберт Мамедов magdelphi




Наши соцсети

Подписаться Facebook Подписаться Вконтакте Подписаться Twitter Подписаться Google Подписаться Telegram

Популярное

Ссылки

Новости [1] [2] [3]... Android/ iOS/ J2ME[1] [2] [3]) Android / Архив

Рейтинг@Mail.ru Яндекс.Метрика
MobiLab.ru © 2005-2018
При использовании материалов сайта ссылка на www.mobilab.ru обязательна