2009/10/13

Everioの動画をMPEG4にお手軽変換できるワークフロー

動画撮影にはHDDビデオカメラのビクターEverio(GZ-MG77)を使っているのだが、これで撮れるMPEG2形式の動画ファイル、実はMacではそのまま扱えない。QuickTimeで再生できないし、当然、iMovieで使う事もできない。Apple純正のQuickTime用「MPEG2再生コンポーネント」なるものはあるが、EverioのMPEG2ファイルに関しては再生ができても音が出ないそうだ。Everioの音声コーデックに非対応ならしい。

というわけで、WindowsからMacに乗り換えて数ヶ月、動画を撮り貯めるばかりで、編集する事はもちろん、再生する事すらもできていなかったのだが、ようやく最近になって、このEverioの動画ファイルを扱える環境をMac上に整えた。と言っても、再生のためにVLCを、iMovieで読み込めるMPEG4への変換のためにffmpegXを、それぞれインストールしただけ。こんなに簡単ならもっと早く使い始めておけばよかった。

これらのツール、対応する動画/音声のフォーマットが豊富で、UIはシンプルで使いやすく、とにかく素晴らしく便利。そこで今回は、便利ついでにAutomatorでワークフローを作ってみた。
Automator画面
このワークフローでは、ffmpegXのアプリパッケージに含まれているffmpegというコマンドを利用している(っていうか、そもそもffmpegXはffmpegのGUIラッパーなので、ffmpegこそが本体なんだけど)。「選択されたFinder項目を取得」の後ろに、ffmpegを実行するための「シェルスクリプトを実行」をつないで、Finder用のプラグインとして保存。Finderで選択した動画ファイルをMPEG4に自動変換してくれる。
ちなみに、シェルスクリプトの中身を書き下すと以下のような感じ。
for f in "$@"
do
/Applications/ffmpegX.app/Contents/Resources/ffmpeg -i "$f" \
-vcodec mpeg4 -s 640x480 -aspect 4:3 -r 29.97 -b 4000k -qmin 2 -qmax 9 \
-acodec aac -ab 96k "$f".mp4
done
ffmpegにはかなり豊富なコマンドオプションが用意されているので、特に出力の各種パラメータについては色々とカスタマイズの余地がありそうだが、これで変換がお手軽にできるようになった。

ワークフローを作るのは初めてだったが、ちょー便利だな、これ。


[2010/03/22追記]
ちなみに、ワイド(16:9)で撮った動画を変換するために、
for f in "$@"
do
/Applications/ffmpegX.app/Contents/Resources/ffmpeg -i "$f" \
-vcodec mpeg4 -s 720x405 -aspect 16:9 -r 29.97 -b 4000k -qmin 2 -qmax 9 \
-acodec aac -ab 96k "$f".mp4
done
と、スクリプトの縦横比関連の指定("-s"と"-aspect")を変えたものも別途用意して使っている。

2009/10/05

Wiiリモコンでラジコン制御

Wiiリモコンを使ってArduinoを載せたラジコンを無線制御できるようにしてみた。
ラジコンとWiiリモコン
基板部分のアップ
と言っても直接つないでいる訳ではなく、MacBookを介して、Wiiリモコン⇔MacBookはBluetoothで、MacBook⇔ラジコン上のArduinoはXBee(Zigbee)で、それぞれつないでいる。プログラム上の問題がまだ色々とあるものの、実際に走行させてみる事もできた。Wiiハンドルを握って操作してると、『リアルマリオカート』な気分になれるかも。;-)

※走行させているシーンではないが、こんな感じで操作できる。


ちなみに、写真にある通り、GPS受信機や各種センサ(加速度、ジャイロ、デジタルコンパス)など、『操作』には必要ないものも色々と載せてあるが、これらのデータはMacBookで記録できるようにしてある。今後、走行させた時のデータを色々と取ってみる予定。目指せ『自動制御』!

2009/10/03

Arduino 0017で、Megaのアナログ入力ピン8〜15が使えないバグ

Arduino Megaでは16本のアナログ入力が使えるはずなのだが、自分の環境では何故か0番から7番のピンしか正常に使えなかった。8番から15番のピンを読み取った結果を見てみると、ただのノイズ、という感じ。
Arduino Forumを調べてみたら、ばっちり同じ問題に関する書き込みがあった。結局、Arduino IDE 0017のバグだったようだ。
Sorry guys, this will be fixed in Arduino 0018. In the meantime, you can edit hardware/cores/arduino/wiring_analog.c and change:

