Androidで写真を撮って、それを音データに変換し、スピーカーで再生すると同時にマイクで録音し、もう一度、画像に戻してみる、それを一連にやってくれるアプリです。

出来上がる写真はこんな感じです。レナさんです。

所謂、現実フィルタというものをやってみたつもりです。

実際、もう少しはっきりした写真で、周りの音に反応してフィルタが、、、という展開を期待したのですが。
(追記 「マイクやスピーカーで20Hz以下の周波数がカットされるから周波数が高い部分しか写真に表示されないんだ」と。なるほど。そうなるとあれか、、、。)
ただまあAndroidを振ったりすると歪みが出たりはしますが。

動機

アラブの春の時にエジプト政府がインターネットを遮断したことがありました。するとtwittergoogleが電話を使ったサービスを開始します。電話口から写真を音に変えて送れるなと思ったわけです。あれ、そうだったかな。ただただ平安を祈るばかりです。このアプリは44100Hzなんで電話では使えないけど。

改善
そもそもカメラアプリの作り方がわかっていないので修業が必要。

ナイキスト周波数の関係で半分RGBAの要素が反映されていないところです。(追記 ちょっとずるだが修正した)

音声を再生するAudioTrackのバッファの再生位置が思ったところにこないので画像の表示位置がずれてしまいます。色々試したのですがまだ方法がわかっていない。

今後

さて、Korgのmonotribeelectribeが音声を用いたアップデート方法を採用し話題になりました。
僕が思いつく方法としてはFSKというやつがあり、0と1のデータを作り変調して2つの周波数の音を作ります。
そして受信側がこのブログでも前に扱いましたがGoertzelアルゴリズムとかで周波数を検出してデータを復元する。
こういうこともやってみたいなと思いつつ、これだと変化を起こすのが大変そうではあります。

ダウンロード

物好きな方はアプリapkを置いておくので勝手に使ってください。
android2.2以上
https://dl.dropbox.com/u/78146942/android/apk/ORcamera.apk
android1.6以上(ちょっといい加減)
https://dl.dropbox.com/u/78146942/android/apk/CamSndCam.apk

手順

設定->アプリケーション->不明な提供元にチェック->apkをダウンロード->インストール

使い方

A.起動するとプレビュー画面が出るのでタッチする。(タッチダウンでフォーカスがあたり、タッチアップで撮影)
B.シャッター音が聞こえ少しするとノイズ音(びよよよ〜ん、ざざざざ〜)が出ます。
C.プレビューに戻ります。
D.sdカード以下にorcamera0000.jpgと保存されています。

ソース

package com.myapp.orcamera;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.content.Context;
import android.hardware.Camera;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.FileOutputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.AudioRecord;
import android.media.MediaRecorder;

public class ORcameraActivity extends Activity {
  Snd snd;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    snd = new Snd();
    setContentView(new CameraView(this, snd));
  }
}
class CameraView extends SurfaceView implements SurfaceHolder.Callback,Camera.PictureCallback,Camera.AutoFocusCallback {
  private SurfaceHolder holder;
  private Camera        camera;
  public Snd snd;

  public CameraView(Context context, Snd s) {
    super(context);
    snd = s;
    holder=getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  }
  public void surfaceCreated(SurfaceHolder holder) {
    camera=Camera.open();
    try {
      camera.setPreviewDisplay(holder);
    } catch (Exception e) {}
  }
  public void surfaceChanged(SurfaceHolder holder,int format,int w,int h) {
    camera.startPreview();
  }
  public void surfaceDestroyed(SurfaceHolder holder) {
    camera.setPreviewCallback(null);
    camera.stopPreview();
    camera.release();
    camera=null;
  }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction()==MotionEvent.ACTION_DOWN) {
      camera.autoFocus(this);
    }
    if (event.getAction()==MotionEvent.ACTION_UP) {
      camera.takePicture(null,null,this);
    }
    return true;
  }
  public void onAutoFocus(boolean success, Camera camera){}
  public void onPictureTaken(byte[] data,Camera camera) {
    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 8;
    bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
    Bitmap b = bitmap.copy(Bitmap.Config.ARGB_8888, true);
    int width = b.getWidth();
    int height = b.getHeight();
    snd.setSize(bitmap, b, width, height);
    try {
      String path=Environment.getExternalStorageDirectory()+"/orcamera0000.jpg";
      data2file(b,path);
    } catch (Exception e) {}
    camera.startPreview();
    bitmap.recycle();
    bitmap = null;
    b.recycle();
    b = null;
  }
  private void data2file(Bitmap b,String fileName) throws Exception {
    FileOutputStream out=null;
    try {
      out=new FileOutputStream(fileName);
      b.compress (CompressFormat.JPEG, 100, out);
      out.close();
    } catch (Exception e) {
      if (out!=null) out.close();
      throw e;
    }
  }
}

class Snd{
  public Snd(){}
  public void setSize(Bitmap bitmap, Bitmap b,int width, int height){
    int buffer_size = width*height*4;
    byte[] outbuf = new byte[buffer_size];
    byte[] inbuf = new byte[buffer_size];
    AudioRecord record = new AudioRecord(
        MediaRecorder.AudioSource.MIC,
        44100, AudioFormat.CHANNEL_CONFIGURATION_MONO,
        AudioFormat.ENCODING_PCM_16BIT, buffer_size);
    AudioTrack track = new AudioTrack(
        AudioManager.STREAM_MUSIC,
        44100, AudioFormat.CHANNEL_CONFIGURATION_MONO,
        AudioFormat.ENCODING_PCM_16BIT, buffer_size,
        AudioTrack.MODE_STREAM);
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        int color = bitmap.getPixel(x, y);
        int rr = Color.red(color);
        int gg = Color.green(color);
        int bb = Color.blue(color);
        int aa = Color.alpha(color);
        outbuf[4*(x+y*width)] = (byte)(127-rr/2);
        outbuf[4*(x+y*width)+1] = (byte)(127-gg/2);
        outbuf[4*(x+y*width)+2] = (byte)(127-bb/2);
        outbuf[4*(x+y*width)+3] = (byte)(127-aa/2);
      }
    }
    track.setPlaybackHeadPosition(0);
    track.reloadStaticData();
    track.play();
    track.write(outbuf, 0, buffer_size);
    record.startRecording();
    record.read(inbuf,  0, buffer_size);
    track.stop();
    record.stop();
    track.flush();
    track.release();
    record.release();
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        int rr = (int)inbuf[4*(x+y*width)] + 128;
        int gg = (int)inbuf[4*(x+y*width)+1] + 128;
        int bb = (int)inbuf[4*(x+y*width)+2] + 128;
        int aa = (int)inbuf[4*(x+y*width)+3] + 128;
        b.setPixel(x, y, Color.argb(aa, rr, gg, bb));
      }
    }
  }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myapp.orcamera"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ORcameraActivity"
            android:screenOrientation="landscape"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>