Androidのディスプレイサイズがxhdpiなのかxxhdpiなのか

Android

Androidのディスプレイサイズがxhdpiなのかxxhdpiなのか

Androidアプリを作っていて、とてもめんどくさいのが多種なスクリーンサイズに対応することですね。

「あれ、この端末で見た目がおかしいよ?」という時、まずはその端末が下記のどの画面サイズのグループに該当するのか確かめるのが先です。

mdpi:160dpi
hdpi:240dpi
xhdpi:320dpi (Galaxy S3a)
xxhdpi:480dpi (HTC J butterfly)
xxxhdpi : 640dpi

というわけで、上記の画面密度グループを表示するサンプルコードです。

 WindowManager windowManager = getWindowManager();
 Display display = windowManager.getDefaultDisplay();
 DisplayMetrics displayMetrics = new DisplayMetrics();
 display.getMetrics(displayMetrics);
 Log.d(TAG, "ディスプレイの密度グループ"+displayMetrics.densityDpi);

HTC J butterflyって乃木坂46がCMしているんですね!(・∀・)

AndroidのGoogle Play Serviceを利用する位置情報API

2013年にAndroidの位置情報を取得するAPIが新しくなりました。
より精度がよく、より速やかに、よりバッテリー消費が少なく!!位置情報が取得できるようになりました。

新APIはGoogle Play Serviceライブラリを利用します。
Fused location providerと呼んでいる人もいるようですが、それは新APIの位置情報プロバイダがそのように呼ばれているだけで、一部の話です。

http://developer.android.com/google/play-services/location.html

Androidで撮影したビデオがWebサイトで見られない

Android
Android ビデオ録画機能を作る

Androidで撮影したビデオがWebサイトで見られない

かなりマニアックなシチュエーションだと思いますが、今、次のようなことにトライしています。なお、ここまでの歩みはAndroid ビデオ録画機能を作るにもまとめてあります。

今回は、スマホで動画を撮影に加えて、

  1. Androidアプリで動画を撮影
  2. Webサーバーにアップロードして、Webでもその動画が楽しめる!

というものなんですが、Androidアプリでちょこっとパラメーターをいじるとブラウザで見られないという事象が発生していました。
なので、調べたことを書いておきます。

検証したスマホはXperia SO-04Dです。PCはWin7と、下記で紹介するブラウザたちです。

①まず、最初は下記のようにやっていました。
MediaRecorderを作るときに、getCamcoderProfileというメソッドのほうで、なるべく低い画質のビデオを作るべく、CamcorderProfile.QUALITY_LOWを指定しています。

 if(mrec == null){
      mrec = new MediaRecorder(); 
      mrec.setOnInfoListener(this);
      mrec.setOnErrorListener(this);       
 }
         
 mrec.setCamera(mCamera);
 
 mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
        
 mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
 
 int displayRotation = getOrientation(context, mrec);
 mrec.setOrientationHint(displayRotation);
 
 //ビデオの画質
 CamcorderProfile camcorderProfile = getCamcoderProfile(context, mrec);
 mrec.setProfile(camcorderProfile);
       
 mrec.setVideoSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);        
 
 mrec.setOutputFile(file_path);
        
 mrec.setPreviewDisplay(surfaceHolder.getSurface());
 private static CamcorderProfile getCamcoderProfile(Context context, MediaRecorder mrec){
       // 録画される画面の縦横を決める
       int degrees = getSurfaceDegrees(context);
 
       Camera.CameraInfo camInfo = new Camera.CameraInfo();
       int camera_id = findFrontFacingCameraID();
       Camera.getCameraInfo(camera_id, camInfo);
 
       //ビデオの画質
       CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_LOW);
       
       return camcorderProfile;
   }
   

しかし、上記でアップロードされたビデオファイルを見ると、

Firefox 38.0.5 「サポートされたファイル形式およびMIME形式のファイルが見つかりませんでした」
IE11 「無効なソース」
Chrome 再生のコントロールパネルが表示されるけれども、再生されない状態

となります。下記は悲しいIE11の画面です。
ie11.png

ちなみに、ブラウザ側のHTML5のビデオプレーヤーは下記のように指定しています。

 <video controls>
   <source src="hogehoge.mp4" type="video/webm">
   <source src="hogehoge.mp4" type="video/mp4">
    I'm sorry; your browser doesn't support HTML5 video in WebM with VP8 or MP4 with H.264.
 </video>

