0

OpenCVで画像のサイズを求めるgearman workerを作って、Rubyから呼ぶ

ファイル名を渡すと画像サイズを返すgearman workerを作った。

{"width":1600, "height":1200}
という風にJSON風に値を返す。


以前画像のだいたいの色を求めるgearman workerを作ってたんだけど、C++で書いたworker側を単独で動作テストする事ができなくてどうしようか悩んだ。テスト用のclientと同時に作らなければならなくて、どちらにバグがあるのか切り分けるのが面倒だった。

そこで、workerの起動時に
./imgsizeWorker --test "/path/to/imagefile.jpg"
という風にgearman clientから来る引数と同じ形式で渡すと、単体のプログラムとしても動作チェックできるようにした。


こうすると普通にworkerとして起動する。
./imgsizeWorker -s localhost -p 7003
これはboost::program_optionsでやると便利だった。
今後はこのコードを雛形にすればC++とOpenCVで高速に画像解析して、gearmand経由でスクリプト言語から呼び出しまくれる。


■使ったライブラリ
boostライブラリを中心にいろいろ使った。

opencv1.0は画像サイズを取得する為だけに使った。GUIを使って無いのにcvLoadImage()のためだけにhighgui.hを読み込んでいる。

boost::tupleとboost::tieを使うと返り値を複数返す関数が作れるので、画像の情報を返すのに便利。そういえばRubyでは普通に使ってたけどC#3.5やAS3.0ではタプル無かったな。欲しい。ASはArrayに何でも入れやすいからいいか。
boost::tupleで多値を受け取る – 橋本詳解
boost::tieでtupleを展開 – 橋本詳解

boost::formatでprintf風にstd::stringをフォーマット。
boost::program_optionsで引数をparseする。
boost::program_optionsでコマンドライン引数を読む – 橋本詳解

rubyはxing-gearman-rubyを使った。
橋本商会 Cでgearman workerを書いてRubyのclientから呼び出す


■プログラム
libgearmanのgearman_worker_add_serverはIPアドレスを渡さないとならないので、一応ホスト名を解決するようにした。

imgsizeWorker.cpp
// 画像サイズを返すgearman worker
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/program_options.hpp>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <libgearman/gearman.h>

using namespace boost;
using namespace std;

tuple<int, int> get_size(string fileName); // 画像のwidth,heightを返す
string imgsize(string fileName); // gearman workerとしてclientに返すstringに整形する
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);

int main(int argc, char* argv[]) {
  program_options::options_description opts("options");
  opts.add_options()
    ("help,h", "helpを表示")
    ("server,s", program_options::value<string>(), "gearmanサーバーのアドレス")
    ("port,p", program_options::value<int>(), "gearmanサーバーのport番号")
    ("test,t", program_options::value<string>(), "gearman worker単体テスト用query");
  program_options::variables_map argmap;
  program_options::store(parse_command_line(argc, argv, opts), argmap);
  program_options::notify(argmap);

  if(!argmap.count("help")){
    if(argmap.count("test")){
      cout << "---test---" << endl;
      string gearman_param = argmap["test"].as<string>();
      imgsize(gearman_param); // 単体でworkerとしてのテスト
      return 0;
    }else if(argmap.count("server") && argmap.count("port")){
      gearman_worker_st worker;
      gearman_worker_create(&worker);
      string g_server = argmap["server"].as<string>();
      int g_port = argmap["port"].as<int>();

      struct hostent *g_host = gethostbyname((char*)g_server.c_str());
      string g_server_addr = str(format("%d.%d.%d.%d") %
 (uint)(uchar)g_host->h_addr[0] %
 (uint)(uchar)g_host->h_addr[1] %
 (uint)(uchar)g_host->h_addr[2] %
 (uint)(uchar)g_host->h_addr[3]);

      gearman_worker_add_server(&worker, g_server_addr.c_str(), g_port);
      gearman_worker_add_function(&worker, "img_size", 0, job_imgsize, NULL);
      cout << str(format("---start worker (%s:%d)---") %
  g_server_addr % g_port) << endl;
      while(true) gearman_worker_work(&worker); // workerとして待機
      return 0;
    }
  }
  cerr << "server,portが必要です" << endl;
  cerr << opts << endl;
  return 1;
  
}

