2007年10月3日水曜日

Eclipse PDTでPHPコマンド実行のUIがでない

PDTを1.0.0.v20070917というのに入れ替えたのですが、Run/DebugのビューでPHPコマンドの設定がエラーとなり表示されません。PHP Web ページの方は問題なく表示されています。 Eclipseのバグレポートを見たらそれらしいものがあったので、既に報告済みだと思われます。とりあえずはバグフィックス待ちということになるもよう。

Updateでバージョンアップしたのですが、念のためPDTのall-in-oneを手動でダウンロードして、インストールしてみましたがだめでした。プロジェクトの設定でみても、他のfeatureとの不都合などが見られないため、おそらくPDTのバグだと思います。 今のところ回避方法は見つけてはいません。おそらくバージョンアップに伴うレベルダウンと思われるので、古いfeatureに戻せばよいかと思ったんですが、何故か同じようにエラーになります。なぞです。

現状、PHPのコマンド実行でデバッグなどはしてないので、体制に影響はないのですが、PHPコマンドを実行したいときに、EclipseのRunから実行できないので、(Windowsなので)DOS窓から普通に「php xxx.php」のようにコマンドを叩いて実行しています。

  • COACH今だけ価格
  • 缶コーヒーはまとめ買いがお得
  • Vivian Westwood期間限定プライス

2007年9月13日木曜日

PEAR::HTML_Page2を使う

PEARにHTML_Page2という、HTMLページを作成するためのパッケージがある。HTML_Page2については、PEARのサイトにHTML_Page2のドキュメント(日本語)があって、このドキュメントを見れば特にこの先の話は必要ではないかもしれないが、初めてHTML_Page2を使う人向けに簡単な例を書こうと思う。

まず、HTML_Page2の基本的な使い方は以下の通り。

  • オブジェクトの作成(newする)
  • ヘッダー関連を設定する
  • ボディコンテンツの追加
  • HTMLの出力

オブジェクトの作成

newのパラメタ(すなわちコンストラクタのパラメタ)にいくつかの情報を設定することができる。
まず、この文章を読んでいる人たちは、languageを"ja"にしたいと思うだろう。Windowsだったら、改行コードを"\r\n"にしたいと思うだろう。 という設定をしてインスタンスを生成するには、以下の通りにする。

require_once 'HTML/Page2.php';
$attributes = array(
 'language' => 'ja',
 'lineend' => 'win',
);
$page = new HTML_Page2($attributes);

ヘッダー関連を設定する

まずはHTML的にtitleタグが必須であるので、タイトルを設定する。あと、HTML_Page2でHTMLを生成すると、"Generator"をいうメタタグを設定するのであるが、これをなくしたいと思うとすると。以下の通りにすればよい。

$page->setTitle('ページのタイトル');
$page->setMetaData('Generator', NULL);

あとは、スクリプトやスタイルシートの設定などについては、PEARのドキュメントに詳しく書いてある。

ボディコンテンツの追加

あとは、ブラウザに表示されるコンテンツを追加していけばよい。このとき、HTML_Page2::addBodyContent()というメソッドを使う。このメソッドの引数にHTMLのタグを含む文字列(String)を追加していけばよい。 また、このメソッドは、文字列だけでなく、オブジェクトも追加することができる。このときこのオブジェクトにtoHTML()もしくはtoString()というメソッドを実装しておくと、HTML_Page2がHTMLを生成するときに、それぞれのオブジェクトのこれらのメソッドを呼び出し、その結果を取り込み出力する。
さらにHTML_Page2::addBodyContent()は、文字列やこのようなオブジェクトを含む配列(array)も渡すことができる。このときは、配列に含まれる順番に追加されていく。

ちなみにPEARに含まれるHTMLのパッケージ群は、toHTML()が実装されているので、オブジェクトのまま追加することができる。PEARのパッケージを使わなくても自前クラスでtoHTML()を実装して、HTML_Page2::addBodyContent()に渡すというやり方がHTML_Page2の多い使い方ではないかと思う。

