工作と競馬

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

カテゴリ:4.ソフトウェア > 4.5 Android

概要

NFCタグをタッチして、自作アプリの特定アクティビティを表示させる方法を調べ、まとめた。


背景と目的

NFCタグをタッチすることで、特定のアクティビティを選択することができれば、使用状況に応じて、簡単に必要な画面を表示できる。そこで、特定のアクティビティを指定する方法を調べて、まとめる。


詳細

0.方法を簡単に箇条書き

いろいろ探し回った結果、以下の手順を行えばよいということがわかった。

  • NFCタグに、アクティビティ選択のためのNDEFレコードを書き込む
  • Manifestファイルへのインテントフィルタの記述
  • デフォルトのアクティビティで、インテントとして渡されたNDEFレコードを解析しアクティビティを選択できるコードを実装


1.NFCタグ

アプリを起動するためのNFCタグは、通常、アプリケーションレコードというタイプのNDEFレコードを使う。しかし、これだけではアクティビティ選択の情報が何もないので、何らかのNDEFレコードにアクティビティ選択のための情報を追加する。自作アプリなので何でもいいと思うが、とりあえず一番簡単なテキストレコードを使う。ペイロードには、アクティビティ名を入れておく。(アクティビティが区別できればなんでもよい。)


2.Manifestファイル

次に、こちらによれば、Manifestファイルに、タグ読み取り時のインテントを受け取るためのインテントフィルタを設定する必要がある。

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>


3.コーディング

2の記述により、デフォルトのアクティビティ渡されたインテントには、NFCタグのNDEFレコードが格納されているので、1で設定したテキストレコードを解析し、目的のアクティビティを選択するコードを書く。OnCreateメソッドに実装すれば、デフォルトのアクティビティ起動後、すぐに目的のアクティビティが表示できる。

        // アクティビティに渡されたインテントを取得
        Intent intent = this.getIntent();
        // NDEFを取得し解析する
        Parcelable[] parcelables = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (parcelables != null) {
            for (Parcelable parcelable : parcelables) {
                NdefMessage msg = (NdefMessage) parcelable;
                NdefRecord[] records = msg.getRecords();
                for (NdefRecord record : records) {
                    // ここで、テキストレコードを解析
                    // 条件に合致すれば、以下のように目的のアクティビティを
                    // 開始する
                    // レコードの解析方法はWeb各所等を参照
                    // Intent newIntent = new Intent(this, TargetActivity.class);
                    // startActivity(newIntent);
                }
            }
        }


4.動作確認

上記の内容を自作アプリに適用し、タグをかざしたところ、無事目的のアクティビティが表示された。というわけで、成功!


まとめ

NFCタグをタッチして、自作アプリの特定アクティビティを表示させる方法を調べ、実装、動作確認することができた。

概要

NFC延長アンテナを自作し、動作することが確認できた。


背景と目的

スマホやタブレットなどのデバイスについているNFC読み取り部は、機種によって搭載位置が異なる。背面についているデバイスでは、手に持って固定されたタグにかざすのはやりやすいが、逆にデバイスを固定してタグをかざす場合にはやりにくい。そこで、背面についているデバイスのアンテナを延長して、デバイスの横などに受信部を移設にトライする。


詳細

1.情報収集

世の中には、すでにNFC延長アンテナというものが売られているのでそれを買えばよいのではあるが、こちらの動画にあるように、自作している例もある。そこで、自作してみたい。


2.設計と製作

上記のリンク先サイト内のコメントを頼りに、いきなり作成してみた。もちろん、デバイス本体の改造は不要である。

  • エナメル線(直径0.4mm)を2m程度使用
  • コイル状のアンテナ部(直径約4㎝で、8~9ターン程度)を2つ用意し、接続
  • アンテナ同士の間隔は、タブレット等の横に引き出せる長さとして約10cm

DSC_0067

図2 出来上がった延長アンテナ