ADMUX = (analog_reference << 6) | (pin & 0x0f);

to:

ADMUX = (analog_reference << 6) | (pin & 0x07);

Arduino Forum - Trouble with Mega Anolog in port.
上記の通り修正したら問題なく使えるようになった。

2009/09/28

OSX上のArduinoIDE(0017)で"Serial Port Already in Use"エラー

Javaでシリアル通信するためにRXTXライブラリを/Library/Java/Extensions/に入れたら、スケッチをArduinoへ転送する際にタイトルの通りのエラーが出るようになってしまった。

ArduinoIDEのパッケージに含まれるRXTXライブラリと何かしらバッティングでもしてるのか、などと疑って調べてみたら、見当違いだった。エラーは、/Library/Java/Extensions/に入れた方のライブラリが出しているもので、シリアルポートの排他制御のために使う/var/lock/ディレクトリが存在しなかったのが原因。どうやら、RXTXライブラリのコンパイルオプションでは/var/lock/を使うのがデフォルトらしい。
Arduino Forumに情報があった。
the default compilation options for RXTX create a library that uses locks to help ensure that only one program tries to access the serial port at a time. This requires you to setup the lock directory (this was the purpose macosx_setup.command script that used to come with Arduino and Processing). If you don't set up the lock directory, you get an error like the one you saw.

Arduino Forum - Serial Port Already in Use - MacOSX leopard
というわけで対策は、/var/lock/を使わないオプションでRXTXをリコンパイルするか、/var/lock/ディレクトリを作成して、ユーザから書き込めるようにパーミッションを設定してやるか。
僕はお手軽に、
$ sudo mkdir /var/lock
$ sudo chown <user> /var/lock
で済ませた。
ちなみに、Arduino Forumの記事にあるようなchmod 777はちょっとイヤだったので、オーナーを使用ユーザに変更するにとどめておいた。今のところ問題なく動いているようだ。

Arduinoをラジコンに仮配置

GPSやセンサをつないだArduinoを使って、ラジコンの自動制御をやってみよう!
というわけで、、、
諸々の機能(GPSやセンサのデータ取得、ラジコンのサーボ&スピコン制御、LCD表示、XBeeでの通信無線化など)のArduino上での動作に目処がついたので、必要な部品をArduino Megaと2枚のボード上にまとめて、ラジコンに載せられるようにした。それぞれ単体での動作確認が済んだ段階で、まだボード間の配線はできていないが、実際に載せてみるとこんな感じ。
とりあえず仮載せ
右側に見えているのがArduino Mega+GPS受信機シールド。奥に見えているボードには、秋月のGPS受信機GT-720F、無線通信用のXBee、および、キャラクタLCDを、手前のボードには、3軸加速度センサ、2軸ジャイロ、および、デジタルコンパスを、それぞれまとめてある。他に、障害物検知用のセンサなども車体の前の方に載せていきたい。

さて、ボード間をつないで早く動かしたいが、自動制御はやっぱり相当難しそう。なので、まずは簡単なところから、Wiiリモコンでこのラジコンを制御できるようにしようと思っている。GPSやセンサで色々とデータを取ってみてから、自動制御に向けて何をすべきかじっくり考えることにした。

ちなみに、このラジコン、タミヤのグラベルハウンドという製品である。数年前、どうしても組立式のラジコンカーが欲しくなった時に、衝動買いした。少し遊んだだけで家の奥にしまいっぱなしにしていたので、今回、再び動かしてやれてよかった。

2009/09/26

NewSoftSerialがArduino Megaで使えない

ソフトウェア的にシリアル通信を実現してくれるNewSoftSerial、高速通信時の動作も安定していてとても便利。なんだけど、、、Arduino Megaで使おうとしたらどうしても動かない。あれこれ試してみてもダメなので少し調べてみると、現時点での最新版(バージョン9)では、まだMegaがサポートされていないとのこと。
What’s next for NewSoftSerial? Well, there are a number of things being asked for:
  • Arduino Mega support
  • Signal inversion option
  • RTS/CTS flow control option
  • Configurability, i.e. “E, 7, 1″ style parity, data and stopbit configuration.