HTMLの出力

ボディコンテンツを全て追加した後、HTMLとして出力する。

$page->display();

この例では標準出力に吐き出すが、文字列として結果を得るにはHTML_Page2::toHTML()を使う。また、HTML_Page2::toFile()というメソッドがあるが、これは引数にファイル名を与えるとそこに出力するというものである。

HTML_Page2の派生

上記のオブジェクトの生成で、"language"や"lineend"のパラメタを毎回コンストラクタに渡したりするよりは、クラスを派生したほうがよっぽどエレガントであると思われる。

require_once 'HTML/Page2.php';
class MyHTMLPage extends HTML_Page2 {
 function __construct($attributes = NULL) {
  if ($attributes != NULL) {
   if (!array_key_exists('language', $attributes)) {
    $attributes['language'] = 'ja';
   }
   if (!array_key_exists('lineend', $attributes)) {
    $attributes['lineend'] = 'win';
   }
  }
  else {
   $attributes = array('language' => 'ja', 'lineend' => 'win');
  }
  parent::__construct($attributes);
  $this->setMetaData('Generator', NULL);
 }
}

もちろん、これ以外のヘッダ関連の設定などを追加したほうが便利なことが多いだろう。

2007年9月12日水曜日

Norton AntiVirusでブラウザが遅くなる

今日はソフト開発系のネタではないのですが、最近困っていたことが解決したので、何かの役に立つかもしれないということで、ネタにしてみました。

ここ1週間ぐらい前から、ブラウザを使うとWindowsがものすごく遅くなるという現象になっていた。ブラウザは、Firefox、IE7、Operaのいずれも同様な現象が発生していた。ただ、ブラウザ以外のソフトでは、遅くなるようなことがなかった。
何かの常駐物が悪さをしているか、いわゆるマルウェアとかいうのがいるのか、とか原因を探っていたところ、どうやら、Norton AntiVirusのAuto Protectを無効にすると、遅くならないことが判明した。

すなわち、犯人はNorton AntiVirusということであった。しばらくの間、遅いのを我慢するか、どうしてもというときには、Auto Protectを無効にしていたのだが、さすがにウィルス対策なしの状態で使い続け続けるのは、いかがなものかと思い、いろいろ調べてみたところ、次のようなことがわかった。

Javascriptをスキャンさせると遅くなる
プラグインなどでJavascriptを読み書きにいくことが多いため、起きるものだと考えられる。これを回避する方法は、Auto Protectの「除外」リストに「*.js」を追加する。ただし、くれぐれも手動スキャンの除外には追加しないように。
ただし、パフォーマンスは上がるが、安全性は落ちる可能性があることを理解して使うべきである。
圧縮ファイルの中身をスキャンすると遅くなる
これは文字通りだが、ブラウザとの関係としては、JARぐらいしか思い当たらないが、これが原因であることがあるらしい。回避方法は、Auto Protectの設定で「圧縮ファイルの内部をスキャンする」という、そのまんまなオプションがあるので、これをオフにする。
ちなみにうちの環境では、このオプションを変えてもほとんど動作に変わりはなかったので、そのままオンの状態にしている。
もし、Eclipseの動作がえらく遅いときは、このオプションを変えてみると効果があるかもしれない。

2007年8月14日火曜日

PHPでself::でスタティックなメソッド呼び出すとサブクラスのメソッドは呼び出されない

小ネタですが、PHPでスタティックなメソッドを呼び出す時の話です。 PHPでは、「self::method();」という形式でstaticなメソッドを呼び出すことができます。これは文字通り、「self」すなわち自分のスタティックメソッドを呼び出すわけです。