使い方としては、一方のコイルの上に、タブレットの背面読み取り部等を合わせて載せ、もう一方のコイルの上に、タグをかざすという感じだ。


3.動作確認

以下の通り、読み取ることができた!きわめて単純な構造ではあるが、実使用できそうだ。壁にタブレットを設置し、タブレット横に引き出した受信部にタグをかざして読み取らせるという使い方ができそうだ!


まとめ

NFC延長アンテナを自作し、動作することが確認できた。

概要

Android Studioの表示の日本語化を行った。


背景と目的

Android Studioを英語表記のまま使用していたが、ちょっと作業効率が悪いので日本語化することにした。


詳細

0.環境

  • Window10
  • Lenovo Idepad Yoga 13
  • Android Studio 2.3.3


1.参考


2.

2.1 Pleiadesをダウンロード。

http://svn.osdn.jp/svnroot/mergedoc/trunk/Pleiades/build/pleiades.zip


2.2 ファイルの配置

次に、zipを解凍し、plugin/jp.sourceforge.mergedoc.pleiadesというフォルダを、C:\Users\{ユーザー名}\.AndroidStudio2.3にコピー。


2.3 起動オプションの編集

Android StudioのメニューバーHelp > Edit Custom VM Options...を選択し、ファイル作成の可否を聞かれるので、OKを押すと、

  • "C:\Users\{ユーザー名}\.AndroidStudio2.3\studio64.exe.vmoptions"

というファイルができる。次に、このファイルに、デフォルトの設定を引き継ぐために、

  • "C:\Program Files\Android\Android Studio\bin\studio.exe.vmoptions"

というファイルを開き、内容をコピーする。コピーした様子が以下。

# custom Android Studio VM options, see http://tools.android.com/tech-docs/configuration
-Xms256m
-Xmx1280m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-da
-Djna.nosys=true
-Djna.boot.library.path=

-Djna.debug_load=true
-Djna.debug_load.jna=true
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Didea.paths.selector=AndroidStudio2.3
-Didea.platform.prefix=AndroidStudio
-Didea.jre.check=true

そして、このファイルの末尾に以下の2行を追加。※ディレクトリの区切り文字は、バックスラッシュでないと、起動しなかった。Windowsではちゃんとバックスラッシュにすること

-Xverify:none
-javaagent: C:\Users\{ユーザー名}\.AndroidStudio2.3\jp.sourceforge.mergedoc.pleiades\pleiades.jar

そして、再起動。


3.動作確認

起動した様子は、以下。ちゃんと日本語化されている。

20170715120243


まとめ

Android Studioの日本語化ができた。ちょっと作業しやすくなった。

概要

HTTPSでJSONデータをPOSTするためのコードを自分用にまとめておく


背景と目的

HTTPSでJSONデータをPOSTするためのコードを自分用にまとめておく。


詳細

0.状態

  • Android Studio 2.3.3
  • Minimum SDK Version 21

1.Manifestファイル

まず、以下を記述。


2.インポート

import org.json.JSONObject;
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.PrintStream;

3.ソースコード

処理をまとめた静的メソッドをもつクラスを作っておく。

public class HttpsJson {

    // HTTPSで、JSONデータをPOSTして、JSONデータをもらう
    // targetUrl: 送信先URL
    // jsonstr: 送信したいJSON文字列
    // 戻り値: 文字列
    public static String post(String targetUrl, String jsonstr) {

        try {
            // 接続する
            URL url = new URL(targetUrl);
            HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
            con.setRequestMethod("POST");
            con.setInstanceFollowRedirects(false);
            con.setDoOutput(true);
            con.setRequestProperty("Content-Type", "application/json; charset=utf-8");
            // POSTのデータを送る
            OutputStream os = con.getOutputStream();
            PrintStream ps = new PrintStream(os);
            ps.print(jsonstr);
            ps.close();
            // レスポンスをもらって文字列化する
            String str = InputStreamToString(con.getInputStream());
            // 接続を切る
            con.disconnect();
            // 値を返す
            return str;
        } catch(Exception ex) {
            // エラーの時
            System.out.println(ex);
            return ex.toString();
        }
    }

