10 августа 2010 г.

Cтруктура mpeg4

MP4Box неправильно прописывал некоторые параметры субтитров при конвертировании видео для iPhone (статья). Можно было использовать Dumpster Atom Inspector для ручного редактирования нужных полей, но я хотел написать скрипт который делает все автоматически.

Файл формата MPEG4 (будь то .mov, .mp4 или .m4v) состоит из блоков, называемых атомами. Каждый атом имеет формат:
- байты 0-3 - длинна атома (включая эти 4 байта)
- байты 4-7 - имя атома (четырехсимвольная строка, выделенная строка на рисунке)
- байты 8-… - данные атома



Вот из таких атомов и состоит MPEG4 файл. В каждом видео файле должны присутствовать атомы с именами "ftyp", "moov" и "mdat".

В "ftyp" содержится тип этого файла и типы версий основных структур файла. Этот атом всегда первый в файле. Атом "mdat" содержит потоки видео, аудио, субтитров. Атом "moov" для меня был самым интересным, так как он содержит описание треков, информацию о тегах.

Данные атома "moov" (то что начинается с 8-го байта) имеют древовидную структуру состоящих из блоков такого-же формата что и атом (т.е. 4 байта - размер блока, 4-байта - имя блока, с 8-го байта - данные). Формат блока отределяет эго имя, например блок с именем "mvhd" занимает 108 байт, и понять, что там записано не составит труда посмотрев на содержимое этой секции в Atom Inspector.



Кстати о размере блоков, как я понимаю они записаны в "человеческом" виде, т.е. чем байт старше, тем он левее, в компьютере числа представлены в "перевернутом" виде - чем старше байт, тем он правее. Учтите это при получении размеров атомов.

Так же в атоме "moov" -> "udata" -> "meta" содержится информация о тегах iTunes (обложка фильма включительно). Каждый тег в этом блоке так-же представлен блоком формата атом.

Скрипт на Python который печатает структуру MPEG4 файла.

7 августа 2010 г.

Информация о сети

В iOS 4.0 Apple дала возможность разработчикам получить информацию о сети в которой находится телефон, а так-же о текущих звонках. Итак, фраемворк CoreTelephony. Он представлен всего несколькими классами, но из них можно вынести интересную информацию. Класс CTCarrier содержит информацию о сети, CTCall - о текущем звонке. Подписаться на получение изменений информации о звонках и изменении сети можно зарегистрировав слушателей subscriberCellularProviderDidUpdateNotifier и callEventHandler в классах CTTelephonyNetworkInfo и CTCallCenter.

Простой пример

4 августа 2010 г.

Local Notifications в iOS 4.0

Начиная с iOS SDK 4.0 появилась возможность отсылать локальные сообщения. Локальные сообщения - это сообщения пользователю от вашей программы в определенное время, не зависимо от того, запущено ли ваше приложение. Очень похоже на Push Notification но действует только в пределах одного устройства (без участия севера). Как и Push Notification Local Notification может быть доставлен пользователю как: звуковое сообщение, цифра на иконке приложения и как алерт. Не думайте, что при посылке Local Notification ваше приложение запустится, это произойдет только в том случае, если ваше Local Notification имеет тип алерт и пользователь на этом всплывающем окне нажал кнопку "View". Так что никакой важной информации не стоит доставлять через Local Notification, хотя эти сообщения и надежнее чем Push Notification так как не зависят от состояния сети.

Регистрация в системе Local Notification осуществляет метод scheduleLocalNotification: класса UIApplication. Параметром этого метода служит объект класса UILocalNotification. Код который осуществляет отсылку сообщения:

  1. UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
  2.  if (alarm)
  3.  {
  4.   alarm.fireDate = [[NSDate date] dateByAddingTimeInterval:30.0];
  5.   alarm.timeZone = [NSTimeZone defaultTimeZone];
  6.   alarm.repeatInterval = 0;
  7.   alarm.alertBody = @"local notification";
  8.   UIApplication *app = [UIApplication sharedApplication];
  9.   [app scheduleLocalNotification:alarm];
  10.  }