class MyClass {
 static function name() {
  return 'Pichu';
 }
 function getName() {
  return self::name();
 }
}
class MySubClass extends MyClass {
 static function name() {
  return 'Pikachu';
 }
}
という定義をしているときに以下のようになります。
$myObject = new MySubClass();
echo $myObject->getName();
//結果は「Pichu」となる
あえていうまでのことではないといえば、その通りなのですが、スタティックなメソッドでも「$this->」と同様に親クラスから実装されているはずのサブクラスのスタティックメソッドを呼び出したい時もあると思うのです。 これを実現する方法は、これといって難しい方法ではないのですが可能です。
class MyClass {
 static function name() {
  return 'Pichu';
 }
 function getName() {
//  return self::name();
  eval('$name='.get_class($this).'::name();');
  return $name;
 }
}
と、いった具合になります。 コードは簡単なので特に説明は不要でしょう。

2007年7月23日月曜日

JAVAでファイルのSHA-1を計算する

ダウンロードしたファイルが正しいかチェックするためにSHA-1のチェックサムを利用していることはよくあることですが、WindowsでSHA-1とかのダイジェストを得るためにはフリーウェアとかを使ったりします。その手のツールは便利でいいのですが、インストールするのが気が引けたりするので、そのくらいならJAVAでさくっとできないものかと思って、とりあえず作ってみました。

とりあえずなので、基本的なコンセプトは以下のような感じ。

Windowsなので結果をウィンドウで表示する
いわゆるメッセージボックスのようなもので計算結果を表示する。
機能は限りなくシンプル
こういうツールを作ろうとすると、いろいろ便利にしたくなるのですが、とりあえずは簡単なものにする。
エラーぐらいは表示
エラーが出ても突っ走って、何も表示されないと悲しいので、エラーメッセージぐらいは表示しようということ。

とりあえず、ソースコードはこちら。ちなみにソースコメントは入れてないです。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.swing.JOptionPane;

public class FileDigest {
 protected static int READ_SIZE = 1024;
 protected static String DIGEST_ALGORITHM = "SHA-1";
 protected static String DIALOG_TITLE = "File Digest Tool";
 protected MessageDigest messageDigest;
 public static void main(String[] args) {
  if (args.length == 0) {
   messageOut("No taget file.", true);
   System.exit(1);
  }
  FileDigest fileDigest = new FileDigest();
  try {
   fileDigest.setFile(args[0]);
  } catch (FileNotFoundException e) {
   messageOut(String.format("File not found (%s).", args[0]), true);
   System.exit(2);
  } catch (IOException e) {
   messageOut("File read error.", true);
   System.exit(3);
  }
  messageOut(fileDigest.digestString(), false);
 }
 public FileDigest() {
  try {
   messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
  } catch (NoSuchAlgorithmException e) {
   System.exit(4);
  }
 }
 public void setFile(String pathName) throws FileNotFoundException, IOException {
  FileInputStream inStream = new FileInputStream(pathName);
  DigestInputStream digestStream = new DigestInputStream(inStream, messageDigest);
  byte readBuffer[] = new byte[READ_SIZE];
  while (digestStream.read(readBuffer) > 0);
 }
 public String digestString() {
  byte codeList[] = messageDigest.digest();
  String digest = new String();
  for (byte code : codeList) {
   digest += String.format("%x", code);
  }
  return digest;
 }
 protected static void messageOut(String message, boolean isError) {
   JOptionPane.showMessageDialog(
     null, message, DIALOG_TITLE, 
     isError ? JOptionPane.ERROR_MESSAGE : JOptionPane.INFORMATION_MESSAGE);  
 }
}
あえてどう使うかという説明をするほどのものではないとは思っているのですが、デスクトップにショートカットをおいて、ドラッグ&ドロップで結果がわかるといいかと思ったので、そうするには以下のようにしてみてください。
ソースをコンパイルして.classファイルを作成
その方法は説明するまではないと思いますが、普通にやれば「FileDigest.class」というファイルができるはず。
デスクトップにショートカットを作成
explorerで右クリックして、「送る」のサブメニューのデスクトップを選択する。
できあがったショートカットのプロパティを表示
リンク先の変更
このときリンク先は、FileDigest.classのフルパスになっているので、「javaw FileDigest」(FileDigest.classではない)に変更する。 作業フォルダはそのままにしておく(CLASSPATHの代わり)。