    /*
    文字列に変換
    */
    private static String InputStreamToString(InputStream is) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }

}

まとめ

HTTPSでJSONデータをPOSTするためのコードを自分用にまとめておく。

概要

Androidで、NFCを読み取る簡単なアプリを作成し、動作を確認した。


背景と目的

AndroidのNFCを使ったアプリを作成したくなったので、手始めにタグ読み取る簡単なアプリを作成し、動作を確認してみる。


詳細

1.環境

私の環境は、

  • スマホ本体: SO-01J(当然、NFC対応)
  • OS: Android 7.0
  • 開発用PC: Windows 10、Android Studio 2.3.3
  • 読み取るタグ: 交通系カード(Suica等)

である。


2.コーディング

とりあえず、動けば何でもいいので手始めにタグのUIDを読み取るだけのアプリを作ることにする。

2.1 プロジェクト作成

新規プロジェクトHelloNFCを作成した。対応APIレベルは、以下の通りAndroid 5.0以降とした。

20170622222257

図2.1 Target Android Devices


2.2 マニュフェストファイル

次に、マニュフェストファイルを編集する。NFCを使うには、最低限、以下の記述が必要なので、追加した。

<uses-permission android:name="android.permission.NFC"/>


2.3 MainActivity

次に、MainActivityのプログラムを作成。短いのですべて載せる。

なお、すべてを理解しきれていないが、徐々に覚えればいいので、今回はメモするだけとする。

package com.example.takuya.hellonfc;

import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    // アダプタを扱うための変数
    private NfcAdapter mNfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // アダプタのインスタンスを取得
        mNfcAdapter = android.nfc.NfcAdapter.getDefaultAdapter(this);

    }

    @Override
    protected void onResume(){

        super.onResume();

        // NFCがかざされたときの設定
        Intent intent = new Intent(this, this.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        // ほかのアプリを開かないようにする
        PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

    }

    @Override
    protected void onPause(){
        super.onPause();

        // Activityがバックグラウンドになったときは、受け取らない
        mNfcAdapter.disableForegroundDispatch(this);

    }

    @Override
    protected void onNewIntent(Intent intent){
        super.onNewIntent(intent);

        // NFCのUIDを取得
        byte[] uid = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
        // 表示
        Toast.makeText(this, Arrays.toString(uid), Toast.LENGTH_SHORT).show();

    }

}


3.動作確認

以下のように、かざした結果UIDがちゃんと表示された。うまく動いたようだ。

BlogPaint

図3 動作した様子


まとめ

AndoridでNFCタグを読み取るアプリを作成できた。徐々に、役に立つアプリに仕上げていきたい。

概要

Androidアプリ作成の環境構築し、サンプルプログラムが動作することを確認した。


背景と目的

最近、スマートフォンをiPhoneからAndroidに変えた。なので、久しぶりにAndroidアプリを作ってみたくなった。なのでとりあえず、環境構築。


詳細

1.Android Studioのセットアップ

これは、Web各所に解説されているのでそれに倣った。参考になったのは、

こちら

あたり。無事、インストールが完了。

2.端末をデバッグ用に

次に、自分のAndroidスマホでデバッグできるように設定。昔やったことがあるが完全に忘れたので、

参考

を見ながら、

設定>端末情報>ビルド番号を5回くらい連打>端末情報に戻る

開発者向けオプションが表示される

とりあえず、今回はスリープモードにしないとUSBデバッグをONにし、デバッグモードを許可。

という感じで、設定。以下が、PCに端末を接続して、Runを押したとき。デバッグ対象のデバイスとして選択できるようになった。

20170423105140

図2 接続して認識された様子


3.動作確認

下のスクリーンショットが、サンプルアプリを動かした様子。無事、動いた。これで、自分のスマホでいろいろできるだろう。