// opencvで画像サイズを取得
tuple<int, int> get_size(string fileName){
  IplImage *img = cvLoadImage(fileName.c_str());
  if(!img){
    return make_tuple(-1, -1);
  }
  else{
    return make_tuple(img->width, img->height);
    cvReleaseImage(&img);
  }
}

// 画像サイズを取得してgearman serverに返すstringに整形する
string imgsize(string fileName){
  string result_str = "";
  int width, height;
  tie(width, height) = get_size(fileName);
  if(width > 0 && height > 0){
    result_str += str(format("{\"width\":%d, \"height\":%d}")
    % width % height);
  }
  else{
    result_str = "error : image load error";
  }
  cout << fileName << " => " << result_str << endl;
  return result_str;
}

// gearman worker job
void *job_imgsize(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
  string fileName = (char*)gearman_job_workload(job);
  string result_str = imgsize(fileName);
  
  char *result = (char*)strdup(result_str.c_str());
  *result_size = result_str.size();
  *ret_ptr = GEARMAN_SUCCESS;
  return result;
}


Makefile
# Mac用Makefile
SRC = imgsizeWorker.cpp
DST = imgsizeWorker

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a

GEAR_INCPATH=/usr/local/include
GEAR_LIBPATH=/usr/local/lib
GEAR_LIBS=$(GEAR_LIBPATH)/libgearman.a

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS) -I$(GEAR_INCPATH)/libgearman -L. -L$(GEAR_LIBPATH) $(GEAR_LIBS)


client側。workerからの返り値の先頭にerrorが書いていなかったらJSONとしてparseする
testclient.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'gearman'
require 'json'

if ARGV.size < 1
  puts '画像へのパスが必要'
  puts 'ruby testclient.rb ~/path/to/images/*.png'
  exit 1
end

c = Gearman::Client.new(['localhost:7003'])
taskset = Gearman::TaskSet.new(c)