下記のサイトなどを調べて、MP4だったら再生されるはず? と思っていたため、最初かなり時間がかかりました。
https://developer.mozilla.org/ja/docs/Web/HTML/Supported_media_formats

たとえば、撮影した動画ではなくって、別のMP4なら再生されるため、もしかしてビデオコーデックのせいかな?と考えました。
で、動画ファイルのコーデックを調べられるMediaInfoというフリーソフトをインスコして調べてみます。

MediaInfo
http://www.gigafree.net/media/mediainfo.html

mediaInfo.png

えっ H263のビデオコーデックになっています。

コーデックについて、本腰で調べるときがきたようです…。
下記のWikipediaで調べてみます。
https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%87%E3%83%83%E3%82%AF

ふうむ。


ビデオコーデックを指定しないといけないのか!と思い、前にも散々、Androidでビデオ録画機能を作る時に気をつけたいことで公式ドキュメント読みなさーいっ って言ってたんですが、またまた公式ドキュメントを読んでいたにもかかわらず、英語力があまりなくって誤解していたみたいです[sad]

下記のページにある
http://developer.android.com/guide/topics/media/camera.html

次の記述ですね。

>Set the video output format and encoding. For Android 2.2 (API Level 8) and higher, use the MediaRecorder.setProfile method, and get a profile instance using CamcorderProfile.get(). For versions of Android prior to 2.2, you must set the video output format and encoding parameters:
>
> setOutputFormat() – Set the output format, specify the default setting or MediaRecorder.OutputFormat.MPEG_4.
> setAudioEncoder() – Set the sound encoding type, specify the default setting or MediaRecorder.AudioEncoder.AMR_NB.
> setVideoEncoder() – Set the video encoding type, specify the default setting or MediaRecorder.VideoEncoder.MPEG_4_SP.

Set the video encoding type, specify the default setting or MediaRecorder.VideoEncoder.MPEG_4_SP. というのは、指定しなければMPEG_4_SPが指定されるのかと思っていました。

そうではなく、指定しないとそのスマホのデフォルトが使われる、ということなんですね。そして、今回のようなトラブルになるわけですorz

で、MediaRecorderにsetVideoEncoderを設定するぞ!と下記のように変更してみると

 //中略
 int displayRotation = getOrientation(context, mrec);
 mrec.setOrientationHint(displayRotation);
 
 //下記を追加
 mrec.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
 mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 mrec.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
        
 //ビデオの画質
 CamcorderProfile camcorderProfile = getCamcoderProfile(context, mrec);
 mrec.setProfile(camcorderProfile);
 
 //後は略

で、上記を実行すると

 setOutputFormat called in an invalid state: 4

というエラーが出て、録画を開始できません。

あわわー 

 mrec.setProfile(camcorderProfile);

とやっているので、CamcorderProfileの中で設定しないといけなかったんですね。

上記で追加したところを削除し、CamcoderProfileのフィールドのvideoCodecを
MediaRecorder.VideoEncoder.H264で指定します。

   private static CamcorderProfile getCamcoderProfile(Context context, MediaRecorder mrec){
       // 録画される画面の縦横を決める
       int degrees = getSurfaceDegrees(context);
 
       Camera.CameraInfo camInfo = new Camera.CameraInfo();
       int camera_id = findFrontFacingCameraID();
       Camera.getCameraInfo(camera_id, camInfo);
 
       //ビデオの画質
       CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_LOW);
       
       //次を追加
       camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;
       
       return camcorderProfile;
   }
   

上記を試してみると、下記のような感じになりました。

  • Firefox 38.0.5 「サポートされたファイル形式およびMIME形式のファイルが見つかりませんでした」
  • IE11 「無効なソース」
  • Chrome 再生できた!

おおーっ Chromeだけではとりあえず、再生できたようです!!!
しかし、これはゴールではありません。。。IE11で再生できるのが、最終ゴールなのです・・・。


これからは、あまり明確な解決方法がないんですが、下記のサイトで、Win7+IE11では、1920×1088 pixels の動画しかサポートしない、という情報がありました・・・。マジ?

http://stackoverflow.com/questions/21124885/html5-video-not-working-in-ie-11

しかし・・・動画の大きさはもしかしたら関係あるのかもしれない・・・
80Kぐらいの、とても小さい動画なので・・・。
で、次のようにビデオのサイズを上げてみました。

 //ビデオの画質
 CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_720P);
 camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;