Screenshot_20170423-105302

写真3 サンプルアプリが動いた様子


まとめ

Androidアプリ作成の環境構築し、サンプルプログラムが動作することを確認できた。

概要

 wavファイルを扱うためのクラスを作成した。2014/09/17 littleEndianShortメソッドを追記。


背景と目的

 以前作成したwavファイルを再生するアプリに続き、録音アプリを作成しようとしている。だが、録音しながらwavファイルを作成するための処理は、そこそこ複雑で、今後もよく使いそうなので専用のクラスを作ることにした。


詳細

1.構想

 扱えるファイルの仕様は、とりあえずサンプリング周波数44.1kHz、16ビット、モノラルのリニアPCMに限る。PCMデータは、後からどんどん追加して伸ばしていけるようにする。

2.実装
2.1 フィールド

 rafは、RandomAccessFileクラスのオブジェクト。ファイルの任意の位置に任意のデータを書き込むためにこのクラスを使う。recFileは、wavファイルのオブジェクト。その他は、wavファイルのヘッダの各要素に対応する。

 private final int FILESIZE_SEEK = 4;
 private final int DATASIZE_SEEK = 40;
 
 private RandomAccessFile raf;
 private File recFile;
 private String fileName = "test.wav";
 private byte[] RIFF = {'R','I','F','F'};
 private int fileSize = 36;
 private byte[] WAVE = {'W','A','V','E'};
 private byte[] fmt = {'f','m','t',' '};
 private int fmtSize = 16;
 private byte[] fmtID = {1, 0}; // 2byte
 private short chCount = 1;
 private int sampleRate = 44100;
 private int bytePerSec = 44100 * 2;
 private short blockSize = 2;
 private short bitPerSample = 16;
 private byte[] data = {'d', 'a', 't', 'a'};
 private int dataSize = 0;
2.2 コンストラクタ

 コンストラクタは、デフォルトだけにする。ソースコードは省略

2.3 createFileメソッド

 wavファイルを作成するためのメソッド。引数としてファイル名を与えて、新規ファイルを作成する。該当するファイルがあれば削除してから作成する。また、PCMデータのないヘッダだけを書き込んでおく。