これで、ショートカットから、ツールが起動できるはず。うまくいけば、アイコンがJAVAのものに変わる。 ちなみに単にそのショートカットで起動すると「No target file.」と書いてあるメッセージボックスが表示されます。ダイジェストを計算したいときは、explorerからこのショートカットへドラッグ&ドロップすると、計算結果がメッセージボックスで表示されます。

2007年7月10日火曜日

EclipseにZend Debuggerを組み込む

Eclipseを3.3に変えて、Zend Debuggerをインストールしたので、そのときのインストールメモです。ようするにZendの「Extension Binary」と「PHP Debugger Plugin」をインストールするということです。

まずはZendのPDTのページへいく
Debugger Extension Binaryをダウンロードする
「Download Debugger Extension Binaries」というリンクをクリックすると、バージョンとプラットホーム別にファイルがあるので、適切なファイルをダウンロードする。
ファイルを展開してコピー
ダウンロードしてきたファイルを展開して、適当なところへコピーする。ファイルを読み込ますときの設定はフルパスで設定するので、常識的な範囲でどこでもよい。
php.iniの設定
展開されたフォルダにREADMEがあるので、それを読むとわかりますが、php.iniに以下の項目を追加する。
zend_extensionまたはzend_extension_tsにsoまたはDLLのフルパスを設定
Windowsの場合、「zend_extension_ts=c:/path/ZendDebugger.dll」のような感じで設定する。このとき、実行しようとしているPHPのバージョンに合ったファイルを指定する必要がある。たとえば、PHPが5.2だったら「5_2_x_comp」のフォルダの下にあるファイルを設定する。
zend_debugger.allow_hostsに接続するホストを設定
デバッグしようとするマシンのIPアドレスを指定する。ローカルで閉じているならば、「zend_debugger.allow_hosts=127.0.0.1」と書けばよい。
zend_debugger.expose_remotely=alwaysを追加する
よくわからないが設定する。
dummy.phpをドキュメントルートにコピー
展開されたフォルダーにあるdummy.phpをWebサーバーのドキュメントルートにコピーする。と、READMEに書いてあるが、これをしなくてもデバッグできるもよう。
Webサーバーを再起動
phpinfo();を表示すると、失敗してなければ「Zend Debugger」という項目が表示される。表示されているディレクティブの中に「zend_debugger.allow_hosts」が設定した通りになっているか確認する。
Debugger Pluginのインストール
「Download Executable Debugger Plugin」というリンク先からファイルをダウンロードしてEclipseにインストールするか、Update SiteにZend PDTを登録して、「Zend Debugger Feature」インストールするかのどちらかであるが、これはEclipseのプラグインの一般的なインストール方法である。

Featureのインストールをしくじってなければ、「PHP Debug」のパースペクティブが追加可能になっているはずなので、これでWebサーバで実行されるPHPのデバッグが可能になる。

Eclipse PDTを使っていて 意味不明な"syntax error, unexpected~"がでる場合

小ネタですが、PDTを使ってPHPファイルを編集実行したときに、"syntax error, unexpected~"(~のところは"@"とかT_STRINGとか)が発生しているが、エディタ上ではどう見てもそんな文字がないのは、どういうことかというお話です。

おそらく、コード系がおかしいと思われます。しかし、ソースコード上でマルチバイト文字がないときでも、このようなエラーが出るようです
とりあえず、ソースファイルのプロパティを開いて、保存のエンコーディングを確認したほうがよいでしょう。おそらく、もっとも無難だと思われるのは「UTF-8」だと思います。UTF-8で保存するとあぶり出しのようにエラーになっている文字列が現れます。