すると、
Firefox 38.0.5 再生できた!
IE11 再生できた!
Chrome 再生できた!!!

ついに!再生したい全部のブラウザで再生ができました。

ちなみに、上記のようにしておくと、CamcorderProfile.QUALITY_720Pがないカメラでは、ビデオが開始できません。
なので、下記のようにカムコーダーのプロファイルがあるかどうか調べて使ったほうがいいでしょう。

 CamcorderProfile camcorderProfile = null;
 if(CamcorderProfile.hasProfile(camera_id, CamcorderProfile.QUALITY_480P)){
           
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_480P);
       
       }else if(CamcorderProfile.hasProfile(camera_id, CamcorderProfile.QUALITY_720P)){
       
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_720P);
       
       }else{
       
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_HIGH);
       
       }
       
  camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;
  • エクスペリアの英語表記はXperiaですよ。 — ゆき {2015-06-17 (水) 09:55:52}
  • ゆきさん、ご指摘有難うございました!直しました。 — 書いた人 {2015-06-22 (月) 11:33:54}

Androidでフォーム画面をつくるとき、入力部分の幅を可変にする!

Android

Androidでフォーム画面をつくるということは、多々ありますね。
そんなとき、テーブルを使って左に項目名、右に入力部分を持ってくるというのが一般的かと思われます。
こんな感じで。
variable_table.png

レイアウトする前に - ゴールとなるxmlの構造を把握してみよう -

今回のゴールは以下のようなレイアウト構造になります。

 <LinearLayout>
   <TableLayout>
     <TableRow>
       <TextView>
       <EditText>
     </TableRow>
   </TableLayout>
 </Linearlayout>

下準備編 - テーブルを画面幅いっぱいに表示する -

テーブルを画面いっぱいにするためには、まずはLinearLayoutの幅を画面幅いっぱいにしましょう。

 
 android:layout_width="match_parent"

次に、テーブルの中で一番大枠のTableLayoutの幅を常に画面幅いっぱいになるようにしましょう。
幅にfill_parentを指定するだけでLinearLayoutの幅いっぱいになります。

 <TableLayout
   android:layout_width="fill_parent"

fill_parentは、その名の通り、親の幅をfillします!つまり、自分の横幅を親要素の幅いっぱいにします。
ここでの親要素はLinearLayoutですね!

次に、テーブルの一行ごとの要素である、TableRowの幅も画面いっぱいになるようにしちゃいましょう!!

 
 <TableRow
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"

「center_vertical」というのは、TableRowの中の要素を、TableRowの縦の中心に表示させるというものです。
上下均等に綺麗に余白ができるということです。

これで下準備が完了です!

実践編 - テーブルの中身をつくりましょう -

では、下準備ができたことですので、テーブルの中身をつくりましょう。
使うのは、

 TextView -> 項目名
 EditText -> 入力部分

TextViewがテーブルの左側に表示させる項目名、EditTextが右側に表示させる入力部分。

まずは、この二つをTableRowの中に配置しましょう。

 <TableRow
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical">
 
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="項目名1">
    </TextView>
    <EditText
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:inputType="text"
      android:layout_weight="1">
    </EditText>
 
 </TableRow>

TextViewとEditTextのwidthとheightには、wrap_contentを指定しました。
これは、Androidが自動的に幅を決めてくれる優れモノです。一概には言えませんが、画面全体に表示させたいというわけでないなら、wrap_contentを使いましょう。

ここで、EditTextだけに「layout_weight」が指定されています。
これは、親要素の余白を使う比率を表しています。
これを1に指定すれば、親要素のすべての余白を吸収して自分の横幅に使うことができます。
この場合、EditTextはTableRowの余白を吸収し、自分の横幅にしてしまいました。
EditTextとTextViewの間に格差ができたわけですね。

これで、横幅が可変になるフォームを作れます。
あとは自力でマージンやパディングを指定して、綺麗なフォームを作ってください!

おわりに・・・

ついに今回の白熱講義が終わってしまいました。
ちなみに、画面を横にした時の画像はこれです。
device-2013-09-10-120207.png
ちゃんと入力部分の横幅が変わっていますね。

Androidでビデオ録画機能を作る時に気をつけたいこと

Android
Android ビデオ録画機能を作る

Androidでビデオ録画機能を作る時に気をつけたいこと