Этот код создает объект alarm типа alert c текстом "local notification" и регистрирует его в системе как событие, которое произойдет через 30 сек.

Посмотреть зарегистрированные в системе от вашего приложении нотификации позволяет метод scheduledLocalNotifications. Отменить все нотификации можно методом cancelAllLocalNotifications.

Что э происходит если событие нотификации происходит при запущенном приложении? Системных событий тогда никаких не происходит (не звуков не проигрывается, не показывается всплывающее окно), но делегату вашего приложения приходит событие application:didReceiveLocalNotification: в котором можно обработать сообщение.

Пример работы.

31 июля 2010 г.

Конвертирование видео для iPhone/iPod Touch/ iPad с несколькими звуковыми дорожками и субтитрами

Конвертировать будем в систем Linux. Используемые утилиты:
ffmpeg - получение информации о фильме, извлечение дорожек, декодирование
mkvtools - получение информации о фильме, извлечение дорожек
MP4Box - собирание дорожек в m4v в файл
AtomicParsley - пропитывание тэгов

Алгоритм работы:
1. Извлекаем информацию о видео-файле
используем ffmpeg:

  1. $ ffmpeg -i <file_name>

ffmpeg не выводит тип субтитров (ASS/SRT), поэтому если тип файла видео mkv используем утилиту mkvinfo:

  1. $ mkvinfo <file_name>


2. Вычисляем конечного разрешение видео. Дело в том, что разрешение видео для iPhone что по вертикали, что по горизонтали должно быть кратно 16. Мы же будем привязывать разрешение к размеру экрана:

  1. # код на Python, переменные w,h - разрешение исходного видео ролика, _w, _h - разрешение финального видео
  2. _w = 480 # ширина будет равна ширине экрана
  3. _h = (h*_w)/w # вычисляем высоту
  4. tmp = _h%16
  5. if _h%16>7: # округляем высоту до ближайшего числа кратного 16
  6. _h += 16-_h%16
  7. else:
  8. _h -= _h%16


3. Конвертируем видео используя ffmpeg (используем двухпроходное кодирование), как собрать ffmpeg c поддержкой x264 я описывал здесь:

  1. $ ffmpeg -y -i <input_video> -an -vcodec "libx264" -b "600k" -s "<_w>x<_h>" -flags "+loop" -cmp "+chroma" -partitions "+parti4x4+partp8x8+partb8x8" -subq "5" -trellis "1" -refs "1" -coder "0" -me_range "16" -g "300" -keyint_min "25" -sc_threshold "40" -i_qfactor "0.71" -maxrate "300k" -bufsize "300k" -rc_eq "blurCplx^(1-qComp)" -qcomp "0.6" -qmin "15" -qmax "51" -qdiff "4" -level "30" <out_video>.mp4

в результате мы получили отдельно видео поток в <out_video>.mp4

4. Конвертируем аудио. Нам нужно получить стерео дорожку в формате AAC
Все хорошо кодируется если исходная аудио дорожка в стерео:

  1. $ ffmpeg -y -i <input_video> -map 0.1 -vn -acodec libfaac -ab 128k -ac 2 -ar 44100 <out_audio>.aac

если же аудио каналов больше то прогоняем через промежуточный файл:

  1. $ ffmpeg -y -i <input_video> -map 0.1 -vn -acodec ac3 -ab 448k -ar 44100 -ac 6 ./tmp.ac3
  2. $ ffmpeg -y -i ./tmp.ac3 -vn -acodec libfaac -ab 128k -ar 44100 -ac 2 <out_audio>.aac

есть лучше вариант:

  1. $ ffmpeg -y -i <input_video> -map 0.x -vn -acodec libfaac -ab 128k -ac 2 -ar 48000 -strict experimental <out.aac>


5. Извлекаем субтитры, если в формате SRT то хорошо, если в ASS - конвертируем в SRT