あと、デフォルトのエンコーディング(新規作成、読み込み)を変更したいときは、「設定(Preferences)」(ウィンドウ(Window)メニューの一番下にある)を開いて、「一般(General)」の項目の中にある「ワークスペース(Workspace)」の画面で設定できます。

Eclipseを3.3にしたんですが、こんなエラーが出てきて、なんだかなぁと思ったら、こんなことになってました。

2007年4月25日水曜日

JavaでWAVファイルを再生する(2)

前回のJavaでWAVファイルを再生するでは、あまりにもとりあえず過ぎたので、ちょっとはましなコードにしてみました。今回は、再生を一時停止しても読み込んだデータをスキップせずにちゃんと出力するようにしました。なお、LineListenerは今回ははずしました。必要があれば、前回のソースを参照してください。

public class RenderAudioFile implements Runnable {
protected AudioInputStream fileStream = null;
protected SourceDataLine sourceDataLine = null;
private byte sampleBuffer[]; //データ受け渡し用のバッファ
private ByteBuffer sampleByteBuffer; //バッファ参照用のByteBuffer

public void openFile(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
 //出力するファイルを受け取るメソッド
 fileStream = AudioSystem.getAudioInputStream(file);
 AudioFormat format = fileStream.getFormat();
 sourceDataLine = AudioSystem.getSourceDataLine(format);
 sourceDataLine.addLineListener(this);
 sourceDataLine.open(format);
 sampleBuffer = new byte[sourceDataLine.getBufferSize()];
 sampleByteBuffer.wrap(sampleBuffer);
}
public void close() {
 //ファイルを閉じるメソッド
 fileStream.close();
 fileStream = null;
}
public void play() {
 // 再生開始、再開用メソッド
 if(sourceDataLine == null || fileStream == null) {
  return;
 }
 sourceDataLine.start();
 new Thread(this).start();
}
public void pause() {
 // 一時停止メソッド
 if(sourceDataLine == null || fileStream == null) {
  return;
 }
 sourceDataLine.stop();
}
public void run() {
 //再生スレッドのメソッド
 do {
  if (sampleByteBuffer.position() == 0) {
   try {
    int readSize = inputStream.read(sampleBuffer);
    if (readSize <= 0) {       sourceDataLine.stop();       break;      }      sampleByteBuffer.position(readSize);     } catch (IOException e) {      sourceDataLine.stop();      break;     }    }    soundByteBuffer.flip();    int writeSize = dataLine.write(sampleBuffer, 0, sampleByteBuffer.remaining());    if (writeSize >0) {
   putSampleData(sampleBuffer, writeSize);
   sampleByteBuffer.position(writeSize);
  }
  sampleByteBuffer.compact();
 } while (sourceDataLine.isRunning());
}
}

とりあえず、普通に再生するには問題ないのですが、まだ、手を抜いてあるところがあります。

  • ファイルをすべて再生しきったときの処理
  • 再生位置の変更

いずれも、自由に再生を開始する位置を変更できればよいのですが、これらを実現するために使えそうなメソッドとして、AudioInputStream.mark()とAudioInputStream.reset()というのがあります。これは、mark()で再生位置を保存しておいて、reset()でその位置に移動するというものです。 ただし、これらのメソッドはInputStreamによって、使えたり使えなかったりしますが、それを調べるためにAudioInputStream.markSupported()というメソッドがあります。再生しようとするファイルや環境に依存するかもしれませんが作ったプログラムでは、markSupported()はfalseを返してきます。すなわち、mark(),reset()が使えないということです。

とすると、どうすればいいかというと、再生位置を先頭に戻すには、一度close()してから再度open()するという処理しかなさそうです。さらに任意の位置に移動させるには、skip()を使うという方法しかないようです。

2007年4月18日水曜日

EclipseでJNIをデバッグする