Direct Port I/O in NewSoftSerial 9 | Arduiniana
場合によってはMegaでもシリアルが足りなくなりそう。
次のバージョンアップを期待。

2009/09/23

Java on OSXでWiiリモコン

今さら感は拭えないが、Wiiリモコンとarduinoを組み合わせて遊びたい。
そこでまずは、MacBook上でJavaからWiiリモコンを触れるようにした。
wii remote

準備

JavaとWiiリモコンをBluetoothでつなげてみよう - ブログ: 岡崎 - Okazaki's blog」や「WiiLi.org Wii Linux - JP:WiiremoteJ/ReadMe」などにあるように、必要なものは、Java版Bluetooth API(JSR-82)のオープンソース実装bluecoveと、WiiプロトコルのJavaライブラリWiiRemoteJだけ。2009年9月22日現在での最新バージョン、BlueCove2.1.0とWiiRemoteJ v1.6を使ってみた。

JavaVMの起動オプション

いきなりつまずいた。リモコンを探しに行くところで、
java.lang.IllegalArgumentException: PCM values restricted by JAR82 to minimum 4097
とエラーメッセージが出て止まってしまう。
Googleで検索してみると色々勉強になる(例えばここ)が、結局、「JavaVMの起動オプションに以下を加えることで問題を回避できる」そうだ。
-Dbluecove.jsr82.psm_minimum_off=true
理由は問わない。魔法だと思おう...

実装例 for 安定動作?

とりあえずWiiRemoteJの配布物に含まれているサンプルコードを動かしてみる。
リモコン探して、つないで、ボタンのイベントや加速度センサのデータなどを取得する、一通りの方法はとりあえず分かった。が、このWiiRemoteJ、安定して動作させるのがなかなか難しい、ということも分かった。特にリモコンとの接続確立時に、アプリ側で対処できない(と思われる)エラーがよく起きる。

色々やってみた結果、サンプルコードで使われているfindRemote()でなく、findRemotes(WiiDeviceDiscoveryListener listener)を使った方が、動作が安定する、っぽい。あくまで「っぽい」だけど。WiiDeviceDiscoveryListenerを使ったコードは、例えば以下のようになる。
import wiiremotej.WiiRemote;
import wiiremotej.WiiRemoteJ;
import wiiremotej.event.WRAccelerationEvent;
import wiiremotej.event.WRButtonEvent;
import wiiremotej.event.WRStatusEvent;
import wiiremotej.event.WiiDeviceDiscoveredEvent;
import wiiremotej.event.WiiDeviceDiscoveryListener;
import wiiremotej.event.WiiRemoteAdapter;

public class TestWiiRemote extends WiiRemoteAdapter implements WiiDeviceDiscoveryListener
{
public static void main(String[] args)
{
WiiRemoteJ.setConsoleLoggingAll();
TestWiiRemote test = new TestWiiRemote();
try{
WiiRemoteJ.findRemotes(test,1);
}catch(Exception e){
e.printStackTrace();
}
}

public TestWiiRemote()
{
}

public void findFinished(int numFound)
{
System.out.println(numFound+" remotes has been found!");
}

public void wiiDeviceDiscovered(WiiDeviceDiscoveredEvent evt)
{
System.out.println("remote(#"+evt.getNumber()+") was discovered!");
WiiRemote remote = (WiiRemote)evt.getWiiDevice();

try{
remote.setAccelerometerEnabled(true);
remote.setSpeakerEnabled(true);
remote.setLEDIlluminated(0,true);
}catch(Exception e){
e.printStackTrace();
if(remote!=null && remote.isConnected()){
remote.disconnect();
}
}

final WiiRemote remoteF = remote;
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable(){
public void run(){
if( remoteF!=null && remoteF.isConnected() ){
remoteF.disconnect();
}
}
}));

remote.addWiiRemoteListener(this);
}

public void disconnected()
{
System.out.println("Remote disconnected...");
System.exit(0);
}

public void statusReported(WRStatusEvent evt)
{
System.out.println("Battery level: " + (double)evt.getBatteryLevel()/2+ "%");
System.out.println("Continuous: " + evt.isContinuousEnabled());
System.out.println("Remote continuous: " + evt.getSource().isContinuousEnabled());
}