6. Собираем все наши дорожки в один файл M4V:

  1. $ MP4Box -add <out_video>.mp4 -add <out_audio1>.aac:lang=xxx:group=1 -add <out_audio2>:lang=xxx:disable:group=1 -add <out_subs1>.srt:group=2:lang=xxx -add <out_subs2>.srt:group=2:lang=xxx:disable <out_video>.m4v -new


7. MP4Box не правильно прописывает заголовки субтитров (alternativeGroup и subType), исправляем. В одном из следующих постов я расскажу о формате заголовка m4v файла.

8. Прописываем теги утилитой AtomicParsley

Скрипт который все это реализует я в общий доступ не выкладываю (он написан довольно криво, да и состоит в стадии тестирования), но если вы хотите, я могу его вым выслать с инструкцией по применению. Мой ящик

Так-же смотрите структуру MPEG4 файла

27 июля 2010 г.

MapKit в iOS

В моем старом приложении требовалась поддержка прошивки iPhone OS 2.х. Поэтому я не мог использовать все прелести фраемворка MapKit, который появился в 3.0 прошивке. В те далекие времена (около года назад) я отображал карту через UIWebView. Так как сейчас количество устройств с установленной на них прошивками 2.х ничтожно мало, было принято решение переходить на встроеные компоненты отображения карты.

Отобразить в вашем приложении карту можно используя класс MKMapView из фраемворка MapKit. Класс представляется множеством очевидных методов: mapType, zoomEnabled, region, showUserLocation, которые настраивают внешний вид карты. Но это все просто, мне было интересно добавлять на карту свои объекты. С этим мы и будем разбираться в данной статье.

Для добавления на карту элемента привязанного к одной геокоординате используется метод addAnnotation:. В качестве аргумента он принимает объект который наследует протокол MKAnnotation. Протокол реализует методы задания координаты нашей аннотации (coordinate), задание заголовка аннотации (title) и ее текста (subtitle). Вот самый простой пример объекта аннотации:

marker.h
  1. @interface marker : NSObject <MKAnnotation>
  2. {
  3. }
  4. @end


marker.m
  1. #import "marker.h"
  2.  
  3. @implementation marker
  4. #pragma mark MKAnnotation
  5. - (CLLocationCoordinate2D) coordinate
  6. {
  7.   return CLLocationCoordinate2DMake(46.46258530.750186);
  8. }
  9.  
  10. - (NSString *) title
  11. {
  12.   return @"This annotation";
  13. }

создадим экземпляр этого объекта и добавим его на карту:

  1. marker *= [[marker alloc] init];
  2. [mapView addAnnotation:m];
  3. [m release];

довольно просто, правда ведь? Если мы хотим кустимизировать нашу аннотацию, например изменить иконку иголочки, то нам необходимо использовать метод mapView:viewForAnnotation: делегата нашей карты, пример приведен в конце статьи.

Пусть теперь мы хотим нарисовать на карте объект привязанный к нескольким геокоординатам (например маршрут из точки А в точку В). Тут нужно добавлять не аннотацию, а оверлей addOverlay:. Добавим в него MKPolyline:

  1. CLLocationCoordinate2D mapCoords[6];
  2. mapCoords[ 0] = CLLocationCoordinate2DMake(46.476472, 30.704776);
  3. mapCoords[ 1] = CLLocationCoordinate2DMake(46.469664, 30.732229);
  4. mapCoords[ 2] = CLLocationCoordinate2DMake(46.462585, 30.750186);
  5. mapCoords[ 3] = CLLocationCoordinate2DMake(46.447197, 30.743040);
  6. mapCoords[ 4] = CLLocationCoordinate2DMake(46.415384, 30.723226);
  7. mapCoords[ 5] = CLLocationCoordinate2DMake(46.409143, 30.729909);
  8. MKPolyline *polyLine = [MKPolyline polylineWithCoordinates:mapCoords count:6];
  9. [mapView addOverlay:polyLine];
  10. [mapView setDelegate:self];