前回、EclipseでJNIの開発について書いたが、その後デバッグをするにはどうするのかというのをゴリゴリとやっていたが、例のごとくはまってしまってので、忘れないように書いておくことにする。 まずは、JNIモジュールのデバッグは次のような基本手順を踏むということする。

  1. JNIのソースにブレイクポイントを設定
  2. 呼び出すJavaソースにブレイクポイントを設定
  3. Javaをデバッグ実行
  4. JNIモジュールをデバッガでアタッチ

まずはJavaのブレイクポイントは、System.LoadLibrary();を呼び出した後でかつJNIのブレイクポイントが呼び出される前に設定する。Javaのデバッグ実行でこの設定したブレイクポイントで止まった状態で、JNIモジュールをアタッチする。

JNIモジュールのデバッグ設定は、Eclipseの「構成およびデバッグ」のダイアログで、「ローカルアプリケーションへの接続」という構成で設定できる。このとき、アタッチの対象となるモジュール(ここではWin32なのでDLL)を「メイン」のC/C++アプリケーションというところに書くのだが、ここにはビルドしたモジュールを設定する(デフォルトだと「デバッグ/<DLLのファイル名>」)。

あと、「デバッガー」の項目のところは、「gdb/mi」を選択して、gdbのコマンドのところは、実行できる適切なパスが設定されていればよい。あと、このときに「詳細コンソールモード」というチェックをONにしておくと、デバッガーが何をしているのかわかるので最初のうちはONにしておいたほうがよいだろう。

この設定で指定したモジュール(DLL)がJavaから呼び出されないとアタッチできないので、Javaのデバッグ構成の管理で、引数のところでプログラムの引数かVMの引数のところで、「-Djava.library.path=<デバッグするモジュールのパス>」を設定をする。

これでEclipseからの操作でデバッグができる。DLLのデバッグで、gdbのアタッチ後に中断状態になるが、そのまま再開すればモジュールは実行される。

あとは、MinGWの固有だと思われるが、gdbのバージョンが古いとEclipseから投げるMIのコマンドの不一致か、ブレイクポイントがうまく設定されないようだった。そのため、MinGWからgdb6.xをダウンロードしてインストールすればよいようだ。

もうひとつ、これはうちの固有の問題かもしれないが、gdbでデバッグしようとすると「LdrAccessResource」のところでセグメンテイションエラーが発生して、gdbが落ちてしまう。これについては、いろいろ調べているものの解決方法がわからず、現状デバッグ不能である。どうやら、ntdllとgdbの相性の問題のようで詳細はわかりませんでした。

2007年4月12日木曜日

EclipseでJNIモジュールの開発をする

プログラミングTIPSではないのですが、EclipseでJNIのモジュールを開発を試みたときにプロジェクトをどう設定すればよいかという話です。要するにちょっと設定にはまったので、忘れないようにメモっておこうということです。ただし、以下の方法はベストな方法とは思ってません。とりあえず、こうやったらできましたっていう程度のものだと思ってください。ちなみにですが、Win32でEclipseとMinGWを使って開発するというお話しです(他のプラットホームでは違うところがあるかもしれません)。

蛇足かもしれませんが、JNIの開発そのものは、JDKのドキュメント(Java5のJDKのリンクですが)に載っていて、MinGWでJNIのモジュールをコンパイルするにはどうするかというのは、MinGWのFAQに書いてあります。

まずは、JNIのモジュールを開発するには、nativeの宣言を含むJavaのソースをまず書いてコンパイルする。classファイルからjavahでC(++)のヘッダーを出力して、C(++)のコードを書いてコンパイルする。出来上がったモジュールをJavaから実行させる、という手順であると思っていることが前提となります。

何が話題かというと、JavaのモジュールはJavaのプロジェクトで開発して、JNIのモジュールはCDTを使って、C(++)のプロジェクトで開発するのですが、javahで作られるヘッダーをどうやって、C(++)のプロジェクトへもってくるか、できたモジュールをどこにコピーして、実行するかということです。