public void accelerationInputReceived(WRAccelerationEvent evt)
{
double x = evt.getXAcceleration();
double y = evt.getYAcceleration();
double z = evt.getZAcceleration();

System.out.println(x+","+y+","+z);
}

public void buttonInputReceived(WRButtonEvent evt)
{
if (evt.wasPressed(WRButtonEvent.TWO))System.out.println("2");
if (evt.wasPressed(WRButtonEvent.ONE))System.out.println("1");
if (evt.wasPressed(WRButtonEvent.B))System.out.println("B");
if (evt.wasPressed(WRButtonEvent.A))System.out.println("A");
if (evt.wasPressed(WRButtonEvent.MINUS))System.out.println("Minus");
if (evt.wasPressed(WRButtonEvent.HOME))System.out.println("Home");
if (evt.wasPressed(WRButtonEvent.LEFT))System.out.println("Left");
if (evt.wasPressed(WRButtonEvent.RIGHT))System.out.println("Right");
if (evt.wasPressed(WRButtonEvent.DOWN))System.out.println("Down");
if (evt.wasPressed(WRButtonEvent.UP))System.out.println("Up");
if (evt.wasPressed(WRButtonEvent.PLUS))System.out.println("Plus");
}

}
少なくとも自分の環境では、接続時にエラーが出ることはほとんどなくなった。ただ、アプリ終了時のdisconnect()がどうしてもうまくいかない。Ctrl+Cとかでアプリを終了させると、ShutdownHookが利いてdisconnect()が呼ばれるところまでは行くのだが、きちんと切断しきってくれない。アプリが終了した後、Bluetooth接続が残ってしまう。まぁそのままでもいいんだけど、これ、きれいに切りたいなぁ...

2009/09/22

I2C LCD用ライブラリを作ってみた

ビーコン菅原の組み込みがらみ | Arduinoで I2C LCD」を参考にして、ストロベリーリナックスのI2C低電圧キャラクタ液晶モジュール(16x2行)を動かしてみた。
I2C LCD on arduino
ピン2本だけでLCDが制御できてしまうのは楽チンだ。
ついでにアイコンの表示制御機能も追加した上で、ライブラリ化してみた。
スケッチはとにかく簡潔にしたいのである...;-)

I2C LCD(ST7032i)用ライブラリ:NsLCD-0.1.zip

ちなみに、ライブラリを置く場所は、IDEのバージョンが0017なら、「Sketchbook location」直下の「libraries」(無ければ自分で作ればよい)、IDEのバージョンが0016以前なら、IDEインストール場所の「Arduino\hardware\libraries」。

NsLCDを使ったスケッチはこんな感じになる。
#include <Wire.h>
#include <NsLCD.h>

// NsLCD library for I2C LCD(ST7032i) sample sketch
// YKO[2009/09/21]

void setup()
{
// initialize I2C LCD
Wire.begin( 0 ); // I2C master mode
NsLCD.init();
}

void loop()
{
NsLCD.clear();

NsLCD.moveCursor(0,0);
NsLCD.print("cyclic landscape");

NsLCD.moveCursor(0,1);
NsLCD.print(" by YKO");

NsLCD.displayAntennaIcon(true);
NsLCD.displayTelIcon(true);
NsLCD.displaySoundIcon(true);
NsLCD.displayOutputIcon(true);
NsLCD.displayUpDownIcon(true,true);
NsLCD.displayKeyIcon(true);
NsLCD.displayMuteIcon(true);
NsLCD.displayBatteryIcon(true,3);
NsLCD.displayNumIcon(true);

delay(1000);

NsLCD.home();
for(int i=0;i<16;i++) NsLCD.write('@');

NsLCD.displayAntennaIcon(false);
NsLCD.displayTelIcon(false);
NsLCD.displaySoundIcon(false);
NsLCD.displayOutputIcon(false);
NsLCD.displayUpDownIcon(false,false);
NsLCD.displayKeyIcon(false);
NsLCD.displayMuteIcon(false);
NsLCD.displayBatteryIcon(true,0);
NsLCD.displayNumIcon(false);

delay(1000);
}
使い方のポイントは以下の通り。
  • init()する前に、必ずI2Cライブラリの方でWire.begin(0)すること。

  • init()では500msecほど、clear()では200msecほど、それぞれ待ち時間が発生する。

  • moveCursor()でカーソルを動かして、print()で文字列、write()で文字、を表示。

  • display??Icon()で各アイコンの表示状態を制御。