и в делегате нашей карты зададим свойства этой линии

  1. - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
  2. {
  3.  MKPolylineView *polylineView = [[[MKPolylineView alloc] initWithOverlay:overlay] autorelease];
  4.  polylineView.strokeColor = [UIColor blueColor];
  5.  polylineView.lineWidth = 3.0;
  6.  return polylineView;
  7. }


Пример проекта.

Так же смотрите:
- Конвертирование "человеческого" адреса в широту и долготу

21 июля 2010 г.

Работа с музыкальной библиотекой iPod в iOS

Возникла у меня задача, проверить программно, присутствует ли конкретная песня в библиотеке пользователя на iPhone/iPod Touch, и если есть, то проиграть ее. Итак, будем программировать работу с библиотекой iPod.

Для взаимодействия с музыкальной библиотекой в iOS существует класс MPMusicPlayerController из фраемворка MediaPlayer. Функций вроде и не много, но они покрывают практически все возможности приложения iPod.app. При создании экземпляра класса можно выбрать, каким плейером будем пользоваться, созданным отдельно для приложения(метод applicationMusicPlayer), или глобальным плейером iPod(метод iPodMusicPlayer). Первый выгрузится при закрытии приложения, второй может продолжать работать после закрытия приложения. Будем работать с глобальным плейером iPod.

Вызвав метод play мы уже можем услышать музыку доносящуюся из динамиков устройства (iPhone Sumulator не подключается к библиотеке iTunes). Если нужно знать, когда закончилась/сменилась песня нужно зарегистрировать слушателя:

  1. MPMusicPlayerController *ipod = [MPMusicPlayerController iPodMusicPlayer];
  2. [[NSNotificationCenter defaultCenter] addObserver:self
  3.   selector:@selector(playbackItemChanged:)
  4.   name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
  5.   object:ipod];
  6. [ipod beginGeneratingPlaybackNotifications];

тут мы регистрируем функцию playbackItemChanged: текущего класса как слушателя события изменения песни в iPod.

Хорошо, сейчас мы можем прослушивать песни из пленэра, но не можем выбирать, что слушать. Звуковой файл представляется в библиотеке классом MPMediaItem. Но просто так его создать нельзя, его нужно получать фильтруя из музыкальной библиотеки данные. Для фильтрации используется класс MPMediaQuery. Тут можно отфильтровать библиотеку по разным множеству категориям (таким как жанр, аудиокниги, подкасты, плечисты), а также можно добавлять свои фильтры.

  1. [ipod setQueueWithQuery:[MPMediaQuery songsQuery]];
  2. [ipod play];

И по традиции пример приложения.

16 июля 2010 г.

Проигрывание музыки с помощью iPhone SDK

Присутствие звуковых эффектов и музыки сильно улучшает ваше приложение. Сегодня я расскажу как с помощью iPhone SDK управлять проигрыванием звукового файла. Речь пойдет о проигрывали больших звуковых файлов с возможностью приостановки и изменении позиции проигрывания.
Я буду использовать класс AVAudioPlayer из фраемворка AVFoundation. Напишем приложение которое будет циклически проигрывать файл с возможностью перемотки. Я не использую Interface Builder поэтому не буду приводить скриншоты расположения элементов на экране. Итак, в в нашем вью присутствует кнопка старта/остановки проигрывания (playStopBtn), слайдер показывающий позицию в проигрываемом файле (tmSlider) и свичер разрешающий играть музыку при залоченом экране.
Первое, что необходимо сделать, так это создать наш плейер и настроить его. Например в методе init или loadView прописываем:

  1. NSURL *file = [[NSURL alloc] initFileURLWithPath:
  2.     [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"a_hot.caf"]];
  3. NSError *err = nil;
  4. player = [[AVAudioPlayer alloc] initWithContentsOfURL:file error:&err];
  5. [file release];
  6. player.numberOfLoops = -1;
  7. player.delegate = self;
  8. [player prepareToPlay];