C(++)のプロジェクトのほうがビルドの設定の自由度が高いと踏んで、C(++)のプロジェクト側でがんばるということにします。依存関係としては、C(++)のプロジェクトがJavaのプロジェクトを参照しているというふうにします(お互いに参照していてもよいとは思います)。

C(++)のプロジェクトの設定で「C/C++ビルド」という項目の中に、ビルドステップというのあって、ここにビルドの前後に何かコマンドが実行できるので、これを利用することにします。 まずは、javahをビルド前に実行するためにビルド前のステップのところに以下のように入力します。

<JDKのフルパス>\bin\javah -jni -d .. -classpath ../../<Javaのプロジェクト名> <パッケージ名>.<クラス名>

※もし、「\」がバックスラッシュに見えたら、「¥」の半角だと思ってください このとき、 「<パッケージ名>.<クラス名>」は、複数記述することができます。

これで、JavaのプロジェクトからJNIのモジュールを読み込んで実行できるわけです(もちろん、何かバグってたらだめですが…)。デバッグや実行もEclipseからやりたいので、「構成および実行」の設定をして、Javaを実行させればよいのですが、JNIのモジュールがjava.library.pathにないと実行できません。 そのときにどうするかというと、JNIモジュールを読み込むjavahを実行したJavaプロジェクトとは別に、これらを使うJavaプロジェクトを作成します。 この新しいほうのプロジェクトのビルドの構成の「プロジェクト(P)」でJNIのJavaプロジェクトを参照するように設定すれば、実行できます。この設定に「ネイティブ・ライブラリーのロケーション」というサブツリーがあると思いますが、ここでJNIのネイティブモジュール(Win32だとDLL)のパスを設定すればよいです。

リリースしたときにjava.library.pathの設定をどうするのかというところは気になるところですが、VMの実行時にパラメタで渡さなくても、いわゆるpath変数で見つけられるところにモジュールがあれば実行できるようです。

2007年4月10日火曜日

JavaでWAVファイルを再生する

javax.sound.sampledというパッケージを使って、とりあえずWAVファイル(AIFFなども可能)をサウンドデバイスに出力する。とりあえずということで、前提と条件は以下の通り

  • デフォルトのデバイスに出力する
  • 制御は、開始、一時停止、再開だけ

とりあえず、いきなりソースコード。

public class RenderAudioFile implements LineListener, Runnable {
 protected AudioInputStream fileStream = null;
 protected SourceDataLine sourceDataLine = null;
 private byte sampleBuffer[];//データ受け渡し用のバッファ

 public void openFile(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
  //出力するファイルを受け取るメソッド
  fileStream = AudioSystem.getAudioInputStream(file);
  AudioFormat format = fileStream.getFormat();
  sourceDataLine = AudioSystem.getSourceDataLine(format);
  sourceDataLine.addLineListener(this);
  sourceDataLine.open(format);
  sampleBuffer = new byte[sourceDataLine.getBufferSize()];
 }
 public void play() {
  // 再生開始、再開用メソッド
  if(sourceDataLine == null) {
   return;
  }
  sourceDataLine.start();
  new Thread(this).start();
 }
 public void pause() {
  // 一時停止メソッド
  if(sourceDataLine == null) {
   return;
  }
  sourceDataLine.stop();
 }
 public void update(LineEvent ev) {
  // LineEvent用ハンドラ UI制御用に使うとよい
  LineEvent.Type type = ev.getType();
  if( type == LineEvent.Type.OPEN) {
  }
  else if( type == LineEvent.Type.START) {
  }
  else if( type == LineEvent.Type.STOP) {
  }
 }
 public void run() {
  //再生用のスレッド用メソッド
  int readSize = 0;
  do {
   try {
    readSize = fileStream.read(sampleBuffer);
   } catch (IOException e) {
    sourceDataLine.stop();
    break;
   }
   if (readSize < 0) {
    sourceDataLine.stop();
    break;
   }
   }
   sourceDataLine.write(sampleBuffer, 0, readSize);
  } while (sourceDataLine.isRunning());
 }
}