とりあえず問題無く動いてるみたい。

2009/09/20

GPS受信機シールド用ライブラリを作ってみた

シルバーウィークのうちに、arduinoで少しまとめて遊びたい。
というわけで、先日入手したGPSシールド用のライブラリを書いて、動かしてみている。
GPS受信機シールド動作中
ベースはgpsrobocarブログで公開されているテスト用スケッチ
ライブラリ化してみたら、スケッチがすっきりして気持ちがいい。
せっかく作ったので、とりあえず公開しておく。

GPS受信機シールド用ライブラリ:NsGH81-0.1.zip

以下にNsGH81を使ったスケッチのサンプルを載せておく。
シリアルが一本しかないDuemilanoveなどで動くよう、NewSoftSerialを使っている。純正のSerialライブラリを使ってももちろん動くし、手持ちのMegaでも正常に動作した。また、GH-81用に作ったものだが、古野バイナリでデータをやり取りする他の機種(GH-80など)でも動くはず。確認はできないけど。
#include <NsGH81.h>
#include <NewSoftSerial.h>

// NsGH81 sample sketch
// YKO[2009/09/20]

// serial pin
#define SER_RX 2
#define SER_TX 3

// serial baud rate
#define BAUD_RATE_LOCAL 9600 // Serial
#define BAUD_RATE_GPS 9600 // NewSoftSerial

// GPS module's LED pins
#define GPS_LED1 9
#define GPS_LED2 8

// setup SoftwareSerial port
NewSoftSerial mySerial = NewSoftSerial(SER_RX,SER_TX);

// buffer
char buff[50];

void setup()
{
// initialize pin modes
pinMode(SER_RX,INPUT);
pinMode(SER_TX,OUTPUT);

// initialize serial ports
Serial.begin(BAUD_RATE_LOCAL);
mySerial.begin(BAUD_RATE_GPS);

// initialize GPS
NsGH81.init(recieveLocation,recieveTime,GPS_LED1,GPS_LED2);
NsGH81.setStartCmd(buff);
mySerial.print(buff);
}

void loop()
{
int d = mySerial.read();
if( d>=0 ){
NsGH81.update(d);
}
}

void recieveLocation(NSGH81_LOCATION g)
{
int alt_0 = int(floor(g.alt));
int alt_1 = int(round((g.alt-alt_0)*10.0));
sprintf(buff, "lat=%+03d-%02d-%02d.%03d, ", g.lat.deg, g.lat.min, g.lat.sec, g.lat.msec);
Serial.print(buff);
sprintf(buff, "lon=%+04d-%02d-%02d.%03d, ", g.lon.deg, g.lon.min, g.lon.sec, g.lon.msec);
Serial.print(buff);
sprintf(buff, "alt=%d.%01d[m], nsat=%d, status=%d", alt_0, alt_1, g.nsat, g.status );
Serial.println(buff);
}

void recieveTime(NSGH81_TIME g)
{
sprintf(buff, "(20%02d/%02d/%02d %02d:%02d:%02d)", g.year, g.month, g.day, g.hour, g.min, g.sec);
Serial.println(buff);
Serial.println("-----");
}
ライブラリの使い方を整理すると以下の通り。
  • setup()で...

    • init()でコールバック関数とシールド上のLEDにつないだピン番号を設定

    • setStartCmd()で測位動作設定用のコマンドを作成し、シリアルを使ってモジュールへ書き込み

  • loop()で...

    • シリアルからデータを1バイト読んで、読んだ1バイトのデータを引数にしてupdate()をコール、を延々繰り返し。ライブラリ内部で読み込んだデータはパースされ、パース結果(測位結果と時刻)が得られる度に、結果を引数にしてコールバック関数がコールされる。つまり、コールバック関数の中に所望の処理を書いておけばよい。

さて、GPSモジュールは無事つながった。何しよう...笑

2009/09/05

GPS受信機シールド入手

GPSロボットカーコンテスト向けに無償配布されていたArduino用のGPS受信機シールド、首尾よく入手できた。
GPS受信機シールド
GPSモジュールは古野電気のGH-81だそうだ。赤い基板がかっこいい。
とりあえずArduinoにつないでみて、手持ちの秋月GT-720Fと性能などを比較してみるつもり。