в результате плейер у нас создан, и в него загружен наш звуковой файл.
Описываем событие нажатия на кнопку playStopBtn:

  1. if (player.playing)
  2. {
  3.  [player stop];
  4.  [playStopBtn setTitle:NSLocalizedString(@"Play", @"") forState:UIControlStateNormal];
  5.  [tmUpdaterTimer invalidate];
  6.  [tmUpdaterTimer release];
  7.  tmUpdaterTimer = nil;
  8. }
  9. else
  10. {
  11.  [player play];
  12.  [playStopBtn setTitle:NSLocalizedString(@"Stop", @"") forState:UIControlStateNormal];
  13.  tmUpdaterTimer = [[NSTimer scheduledTimerWithTimeInterval:0.2
  14.  target:self selector:@selector(timerAction:)
  15.  userInfo:nil repeats:YES] retain];
  16. }

тут все просто, если сейчас играет музыка - останавливаем ее проигрывание, исли плейер на паузе - начинаем проигрывание. Так-же тут создается/уничтожается таймер, который изменяет позицию слайдера при проигрывали:

  1. - (void) timerAction:(NSTimer *) sender
  2. {
  3.   [tmSlider setValue:player.currentTime animated:YES];
  4. } 

У слайдера показывающего позицию в проигрываемом файле тоже есть два слушателя на изменение позиции и окончание изменения позиции:

  1. - (void) changeTime:(UISlider *) sender
  2. {
  3.   if (!moving)
  4.   {
  5.     isPlaying = player.playing;
  6.     if (player.playing)
  7.     {
  8.       [player stop];
  9.     }
  10.     moving = YES;
  11.   }
  12.   player.currentTime = sender.value;
  13. }
  14.  
  15. - (void) endChangeTime:(UISlider *) sender
  16. {
  17.   moving = NO;
  18.   if (isPlaying)
  19.   {
  20.     [player play];
  21.   }
  22. }

при начале перетаскивания ползунка проверяем, играет ли музыка, если да, то приостанавливаем проигрывание в конце востанавливаем состояние.

Все проигрывается, но при блокировке экрана музыка приостанавливается. Следующий код разрешает проигрывание музыки при выключенном экране устройства:

  1. AudioSessionInitialize(NULL, kCFRunLoopDefaultMode, NULL, self);
  2. UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
  3. AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
  4. AudioSessionSetActive(true);

Мой пример использующий все выше написанное можно скачать

13 июля 2010 г.

Отсылка почты из вашего приложения встроенным клиентом Mail в iOS

Вот решил написать несколько статей по программированию под iPhone/iPod Touch/iPad.
Для начала выбрал простенькую тему описывающуюся одним классом в SDK и несколькими методами. Итак, отсылка почты из вашего приложения встроенным клиентом Mail.
Работу отсылки почты осуществляет класс MFMailComposeViewController наследник от стандартного UIViewController-а. Первое, что необходимо сделать, это подключить к вашему проекту фраемворк MessageUI.

Потом подключить описание этого фраемворка.

Теперь можно кодить.

  1. // Перед тем как вызывать диалог отсылки почты необходимо проверить настроен ли у пользователя вообще почтовый клиент.
  2. if (![MFMailComposeViewController canSendMail])
  3. {
  4.   // Клиент не настроен, показываем всплывающее окно.
  5.  UIAlertView *tmp = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Warning", @"") message:NSLocalizedString(@"Your e-mail application not configured", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil];
  6.  [tmp show];
  7.  [tmp release];
  8.  return ;
  9. }
  10.  // создаем экземпляр обекта MFMailComposeViewController
  11. MFMailComposeViewController *mcvc = [[MFMailComposeViewController alloc] init];
  12.  // указываем обект делегата (слушателя)
  13. mcvc.mailComposeDelegate = self;
  14.  // заполняем поле "Кому"
  15. [mcvc setToRecipients:[NSArray arrayWithObject:@"test@example.com"]];
  16.  // заполняем поле "Тема"
  17. [mcvc setSubject:NSLocalizedString(@"Testing mail", @"")];
  18.  // пишем тело письма
  19. [mcvc setMessageBody:NSLocalizedString(@"This is body of mail", @"") isHTML:NO];
  20.  // прикрепляем к письму данные как текст и указываем имя файла приложения к письму
  21. [mcvc addAttachmentData:[@"Text on attach" dataUsingEncoding: NSUTF8StringEncoding] mimeType:@"text/plain" fileName:@"test.txt"];
  22.  // показываем заполненный контрол
  23. [self presentModalViewController:mcvc animated:YES];