とりあえず、こんな感じです。大したコードでなくて申し訳ないですが、いくつかポイントがあるので、説明させていただきますと。

public void update(LineEvent ev);ですが、いらないときはメソッドごと消してもらって、implementsからLineListenerを消して、、sourceDataLine.addLineListener(this);も消してください。あと、このイベントハンドラーで「START」がくるタイミングですが、sourceDataLine.start();を呼び出した時には呼ばれずに、sourceDataLine.write();でデーターを書き込んだときに呼ばれるようです。UIの制御にこれを使ったほうがよいかというと、startやstopの処理が完了するとこれらのイベントが呼ばれてくるので、連打されたりしたときにおかしなことにならないようにこのタイミングで制御したほうがよいかと思われるからです。

あと、とりあえずで手を抜いてしまってあるところがあるので、白状しておきますが。sourceDataLine.write();はstopが呼ばれると書き込みのブロッキングを解除して復帰してくるのですが、このときの戻り値に書き込みされたデーターのサイズとなります。この戻り値が渡したバッファーのサイズより小さいときには、その差分のデーターは書き込みされなかったわけで、本当なら再開されたときにその分のデーターを書き込んでから、ファイルから読み込むという処理をしないと、そのデーターはスキップされて再生されるということになります。

これを回避するには、この戻り値をメンバー変数にとっておいて、再度スレッドが呼ばれたときにファイルから読み込まず、残りのデーターを渡すという処理を入れればよいでしょう。

2007年4月8日日曜日

Java3Dをやってみる

Java3Dは、SunからJREと同じように配布されているライブラリであるが、JREやJDKには含まれてはいない。どうやら、大きすぎていっしょにいれなかったようだ。 まずプログラムするには、Java 3D APIへいって、APIをダウンロードしてインストールするわけだが、JDKのバージョンもあわしておく必要があるようなので、ついでに最新のJDKを入れておくのがよいかもしれない。APIドキュメントもダウンロードできるが、オンラインでも見れるので、お好みで入れればいいかも。

APIドキュメントだけでは、途方にくれるだけなのでjava.netのJava3DのWikiにあるJava3DBooksにチュートリアル(英語)があるのでそれを読むか、日本語の書籍も出ているようなのでそれを読めばよいのだが…。

とりあえず、何か動かしたいのであれば、チュートリアルのChapter1に載っているソース(HelloJava3D)を打つか、Java3DにおいてあるExampleを動かしてみるとかでよいかもしれない。

とりあえず、おそらくこの段階でわかっておくことは、Java3Dのすべてのオブジェクトはツリー構造で定義される。3次元オブジェクトをツリー構造で記述することは、普通なことなのであえて説明は不要であるとおもうが。 ツリー構造の根っこにあるのが、VirtualUniverseというものであるのだが、そこに3次元のオブジェクト(ドキュメントではSceneGraphといってる)やそれらを表示するためのCanvas3Dも含まれている。

3次元で表示するためには視線(投射法の概念も含むのでカメラというようだが)を定義するためのViewやViewPlatformなどが必要なのだが、VirtualUniverseやViewとかをひっくるめてSimpleUniverseがやってくれるようだ。 すなわち、表示するためのCanvas3Dを作って、それを渡してSimpleUniverseを作成する。あとは、3次元オブジェクトの類をSimpleUniverseに渡すとCanvas3Dに表示される(もちろん、視線に入るものだけだが)ということである。

オブジェクトの定義は、ドキュメントのHelloJava3DだとColorCubeというものを表示しているが、com.sun.j3d.utils.geometryにあるConeやCylinder、Sphereなどを使えば、とりあえず3Dっぽいものは表示できる。今回はとりあえずなので、Geometryについてはいずれまた…。