public void createFile(String fName){
  
  fileName = fName;
  
  // ファイルを作成
  recFile = new File(fileName);
  if(recFile.exists()){
   recFile.delete();
  }
  try {
   recFile.createNewFile();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

  try {
   raf = new RandomAccessFile(recFile, "rw");
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  // wavのヘッダを書き込み
  try {
   raf.seek(0);
   raf.write(RIFF);
   raf.write(littleEndianInteger(fileSize));
   raf.write(WAVE);
   raf.write(fmt);
   raf.write(littleEndianInteger(fmtSize));
   raf.write(fmtID);
   raf.write(littleEndianShort(chCount));
   raf.write(littleEndianInteger(sampleRate));
   raf.write(littleEndianInteger(bytePerSec));
   raf.write(littleEndianShort(blockSize));
   raf.write(littleEndianShort(bitPerSample));
   raf.write(data);
   raf.write(littleEndianInteger(dataSize));
   
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }
 // int型をリトルエンディアンのbyte配列に変更
 private byte[] littleEndianInteger(int i){
  
  byte[] buffer = new byte[4];
  
  buffer[0] = (byte) i;
  buffer[1] = (byte) (i >> 8);
  buffer[2] = (byte) (i >> 16);
  buffer[3] = (byte) (i >> 24);
  
  return buffer;
  
 }
2.3 addBigEndianDataメソッド

 PCMデータをwavファイルに追記するメソッド。引数として与えるのはビッグエンディアンのshort型配列データとして、それをリトルエンディアンに並び替えて追記する。

// PCMデータを追記するメソッド
 public void addBigEndianData(short[] shortData){

  // ファイルにデータを追記
  try {
   raf.seek(raf.length());
   raf.write(littleEndianShorts(shortData));
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  // ファイルサイズを更新
  updateFileSize();
  
  // データサイズを更新
  updateDataSize();
  
 }
 // ファイルサイズを更新
 private void updateFileSize(){
  
  fileSize = (int) (recFile.length() - 8);
  byte[] fileSizeBytes = littleEndianInteger(fileSize);
  try {
   raf.seek(FILESIZE_SEEK);
   raf.write(fileSizeBytes);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
 // データサイズを更新
 private void updateDataSize(){
  
  dataSize = (int) (recFile.length() - 44);
  byte[] dataSizeBytes = littleEndianInteger(dataSize);
  try {
   raf.seek(DATASIZE_SEEK);
   raf.write(dataSizeBytes);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
 // short型変数をリトルエンディアンのbyte配列に変更
	private byte[] littleEndianShort(short s){
		
		byte[] buffer = new byte[2];

		buffer[0] = (byte) s;
		buffer[1] = (byte) (s >> 8);

		return buffer;
		
	}
 // short型配列をリトルエンディアンのbyte配列に変更
 private byte[] littleEndianShorts(short[] s){
  
  byte[] buffer = new byte[s.length * 2];
  int i;
  
  for(i = 0; i < s.length; i++){
   buffer[2 * i] = (byte) s[i];
   buffer[2 * i + 1] = (byte) (s[i] >> 8);
  }

  return buffer;
  
 }
2.4 closeメソッド

 rafを閉じる。

 // ファイルを閉じる
 public void close(){
  
  try {
   raf.close();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
3.動作確認

 以下のようなテスト用プログラムで、動作確認したところwavファイルが作成されSoundEngineFreeで正しく波形が表示された。問題なさそうだ。

public class testWaveFile {

 public static void main(String[] args){
  // TODO Auto-generated constructor stub
  
  WaveFile wav1 = new WaveFile();
  int i;
  short[] d = new short[44];
  for(i=0;i

まとめと今後の課題

 wavファイルを扱うメソッドを作成できた。機能は非常に限られているが、今後徐々に追加すればよいので、とりあえずこれを使って録音アプリを作りたい。

概要

 Bluetoothでシリアル通信をするAndroidアプリを作成し、動作することを確認した


背景と目的

 Bluetooth(以下、BT)を使って無線通信ができるアプリを自作してみたくなった。そこで、非常に簡単ではあるが、BTを使ってPCとシリアル通信をするアプリを作成してみる。


詳細

1.構想

 作成するアプリは、図1のようなボタンを押すとPCに仮想シリアルで文字'a'を送信するものとする。

IMG_1418
図1 アプリの画面

2.準備

 BTでPCとAndroidデバイスでシリアル通信するには、双方のBTバージョンが一致し、プロファイルとしてSPPをもつことが必要。今回は、スマホのBT2.1と互換性を持つBTドングルを使用する。

  • PC:Lenovo IdeaPad Yoga 13
  • ターミナルソフト:Tera Term 4.82
  • スマートフォン:ソニーエリクソン Xperia IS11S、Bluetooth 2.1相当
  • BTドングル:ELECOM LBT-UAN05C2 Bluetooth 1.1~4.0対応
3.プログラム

 プログラムは、『Android SDK 逆引きハンドブック』(図3)を参考に作成した。なお、今回はプログラムを簡単にするため、事前に通信相手のMACアドレスがわかっており、ペアリングがなされていることを前提としている。

IMG_1419
図3 参考にした本

3.1 パーミッション

 まず、BTを使用するには、マニュフェストファイルでBTを使うためのパーミッションを追加する必要がある。

<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
3.2 MainActivityクラス

 必要なフィールドは以下。

 private BluetoothAdapter mBluetoothAdapter; // BTアダプタ
 private BluetoothDevice mBtDevice; // BTデバイス
 private BluetoothSocket mBtSocket; // BTソケット
 private OutputStream mOutput; // 出力ストリーム
 private Button btn1; // 送信ボタン

 次に、onCreateメソッド。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  // ボタンのインスタンスを取得
  btn1 = (ToggleButton) findViewById(R.id.button1);
  
  // BTの準備 --------------------------------------------------------------
  // BTアダプタのインスタンスを取得
  mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  
  // 相手先BTデバイスのインスタンスを取得
  mBtDevice = mBluetoothAdapter.getRemoteDevice("接続先のMACアドレスを記入");
  
  // BTソケットのインスタンスを取得
  try {
   // 接続に使用するプロファイルを指定
   mBtSocket = mBtDevice.createRfcommSocketToServiceRecord(
   UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
  } catch (IOException e) {
   e.printStackTrace();
  }
  
  // ソケットを接続する
  try {
   mBtSocket.connect();
   mOutput = mBtSocket.getOutputStream(); // 出力ストリームオブジェクトを得る
  } catch (IOException e) {
   e.printStackTrace();
  }
  
  // ボタンのイベントリスナ
  // 後述
  
 }

 ボタンのイベントリスナは、以下。

  btn1.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    try {
     mOutput.write('a');
    } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
   
  });

 onDestroyメソッドは、以下。

 @Override
 protected void onDestroy() {
  super.onDestroy();

  // ソケットを閉じる
  try {
   mBtSocket.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
4.動作確認

 動作確認は、事前にスマホとBTドングルをペアリングさせておき、Tera Termを起動したところ、COM3、COM4がBT仮想シリアルポートとして認識された。次に、アプリを立ち上げボタンを押したところ、図4のように文字’a'を受信した。これで、シリアル通信がうまくいったことが確認できた。

シリアル
図4 ボタンを押して、受信した様子。


まとめと今後の課題

 Bluetoothでシリアル通信をするAndroidアプリを作成できた。今後のアプリ作成に役立てたい。

概要

 Androidアプリ制作にあたり、javaの練習としてFFTプログラムを作成した。

 2014/04/12 FFTプログラムに一部ミスがあったため、修正。ソースコード赤字部分。振幅の大きさは正しいが位相の正負が逆転していた。修正により正しく位相が計算される。


背景と目的

 Androidアプリはjavaで開発するのだが、(聞くところによると、java以外でも作れるようだが)私はjavaでプログラムを書いた経験がほとんどないので、練習してみることにした。


詳細
1.練習の題材=FFTプログラム

 javaを書いたことがないとはいえ、経験のあるC++と文法的には似ているので、いきなりFFTプログラムを書いてみる。以前、VBAで書いたものがあるので、それをjavaに書き直す感じだ。それと、javaでFFTの処理など探せばいくらでも既製プログラムが見つかるだろうが、今は気にしない。
ちなみに、プログラミングには参考となる本があると何かと便利なので、インターネット上での評判に基づき『java言語 プログラミングレッスン』の上・下巻を買った。古い本なので中古で非常に安く手に入った。ただし、この本にはFFTのプログラムが載っているわけではない。

2.実装

 以下に、実装したコードを記す。本当は、FFTの計算が呼び出せるクラスにでもできればいいのだろうが、練習なのでここでは何も気にしていない。以前作成したVBAのコードをjavaに変えただけという感じである。

public class MyFFT {

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        double[] x;
        double[] y_re;
        double[] y_im;
        int i;
        int N;
        double pi, f, fs;
        
        N = 1024;
        fs = 44100;
        f = 1000;
        pi = Math.atan(1) * 4;
        
        x = new double[N];
        y_re = new double[N];
        y_im = new double[N];
        
        for(i = 0; i < N -1; i++){
            x[i] = Math.sin(2 * pi * f / fs * i);
            y_re[i] = x[i];
            y_im[i] = 0;
        }
        
        FFT(x, y_re, y_im, N);
        
    }
    
    //高速フーリエ変換
    public static void FFT(double[] x, double[] y_re, double[] y_im, int N) {

        //引数
        //x          入力データ
        //y_re       戻り値実部
        //y_im       戻り値虚部
        //N          FFTサイズ

        //変数
        double pi; //π
        int StageCount; //ステージ数
        int BlockCount; //ブロック数
        int NodeCount; //ノード数
        int stage; //ステージ番号
        int block; //ブロック番号
        int node; //ノード番号
        int n1; //計算用
        int n2; //計算用
        int i; //計算用
        double r; //計算用
        int[] index; //インデックス並べ替え用
        double re1; //計算用
        double im1; //計算用
        double re2; //計算用
        double im2; //計算用
        
        //FFTサイズ確認
        if((N & (N - 1)) != 0){
            return; //2のべき乗でなければ終わり
        }
        
        //計算準備
        pi = Math.atan(1) * 4; //π
        StageCount = (int)(Math.round(Math.log(N) / Math.log(2))); //ステージ数
        //y_re = x; //実部
        //y_im = new double[N]; //虚部
        
        //ステージごとに計算
        for(stage = 0; stage <= StageCount - 1; stage++){

            BlockCount = (int) Math.pow(2, stage); //ブロック数
            NodeCount = N / BlockCount; //ノード数
            r = 2 * pi / N * BlockCount; //定数
            
            //ブロックごとに計算
            for(block = 0; block <= BlockCount - 1; block++){
                //ノードごとの計算
                for(node = 0; node <= NodeCount / 2 - 1; node++){
                    n1 = node + NodeCount * block;
                    n2 = n1 + NodeCount / 2;
                    re1 = y_re[n1];
                    im1 = y_im[n1];
                    re2 = y_re[n2];
                    im2 = y_im[n2];
                    y_re[n1] = re1 + re2;
                    y_im[n1] = im1 + im2;
                    y_re[n2] = (re1 - re2) * Math.cos(r * node) + (im1 - im2) * Math.sin(r * node);
                    y_im[n2] = -(re1 - re2) * Math.sin(r * node) + (im1 - im2) * Math.cos(r * node);y_re[n2] = (re1 - re2) * Math.cos(r * node) - (im1 - im2) * Math.sin(r * node);
                    y_im[n2] = (re1 - re2) * Math.sin(r * node) + (im1 - im2) * Math.cos(r * node);
                }
            }
        }
        
        //並び替えテーブル作成
        index = new int[N];
        for(stage = 0; stage <= StageCount - 1; stage++){
            for(i = 0; i <= (Math.pow(2, stage) - 1); i++){
                index[(int)(Math.pow(2, stage) + i)] = index[i] + (int) Math.pow(2, StageCount - stage - 1);
            }
        }
        
        //並べ替え
        for(i = 0; i <= N - 1; i++){
            if(index[i] > i){
                re1 = y_re[index[i]];
                im1 = y_im[index[i]];
                y_re[index[i]] = y_re[i];
                y_im[index[i]] = y_im[i];
                y_re[i] = re1;
                y_im[i] = im1;
            }
        }
        
        //デバッグ
        for(i = 0; i <= N - 1; i++){
            //System.out.println("i = " + i + ": y(" + i + ") = " + y_re[i] + " + " + y_im[i] + "j");
            //System.out.println(y_re[i] + " + " + y_im[i] + "i");
            System.out.println((44100 / (double) N * (double) i) + "\t" + y_re[i] + "\t" + y_im[i]);
        }
        
    }
    
}

3.動作確認

 後々のオーディオ信号処理を見据えて(?)、サンプリング周波数44.1kHz、1kHz正弦波の信号をFFTしてみた。すると、どうやら正しく計算できていることがわかった。


まとめと今後の課題

 javaの練習ができた。早くAndroidアプリ制作に入っていきたい。

概要

 リアルタイムで録音し、wavファイルとして記録するアプリを作成した。


背景と目的

 前回作成したwavファイルを扱うクラスを用いて、録音を行うアプリを作成する。


詳細

1.構想

 画面は以下の通り。スタートボタンを押すと、録音を開始。ストップボタンを押すと録音を停止するだけ。Wavファイルのフォーマットは、リニアPCM、44.1kHz、16ビット、モノラルとする。

写真
図1 アプリの画面

2.実装
2.1 フィールド

 録音したデータのバイトデータにアクセスするにはAudioRecordクラスが必要なので、myARというオブジェクトを用意。Wav1は、録音波形を記録するwavファイル。shortDataは、読みだされた録音データを保持するため変数。

 final static int SAMPLING_RATE = 44100;
 private int bufSize;
 private AudioRecord myAR;
 private Button btnStart;
 private Button btnStop;
 private WaveFile wav1 = new WaveFile();
 private short[] shortData;
2.2 onCreateメソッド

 ここでは、各ボタンのクリックイベントリスナの登録と、AudioRecordクラスの初期化処理を行う。詳細は後述。wavファイルはcreateFileメソッドでファイルを作成する。

 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  btnStart = (Button) findViewById(R.id.button1);
  btnStop = (Button) findViewById(R.id.button2);
  
  initAudioRecord();
  
  // ファイルを作成
  wav1.createFile("/sdcard/bbb.wav");
  
  // スタートボタンのクリックイベントを設定
  btnStart.setOnClickListener(new OnClickListener() {
   
   @Override
         public void onClick(View v) {
    myAR.startRecording();
    myAR.read(shortData, 0, bufSize / 2);
         }
   
     });
  
  // 停止ボタンのクリックイベントを設定
  btnStop.setOnClickListener(new OnClickListener(){
   
   @Override
   public void onClick(View v){
    myAR.stop();
   }
   
  });
  
 }
2.3 initAudioRecordメソッド

 AudioRecordを初期化するための処理。ここでは、setRecordPositionUpdateListenerメソッドで、コールバックonPeriodicNotificationとコールバックが呼び出される間隔setPositionNotificationPeriodを設定する。基本的にAudioTrackクラスと同じだ。

// AudioRecordの初期化
 private void initAudioRecord(){

  // AudioRecordオブジェクトを作成
  bufSize = android.media.AudioRecord.getMinBufferSize(SAMPLING_RATE,
              AudioFormat.CHANNEL_IN_MONO,
              AudioFormat.ENCODING_PCM_16BIT);
  myAR = new AudioRecord(MediaRecorder.AudioSource.MIC, 
    SAMPLING_RATE, 
    AudioFormat.CHANNEL_IN_MONO, 
    AudioFormat.ENCODING_PCM_16BIT,
    bufSize);
  
  shortData = new short[bufSize / 2];
  
  // コールバックを指定
  myAR.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() {
   
   // フレームごとの処理
   @Override
   public void onPeriodicNotification(AudioRecord recorder) {
    // TODO Auto-generated method stub
    myAR.read(shortData, 0, bufSize / 2); // 読み込む
    wav1.addBigEndianData(shortData); // ファイルに書き出す
   }
   
   @Override
   public void onMarkerReached(AudioRecord recorder) {
    // TODO Auto-generated method stub
    
   }
  });
  
  // コールバックが呼ばれる間隔を指定
  myAR.setPositionNotificationPeriod(bufSize / 2);

 }
2.4 onPauseメソッド

 wav1をクローズする。

 public void onPause(){
  super.onPause();
  wav1.close();
 }
2.5 onDestroyメソッド

 audioRecordをreleaseする。

 public void onDestroy(){
  super.onDestroy();
  myAR.release();
 }
3.動作確認

 アプリを実行したところ、SDカードにbbb.wavというファイルが作成された。これをSoundEngineFreeで波形表示&再生してみた所、正しく録音されていた。これで、Android録音アプリが実現できた。

波形
図3 録音された波形


まとめと今後の課題

 Android録音アプリが実現できた。

このページのトップヘ