Нужно помнить, что контрол сам не убирается после нажатия кнопки "Отправить", поэтому в методе слушателе (mailComposeController:didFinishWithResult:error:) закроем наш контрол:

  1. - (void) mailComposeController:(MFMailComposeViewController*) controller didFinishWithResult:(MFMailComposeResult) result error:(NSError*) error
  2. {
  3.  [controller dismissModalViewControllerAnimated:YES];
  4. }

Проект для xCode можно скачать.

5 июля 2010 г.

Обжим сетевого кабеля напрямую и кроссовер (crossover)

Обжим сетевого прямого и кроссовер (crossover) кабеля 10/100Mbit.

1.При соединении Computer-Hub/Switch (карта-хаб/свитч) используется следующая схема:

С одной стороны
1: Бело-оранжевый
2: Оранжевый
3: Бело-зелёный
4: Синий
5: Бело-синий
6: Зелёный
7: Бело-коричневый
8: Коричневый

С другой стороны
1: Бело-оранжевый
2: Оранжевый
3: Бело-зелёный
4: Синий
5: Бело-синий
6: Зелёный
7: Бело-коричневый
8: Коричневый


2. При соединении двух компьютеров crossover (карта-карта) используется следующая схема обжима:

С одной стороны
1: Бело-оранжевый
2: Оранжевый
3: Бело-зелёный
4: Синий
5: Бело-синий
6: Зелёный
7: Бело-коричневый
8: Коричневый

С другой стороны
1: Бело-зеленый
2: Зеленый
3: Бело-оранжевый
4: Синий
5: Бело-синий
6: Оранжевый
7: Бело-коричневый
8: Коричневый

13 января 2010 г.

follow2iPhone - сервис поиска вашего iPhone

Несколько месяцев назад мне довелось протестировать сервис от Apple "Find my phone". Мне не понравилось то, что для его работы необходим включен сервис Push, а также показ на карте только текущего местоположения без исторических локаций.
follow2iPhone - сервис поиска вашего iPhone а также записи ваших исторических географических координат.

Установка
Для установки вам потребуется iPhone с jailbreak на нем (проще взломаный), с установленной Cydia на нем.
Из приложения Cydia устанавливаем iFile на ваш тедефон
Копируем на ваш телефон пакет с приложением, который скачиваем здесь.
Запускаем iFile, находим пакет который только-что переписали и устанавливаем его кликнув по нему.
Поздравляю, вы установили follow2iPhone.
Перезагружаем телефон.

После перезагрузки на SpringBoard появится новое приложение "follow2iPhoneSettings", запускаем его. Приложение попросит заполнить два поля "email" и "SOS number".
email - валидный email/login у google, так как ваша авторизация на сайте будет проходить через google.
SOS number - телефонный номер, на который будет отправлятся с помощью SMS телефонный номер текущей SIM-карты при смене в телефоне SIM-карты (функция не отключается при enable=OFF).
При SIM-карте в телефоне которая не соответствует той, которая была при первом запуске включается режим SOS: отправляется текущий телефонный номер на SOS number, также включается сохраниние GPS-координат телефона каждый SUI интервал.
Where I been - интервал времени через который включается GPS для сохрания исторических координат которые можно посмотреть на сайте

Приложение находится в стадии тестирования.