Android ビデオを録画するサンプルコードでビデオ録画をするアプリのサンプルコードを紹介しましたが、実際のアプリはこんな単純に作る人はいないと思います。

で、機種依存とか、わけのわからないエラーだとか、クラッシュしまくりに苦しめられる前に!
よかったら、下記を読んでみてください。

実は、Android ビデオを録画するサンプルコードでは、かなり簡単に動作するものを作れたので、そのままアプリを作ってしまいました。

麻雀で言えば、手なりに手を進めてしまって、あまりよくない状態です。
後々、いろいろなエラーに苦しめられました。

しかし、次は同じ過ちは犯さないぞ!これを最初から知っていればこんなに苦しまなかったのに!という反省もこめてまとめておきました。

公式ドキュメントをちゃんと読む!

本当に当たり前のことですが、ついつい英語だからと、他のサイトにも紹介されているからと飛ばしてしまいましたね。

Camera
http://developer.android.com/guide/topics/media/camera.html

特にBuilding a Camera Appのあたりから、注意して読みましょう。
ビデオを録画するには、MediaRecorderというクラスを使いますが、Cameraと切って切り離せないからです。
まずは、Cameraをちゃんと扱わないと、アプリがじゃんじゃん落ちることになります。

カメラの注意点

以下、公式サイトの注意点をピックアップして記述します。

Cameraについて、この順番で利用します。

  1. カメラを見つけてアクセスする
  2. プレビューを作る
  3. プレビューレイアウトを作る
  4. リスナーを作り、ビデオ録画の準備をする
  5. ビデオを録画し、ファイルを保存する
  6. カメラをリリースする

カメラアプリが落ちたり不具合になる原因のひとつが、カメラをリリースできていないことが多いです。
リリースしないと、自分のアプリ内でも再度カメラにアクセスできず、落ちてしまうのです!!(おそろしや)
Camera.release()を忘れないようにしましょう。

Camera.openをするときは、下記のようにGoogleさんがサンプルを示してくれているように、一度中身を空してからopen()しましょう。
そして、try~catchで囲む。

 /** A safe way to get an instance of the Camera object. */
 public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
 }

ビデオの注意点

上記でカメラを作ってみたら、次はビデオです。
ビデオを動かすためには順番がかなり大事。

  1. カメラをopen()する – Use the Camera.open() to get an instance of the camera object.
  2. プレビューに接続する
  3. プレビューをスタートする
  4. ビデオ録画をスタートする だがその前に下記をすべし 
  5.     カメラをUnlockする (Camera.unlock()を使う)
        MediaRecorderを設定する 次の順番でね!
            setCamera()
            setAudioSource()
            setVideoSource()
            ビデオのアウトプット形式とエンコードを設定
                setOutputFormat()
                setAudioEncoder()
                setVideoEncoder() 
            setOutputFile() 
            setPreviewDisplay() -
        ※くどいようですが、必ず上記の順番で!さもなくば、お主に恐ろしい呪いがふりかかるであろう…的な感じです。
    
    1. ビデオをストップする

    これも、下記の順番を守るように!!!

        Stop MediaRecorder - Stop recording video by calling MediaRecorder.stop().
        Reset MediaRecorder - Optionally, remove the configuration settings from the recorder by calling MediaRecorder.reset().
        Release MediaRecorder - Release the MediaRecorder by calling MediaRecorder.release().
        Lock the Camera 
    
    1. プレビューをストップする
    2. カメラをリリースする

    onPauseのときなどに、MediaRecorderをリリースしたり、Cameraをリリースするのを忘れない!

    reset()したり、release()したり、nullにするのを忘れないようにします。
    これもGoogleさんがサンプルコードを書いてくれているので、これを使うのが安全でしょう。

        @Override
        protected void onPause() {
            super.onPause();
            releaseMediaRecorder();       // if you are using MediaRecorder, release it first
            releaseCamera();              // release the camera immediately on pause event
        }
     
        private void releaseMediaRecorder(){
            if (mMediaRecorder != null) {
                mMediaRecorder.reset();   // clear recorder configuration
                mMediaRecorder.release(); // release the recorder object
                mMediaRecorder = null;
                mCamera.lock();           // lock camera for later use
            }
        }
     
        private void releaseCamera(){
            if (mCamera != null){
                mCamera.release();        // release the camera for other applications
                mCamera = null;
            }
        }