ARGV.sort{|a,b| a.split(/\//).last.to_i <=> b.split(/\//).last.to_i}.each{|name|
  puts "add task #{name}"
  task = Gearman::Task.new("img_size", name+"\0")
  task.on_complete{|result|
    if !(result =~ /^error/)
      print "return: #{name} => "
      p JSON.parse(result) 
    else
      puts "return: #{name} => #{result}"
    end

  }
  taskset.add_task(task)
}
taskset.wait(100) # wait 100(sec)


&をつけていくつかworkerを起動する
./imgsizeWorker -s localhost -p 7003&
./imgsizeWorker -s localhost -p 7003&


clientからtask登録。フォルダ内のjpgファイルを全部登録する
ruby testclient.rb ~/Pictures/selected/*.jpg
サイズが返ってくる
add task /Users/sho/Pictures/selected/a66dab3a.jpg
add task /Users/sho/Pictures/selected/3ed6f38e.jpg
add task /Users/sho/Pictures/selected/77ab53f0.jpg
add task /Users/sho/Pictures/selected/889bd644.jpg
add task /Users/sho/Pictures/selected/73177294.jpg
return: /Users/sho/Pictures/selected/a66dab3a.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/3ed6f38e.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/77ab53f0.jpg => {"height"=>1200, "width"=>1600}
return: /Users/sho/Pictures/selected/889bd644.jpg => {"height"=>800, "width"=>1280}
return: /Users/sho/Pictures/selected/73177294.jpg => {"height"=>1200, "width"=>1600}

0

OpenCVで画像のだいたいの色を求めるgearman workerを作って、Rubyから呼び出す

画像のだいたいの色を求めるをgearman worker化した。
画像ファイル名をテキストで投げると

{width: 136, height: 147, r: 249, g: 234, b:230}
という形式で画像のサイズとだいたいの色を返す。


Cでworkerを書いてRubyで呼び出す方法は橋本商会 Cでgearman workerを書いてRubyのclientから呼び出すに書いたとおり。
これでOpenCVの解析をgearman workerとして複数並列に動かしてそれをRubyから使いまくるというのができるワハハ


C++なのはboost::regex_splitでも使って引数を受け取ろうかと思ってたんだけど、そもそもファイル名だけしか受け取らなかったから意味がなかった。
imgcolorWorker.cpp
// 画像のだいたいの色とサイズを返すgearman worker
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <boost/regex.hpp>
#include <boost/format.hpp>
#include <libgearman/gearman.h>

using namespace boost;
using namespace std;
IplImage *img, *img1px, *imgR, *imgG, *imgB;

void *job_imgcolor(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr);

int main(int argc, char* argv[]) {
  img1px = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 3);
  imgR = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
  imgG = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);
  imgB = cvCreateImage(cvSize(1,1), IPL_DEPTH_8U, 1);

  gearman_worker_st worker;
  gearman_worker_create(&worker);
  gearman_worker_add_server(&worker, "127.0.0.1", 7003);
  gearman_worker_add_function(&worker, "img_color", 0, job_imgcolor, NULL);

  while(true) gearman_worker_work(&worker);
  return 0;
}

void *job_imgcolor(gearman_job_st *job, void *cb_arg, size_t *result_size, gearman_return_t *ret_ptr){
  string fileName = (char*)gearman_job_workload(job);
  string result_str = "";
  IplImage *img = cvLoadImage(fileName.c_str());
  if(!img){
    result_str += "{error: image load error}";
  }
  else{
    cvResize(img, img1px, CV_INTER_CUBIC);
    cvSplit(img1px, imgB, imgG, imgR, NULL);
    result_str += str(format("{width: %d, height: %d, r: %d, g: %d, b:%d}")
      % img->width % img->height %
      (uint)(uchar)imgR->imageDataOrigin[0] %
      (uint)(uchar)imgG->imageDataOrigin[0] %
      (uint)(uchar)imgB->imageDataOrigin[0]);
    cvReleaseImage(&img);
  }
  cout << fileName << " => " << result_str << endl;
  
  char *result = (char*)strdup(result_str.c_str());
  *result_size = result_str.size();
  *ret_ptr = GEARMAN_SUCCESS;
  return result;
}
最後の*result_sizeに返り値のサイズを入れておかないと、client側には壊れた文字列が来る。
boost::formatはprintfのフォーマットと違ってunsigned charを数値としてそのまま使えなかったので2回キャストした。


Makefile
# Mac用Makefile
SRC = imgcolorWorker.cpp
DST = imgcolorWorker

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a $(LIBPATH)/libboost_regex-mt.a

GEAR_INCPATH=/usr/local/include
GEAR_LIBPATH=/usr/local/lib
GEAR_LIBS=$(GEAR_LIBPATH)/libgearman.a

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS) -I$(GEAR_INCPATH)/libgearman -L. -L$(GEAR_LIBPATH) $(GEAR_LIBS)



gearman client。ワイルドカードで指定して画像ファイルをひとつずつtask登録する
imgcolorClient.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'gearman'
require 'json'

if ARGV.size < 1
  puts '画像へのパスが必要'
  puts 'ruby imgcolorClient.rb ~/path/to/images/*.png'
  exit 1
end

c = Gearman::Client.new(['localhost:7003'])
taskset = Gearman::TaskSet.new(c)

ARGV.sort{|a,b| a.split(/\//).last.to_i <=> b.split(/\//).last.to_i}.each{|name|
  puts "add task #{name}"
  task = Gearman::Task.new("img_color", name+"\0")
  task.on_complete{|result|
    puts "return: #{name} => #{result}"
  }
  taskset.add_task(task)
}
taskset.wait(100) # wait 100(sec)
task登録する時に、argmentの末尾に”\0″を付けておかないとworker側で受け取った時におかしくなる事がある。



workerを2つ起動しておいて、画像をいくつか投げてみる
make
./imgcolorWorker&
./imgcolorWorker&
ruby imgcolorClient.rb ~/Pictures/test/*


workerが2つあるので、結果は順番には返ってこない。
add task /Users/sho/Pictures/test/実装力不全.gif
add task /Users/sho/Pictures/test/そうだ旧都へ行こう.jpg
add task /Users/sho/Pictures/test/zanmai.jpg
add task /Users/sho/Pictures/test/wiring.jpg
return: /Users/sho/Pictures/test/実装力不全.gif => {error: image load error}
add task /Users/sho/Pictures/test/ed93d6de.jpg
add task /Users/sho/Pictures/test/f9286cff.jpg
add task /Users/sho/Pictures/test/fae75abe.jpg
add task /Users/sho/Pictures/test/img278_s201.jpg
add task /Users/sho/Pictures/test/la-metro.jpg
add task /Users/sho/Pictures/test/wiring-s.jpg
add task /Users/sho/Pictures/test/org19361.jpg
add task /Users/sho/Pictures/test/org19364.jpg
add task /Users/sho/Pictures/test/org19375.jpg
add task /Users/sho/Pictures/test/label.png
add task /Users/sho/Pictures/test/246.png
add task /Users/sho/Pictures/test/247.png
add task /Users/sho/Pictures/test/248.png
add task /Users/sho/Pictures/test/249.png
return: /Users/sho/Pictures/test/zanmai.jpg => {width: 1024, height: 768, r: 22, g: 11, b:7}
return: /Users/sho/Pictures/test/そうだ旧都へ行こう.jpg => {width: 1440, height: 1046, r: 21, g: 21, b:20}
return: /Users/sho/Pictures/test/ed93d6de.jpg => {width: 1920, height: 1200, r: 45, g: 33, b:26}
return: /Users/sho/Pictures/test/wiring.jpg => {width: 2106, height: 1584, r: 26, g: 24, b:31}
return: /Users/sho/Pictures/test/f9286cff.jpg => {width: 1920, height: 1200, r: 88, g: 80, b:72}
return: /Users/sho/Pictures/test/fae75abe.jpg => {width: 1920, height: 1200, r: 21, g: 15, b:22}
return: /Users/sho/Pictures/test/img278_s201.jpg => {width: 1024, height: 768, r: 21, g: 92, b:107}
return: /Users/sho/Pictures/test/la-metro.jpg => {width: 1024, height: 768, r: 128, g: 118, b:111}
return: /Users/sho/Pictures/test/wiring-s.jpg => {width: 1024, height: 770, r: 28, g: 24, b:32}
return: /Users/sho/Pictures/test/org19361.jpg => {width: 1280, height: 800, r: 0, g: 0, b:0}
return: /Users/sho/Pictures/test/org19375.jpg => {width: 1024, height: 633, r: 235, g: 235, b:235}
return: /Users/sho/Pictures/test/org19364.jpg => {width: 1024, height: 681, r: 217, g: 217, b:217}
return: /Users/sho/Pictures/test/label.png => {width: 100, height: 80, r: 255, g: 255, b:255}
return: /Users/sho/Pictures/test/247.png => {width: 140, height: 151, r: 246, g: 235, b:233}
return: /Users/sho/Pictures/test/248.png => {width: 138, height: 149, r: 247, g: 234, b:232}
return: /Users/sho/Pictures/test/246.png => {width: 141, height: 153, r: 246, g: 237, b:236}
return: /Users/sho/Pictures/test/249.png => {width: 136, height: 147, r: 249, g: 234, b:230}


ちなみにCore2 DuoのMacbookProなので、workerを1つよりも2つ起動していた方が1.7倍ぐらい速かった。

引数を複数渡したい時は、適当に渡してworker側でboost::regex_splitでも使うか、getoptやboost::program_optionsでparseできるようにして渡せばいいかな
client側での受け取りはRubyだからどうにでもできる。

workerでエラーが起きた時の例外処理の出し方がよくわからない。ret_ptrにGEARMAN_SUCCESS以外を入れて返すべきなのか?

0

Cでgearman workerを書いてRubyのclientから呼び出す

gearmandをソースからインストールして、Cライブラリのlibgearmanが使えるようになった。

これでC言語でworkerを作ってRubyのclientから呼び出せる。環境はUbuntu9.04とgearmand0.11


■Cでworkerを書く
str_reverseというアビリティを持つworkerを作る。
ほぼAPIドキュメントのままだが、jobの引数を受け取るのと、値を返せるようにがんばった。ジョブ失敗した時とかのエラー処理全然書いてない。たぶんenum gearman_return_tを返せばいいんだろうけど

strreverse-worker.c

// 文字列をreverseして返すworker
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgearman/gearman.h>

void *job_str_reverse(gearman_job_st *job, void *cb_arg, size_t *result_size,
       gearman_return_t *ret_ptr)
{
  char *str = (char *)gearman_job_workload(job);
  int len = strlen(str);
  printf("str:%s  length:%d\n", str, len);

  char reverse[len];
  for(int i = 0; i < len; i++){
    reverse[i] = str[len-i-1]; // 文字列を逆にする
  }

  char *result = strdup(reverse); // 結果の文字列はコピーしてポインタで返す
  *result_size= gearman_job_workload_size(job);
  *ret_ptr= GEARMAN_SUCCESS;
  return result;
}


int main(int argc, char *argv[])
{
  gearman_return_t ret;
  gearman_worker_st worker;

  gearman_worker_create(&worker);
  gearman_worker_add_server(&worker, "127.0.0.1", 7003);
  gearman_worker_add_function(&worker, "str_reverse", 0, job_str_reverse, NULL);
  
  while(1) gearman_worker_work(&worker); // ジョブ登録したらループで待つ

  gearman_worker_free(&worker);
  return 0;
}


Makefile
# Linux用Makefile
SRC = strreverse-worker.c
DST = strreverse-worker

prefix=/usr/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

GEAR_LIBS=$(LIBPATH)/libgearman.a

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/libgearman -L. -L$(LIBPATH) $(GEAR_LIBS)
makeするとstrreverse-workerができる。


■Rubyでclientを書く
xing-gearman-serverを使うといい

client書く。実行時引数を一つずつstr_reverseに登録するclient。
strreverse-client.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'gearman'

c = Gearman::Client.new(['localhost:7003'])
taskset = Gearman::TaskSet.new(c)

ARGV.each{|str|
  puts "add task #{str}"
  task = Gearman::Task.new('str_reverse', str)
  task.on_complete{|result|
    puts "return: #{str} => #{result}" # 文字列が逆になって返ってくる
  }
  taskset.add_task(task)
}
taskset.wait(100) # wait 100(sec)


■動かしてみる
worker動かす
./strreverse-worker

clientから3つ文字列登録
ruby strreverse-client.rb hello konitiwa asdfhujiko

すると文字列返ってくる
add task hello
add task konitiwa
return: hello => olleh
add task asdfhujiko
return: konitiwa => awitinok
return: asdfhujiko => okijuhfdsa

worker側の標準出力はこうなってた
str:hello  length:5
str:konitiwa length:8
str:asdfhujiko length:10


workerを3つバックグラウンドで起動しておく
./strreverse-worker&
./strreverse-worker&
./strreverse-worker&


凄い勢いで処理されてるので非同期になってるのかよくわからない
ruby strreverse-client.rb hello konitiwa asdfhujiko aaiiaaiiaaii hogehogehogehoge mmmasdffajkl23rwdfv ahsdga9sd8uf9p8u nbjkasdoif98pu
add task hello
add task konitiwa
return: hello => olleh
add task asdfhujiko
return: konitiwa => awitinok
add task aaiiaaiiaaii
return: asdfhujiko => okijuhfdsa
add task hogehogehogehoge
return: aaiiaaiiaaii => iiaaiiaaiiaa
add task mmmasdffajkl23rwdfv
return: hogehogehogehoge => egohegohegohegoh
add task ahsdga9sd8uf9p8u
return: mmmasdffajkl23rwdfv => vfdwr32lkjaffdsammm
add task nbjkasdoif98pu
return: ahsdga9sd8uf9p8u => vfdu8p9fu8ds9agd
return: nbjkasdoif98pu => vfdu8up89fiods

1

Rubyでwavファイルをいじる WavFile.rbを作った

gemにしました → 橋本商会 wavファイルをRubyで編集する

*****

Rubyでwavファイルを操作するためにWavFile.rbを作った。スピーカから音を鳴らすのではなくて、wavファイルそのものをいじって合成したりつなげたり、逆再生や左右反転させたりした後ファイルに書き出す為に作った。

packやunpackを使ってRubyでバイナリを読み書きする部分でかなり苦戦したけど、WAVファイル – MoonRock@MoonRock/A mere diary (2002-2)(7年も前に同じような事やってる!)がすごく参考になった。attr_accessorとかも知らなかったから勉強になった。


http://shokai.org/projects/ruby-wavfile/にサンプルを色々置いておく。


例えば、逆再生のwavファイルを作るコードはこう書ける
reverseWav.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# wavファイルを逆再生にして保存する
# ステレオの場合、左右チャンネルが入れ替わってしまうがまあいい
require File.dirname(__FILE__) + '/WavFile'

if ARGV.size < 2
  puts 'ruby reverseWav.rb input.rb output.wav'
  exit 1
end

f = open(ARGV.shift)
format, chunks = WavFile::readAll(f)
f.close

puts format.to_s

dataChunk = nil
chunks.each{|c|
  puts "#{c.name} #{c.size}"
  dataChunk = c if c.name == 'data' # 波形の入っているchunkを探す
}
if dataChunk == nil
  puts 'no data chunk'
  exit 1
end

# 波形をいじる
bit = 's*' if format.bitPerSample == 16 # int16_t
bit = 'c*' if format.bitPerSample == 8 # signed char
wavs = dataChunk.data.unpack(bit) # 16bit or 8bitずつbinaryから読み出し
dataChunk.data = wavs.reverse.pack(bit) # 逆再生、binaryに戻す

open(ARGV.shift, "w"){|out|
  WavFile::write(out, format, [dataChunk])
}
波形部分をRubyの配列として取り出して処理する。ファイルに戻す部分はWavFile.rbがやってくれるようにした。

実行
ruby reverseWav.rb test.wav reverse.wav
結果
フォーマットID: 1
チャンネル数: 1
サンプリングレート: 8000 (Hz)
byte per sec: 16000
bit per sample: 16
ブロックサイズ: 2
data 4077172



主にできる事と、処理の手順はこんな感じ
  1. wavファイルのファイルヘッダ、フォーマットチャンク、データチャンクをメモリに読み込む。WavFile.rbではファイルヘッダとフォーマットチャンクをまとめて管理するためにWavFile::Formatクラスを作ってある。
  2. データチャンクを波形として扱う処理は時前でやってください。ステレオ・モノラルやbpsなどのフォーマットが全て読み込まれているのでそんなに大変ではないはず。format.bitPerSampleやunpackを使う。ただしCPUのエンディアンが違うとおかしくなるかも。
  3. 最後にいじったデータチャンクをpackでバイト列に戻して、ファイルヘッダと合わせてwavファイルに戻す。


上の例ではWavFile::readAllを使ってフォーマットと全チャンクを読み込んでいるけど、wavファイルにはデータチャンク(波形)以外のチャンクもある。でもデータチャンクとフォーマットさえあれば後は必要ない場合が多いので、フォーマットとデータチャンクのみを取り出す関数も用意してある。
require 'WavFile'
File.open("test.wav"){|file|
  format = WavFile::readFormat(file)
  dataChunk = WavFile::readDataChunk(file)
}

タプルを使ってこう受け取る事もできる
format, dataChunk = WavFile::read(file)

wavファイルへ保存
open("out.wav"){|out|
  WavFile.write(out, format, [dataChunk])
}


他にも色々やった。下にいくほど新しい。
上の方はWavFile.rbのバージョンが古い頃の物なので読み書きまわりが少し違うかもしれないが、解説が書いてあるので列挙しておく。最新のWavFile.rbで動くコードはリポジトリに置いておく


wavファイルの扱いについては、C言語で書かれたこの本を参考にした。この本ではファイルを先頭からseekして逐次処理して結果をwavファイルとして書き込んでいる。
でもwavファイルなんて数百MB程度だから、WavFile.rbではメモリ上で処理した方が後々便利そうだから今回は富豪的に全部メモリに読み込むようにした。
WAVプログラミング—C言語で学ぶ音響処理
北山 洋幸
カットシステム
売り上げランキング: 160207
おすすめ度の平均: 3.0
2 C言語初心者向けでわかりやすいが本格的な音響処理はいまいち
3 分かりやすいが…
4 あまり見かけないジャンルの本ですね



作ってる時に大変だったのは音を出さないとデバッグできないので電車の中で作業するためにイヤホンは必須。
バイナリとRubyのオブジェクトとのやりとりの部分は音で聞いてもなんだかわからない時もあるので、putsで波形を数字としてdumpしてエクセルで描画すると原因がすぐわかる。

0

iPhoneからMac経由でdocomoケータイにアドレス帳をコピー

docomoの普通の携帯電話を買ったが、iPhoneからdocomoへのアドレス帳移行の手段が無い。

windowsのiTunesでiPhoneから読み込み→csv形式でドコモdatalinkで携帯電話に転送
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1124847736
という手段があるが、iTunesで読み込む時にoutlookがなければ駄目。windowsアドレス帳形式はドコモdatalinkで転送できない。


今回はiPhone→MacのiTunesでAddress Bookに読み込み→「グループのvcardを書き出し(vcf形式で)」→メールで携帯電話に転送
という手段で移行した。

ただし

  • 検索しやすいようにiPhoneでは読み仮名をローマ字で入力してたが、docomoはカタカナのみ対応
  • vcf形式は1ファイルに複数のvcardを保持できるはずなのに、SH-02Aは先頭のvcardしか読み込んでくれない
  • メールに同時に添付できるファイル数は10個まで

なので読み仮名は全部無くなったし、10個ずつ添付して登録したから面倒だった。


まず、一つのvcfファイルに複数のアドレス帳が入っていても全部読み込める機種なら、MacのAddress Bookからvcfで書き出して
nkf -s phone-addr.vcf > phone-addr-sjis.vcf
でshift jisに文字コードを変えれば読み込めるはず。(そういう機種があるならば)


ファイルを分割するためにスクリプトを書いた
convert-vcard-mac-docomo.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'kconv'

if ARGV.size < 2
puts 'error! 入力ファイルと出力先ディレクトリ名を指定してください'
puts 'ruby convert-vcard-mac-docomo.rb /path/to/input/file.vcf /path/to/output/dir/'
exit 1
end
input = ARGV.shift
output = ARGV.shift
puts output += "/" if !(output =~ /\/$/)

delimiter = "BEGIN:VCARD"
cards = open(input).read.split(/#{delimiter}/m)
cards.shift # 1件目いらない

for i in 0...cards.size do
puts card = delimiter+cards[i]
puts "-"*10
open(output+"#{i}.vcf","w"){|f|
f.puts card.tosjis
}
end

mkdir ~/vcards
ruby convert-vcard-mac-docomo.rb ~/phone-addr.vcf ~/vcards/
で ~/vcards/にたくさんファイルができるので、10件ずつメールに添付して送って登録した。
指が憑かれた。


後で気づいたが、読み仮名はsjisで
SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:ʼヨミガナ;;;;
と書いておくと読み込めるみたい。
俺の場合ローマ字→カタカナに変換しないとならなかったので面倒だが、suikyoを使えば楽にできるかもしれない。