Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.googlecode.android_scripting.facade.media;
     18 
     19 import android.app.Service;
     20 import android.content.Intent;
     21 import android.media.MediaRecorder;
     22 import android.net.Uri;
     23 import android.provider.MediaStore;
     24 import android.view.SurfaceHolder;
     25 import android.view.SurfaceHolder.Callback;
     26 import android.view.SurfaceView;
     27 import android.view.WindowManager;
     28 
     29 import com.googlecode.android_scripting.BaseApplication;
     30 import com.googlecode.android_scripting.FutureActivityTaskExecutor;
     31 import com.googlecode.android_scripting.Log;
     32 import com.googlecode.android_scripting.facade.AndroidFacade;
     33 import com.googlecode.android_scripting.facade.FacadeManager;
     34 import com.googlecode.android_scripting.future.FutureActivityTask;
     35 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     36 import com.googlecode.android_scripting.rpc.Rpc;
     37 import com.googlecode.android_scripting.rpc.RpcDefault;
     38 import com.googlecode.android_scripting.rpc.RpcOptional;
     39 import com.googlecode.android_scripting.rpc.RpcParameter;
     40 
     41 import java.io.File;
     42 import java.io.IOException;
     43 import java.lang.reflect.Field;
     44 import java.util.concurrent.CountDownLatch;
     45 import java.util.concurrent.TimeUnit;
     46 
     47 /**
     48  * A facade for recording media.
     49  *
     50  * Guidance notes: Use e.g. '/sdcard/file.ext' for your media destination file. A file extension of
     51  * mpg will use the default settings for format and codec (often h263 which won't work with common
     52  * PC media players). A file extension of mp4 or 3gp will use the appropriate format with the (more
     53  * common) h264 codec. A video player such as QQPlayer (from the android market) plays both codecs
     54  * and uses the composition matrix (embedded in the video file) to correct for image rotation. Many
     55  * PC based media players ignore this matrix. Standard video sizes may be specified.
     56  *
     57  */
     58 public class MediaRecorderFacade extends RpcReceiver {
     59 
     60   private final MediaRecorder mMediaRecorder = new MediaRecorder();
     61   private final Service mService;
     62 
     63   public MediaRecorderFacade(FacadeManager manager) {
     64     super(manager);
     65     mService = manager.getService();
     66   }
     67 
     68   @Rpc(description = "Records audio from the microphone and saves it to the given location.")
     69   public void recorderStartMicrophone(@RpcParameter(name = "targetPath") String targetPath)
     70       throws IOException {
     71     startAudioRecording(targetPath, MediaRecorder.AudioSource.MIC);
     72   }
     73 
     74   @Rpc(description = "Records video from the camera and saves it to the given location. "
     75       + "\nDuration specifies the maximum duration of the recording session. "
     76       + "\nIf duration is 0 this method will return and the recording will only be stopped "
     77       + "\nwhen recorderStop is called or when a scripts exits. "
     78       + "\nOtherwise it will block for the time period equal to the duration argument."
     79       + "\nvideoSize: 0=160x120, 1=320x240, 2=352x288, 3=640x480, 4=800x480.")
     80   public void recorderStartVideo(@RpcParameter(name = "targetPath") String targetPath,
     81       @RpcParameter(name = "duration") @RpcDefault("0") Integer duration,
     82       @RpcParameter(name = "videoSize") @RpcDefault("1") Integer videoSize) throws Exception {
     83     int ms = convertSecondsToMilliseconds(duration);
     84     startVideoRecording(new File(targetPath), ms, videoSize);
     85   }
     86 
     87   private void startVideoRecording(File file, int milliseconds, int videoSize) throws Exception {
     88     mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
     89 
     90     int audioSource = MediaRecorder.AudioSource.MIC;
     91     try {
     92       Field source = Class.forName("android.media.MediaRecorder$AudioSource").getField("CAMCORDER");
     93       source.getInt(null);
     94     } catch (Exception e) {
     95       Log.e(e);
     96     }
     97     int xSize;
     98     int ySize;
     99     switch (videoSize) {
    100     case 0:
    101       xSize = 160;
    102       ySize = 120;
    103       break;
    104     case 1:
    105       xSize = 320;
    106       ySize = 240;
    107       break;
    108     case 2:
    109       xSize = 352;
    110       ySize = 288;
    111       break;
    112     case 3:
    113       xSize = 640;
    114       ySize = 480;
    115       break;
    116     case 4:
    117       xSize = 800;
    118       ySize = 480;
    119       break;
    120     default:
    121       xSize = 320;
    122       ySize = 240;
    123       break;
    124     }
    125 
    126     mMediaRecorder.setAudioSource(audioSource);
    127     String extension = file.toString().split("\\.")[1];
    128     if (extension.equals("mp4")) {
    129       mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    130       mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    131       mMediaRecorder.setVideoSize(xSize, ySize);
    132       mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    133     } else if (extension.equals("3gp")) {
    134       mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    135       mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    136       mMediaRecorder.setVideoSize(xSize, ySize);
    137       mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    138     } else {
    139 
    140       mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    141       mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    142       mMediaRecorder.setVideoSize(xSize, ySize);
    143       mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    144     }
    145 
    146     mMediaRecorder.setOutputFile(file.getAbsolutePath());
    147     if (milliseconds > 0) {
    148       mMediaRecorder.setMaxDuration(milliseconds);
    149     }
    150     FutureActivityTask<Exception> prepTask = prepare();
    151     mMediaRecorder.start();
    152     if (milliseconds > 0) {
    153       new CountDownLatch(1).await(milliseconds, TimeUnit.MILLISECONDS);
    154     }
    155     prepTask.finish();
    156   }
    157 
    158   @Rpc(description = "Records video (and optionally audio) from the camera and saves it to the given location. "
    159       + "\nDuration specifies the maximum duration of the recording session. "
    160       + "\nIf duration is not provided this method will return immediately and the recording will only be stopped "
    161       + "\nwhen recorderStop is called or when a scripts exits. "
    162       + "\nOtherwise it will block for the time period equal to the duration argument.")
    163   public void recorderCaptureVideo(@RpcParameter(name = "targetPath") String targetPath,
    164       @RpcParameter(name = "duration") @RpcOptional Integer duration,
    165       @RpcParameter(name = "recordAudio") @RpcDefault("true") Boolean recordAudio) throws Exception {
    166     int ms = convertSecondsToMilliseconds(duration);
    167     startVideoRecording(new File(targetPath), ms, recordAudio);
    168   }
    169 
    170   private void startVideoRecording(File file, int milliseconds, boolean withAudio) throws Exception {
    171     mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    172     if (withAudio) {
    173       int audioSource = MediaRecorder.AudioSource.MIC;
    174       try {
    175         Field source =
    176             Class.forName("android.media.MediaRecorder$AudioSource").getField("CAMCORDER");
    177         audioSource = source.getInt(null);
    178       } catch (Exception e) {
    179         Log.e(e);
    180       }
    181       mMediaRecorder.setAudioSource(audioSource);
    182       mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    183       mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    184     } else {
    185       mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    186     }
    187     mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    188     mMediaRecorder.setOutputFile(file.getAbsolutePath());
    189     if (milliseconds > 0) {
    190       mMediaRecorder.setMaxDuration(milliseconds);
    191     }
    192     FutureActivityTask<Exception> prepTask = prepare();
    193     mMediaRecorder.start();
    194     if (milliseconds > 0) {
    195       new CountDownLatch(1).await(milliseconds, TimeUnit.MILLISECONDS);
    196     }
    197     prepTask.finish();
    198   }
    199 
    200   private void startAudioRecording(String targetPath, int source) throws IOException {
    201     mMediaRecorder.setAudioSource(source);
    202     mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    203     mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    204     mMediaRecorder.setOutputFile(targetPath);
    205     mMediaRecorder.prepare();
    206     mMediaRecorder.start();
    207   }
    208 
    209   @Rpc(description = "Stops a previously started recording.")
    210   public void recorderStop() {
    211     mMediaRecorder.stop();
    212     mMediaRecorder.reset();
    213   }
    214 
    215   @Rpc(description = "Starts the video capture application to record a video and saves it to the specified path.")
    216   public void startInteractiveVideoRecording(@RpcParameter(name = "path") final String path) {
    217     Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    218     File file = new File(path);
    219     intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
    220     AndroidFacade facade = mManager.getReceiver(AndroidFacade.class);
    221     facade.startActivityForResult(intent);
    222   }
    223 
    224   @Override
    225   public void shutdown() {
    226     mMediaRecorder.release();
    227   }
    228 
    229   // TODO(damonkohler): This shares a lot of code with the CameraFacade. It's probably worth moving
    230   // it there.
    231   private FutureActivityTask<Exception> prepare() throws Exception {
    232     FutureActivityTask<Exception> task = new FutureActivityTask<Exception>() {
    233       @Override
    234       public void onCreate() {
    235         super.onCreate();
    236         final SurfaceView view = new SurfaceView(getActivity());
    237         getActivity().setContentView(view);
    238         getActivity().getWindow().setSoftInputMode(
    239             WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED);
    240         view.getHolder().addCallback(new Callback() {
    241           @Override
    242           public void surfaceDestroyed(SurfaceHolder holder) {
    243           }
    244 
    245           @Override
    246           public void surfaceCreated(SurfaceHolder holder) {
    247             try {
    248               mMediaRecorder.setPreviewDisplay(view.getHolder().getSurface());
    249               mMediaRecorder.prepare();
    250               setResult(null);
    251             } catch (IOException e) {
    252               setResult(e);
    253             }
    254           }
    255 
    256           @Override
    257           public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    258           }
    259         });
    260       }
    261     };
    262 
    263     FutureActivityTaskExecutor taskExecutor =
    264         ((BaseApplication) mService.getApplication()).getTaskExecutor();
    265     taskExecutor.execute(task);
    266 
    267     Exception e = task.getResult();
    268     if (e != null) {
    269       throw e;
    270     }
    271     return task;
    272   }
    273 
    274   private int convertSecondsToMilliseconds(Integer seconds) {
    275     if (seconds == null) {
    276       return 0;
    277     }
    278     return (int) (seconds * 1000L);
    279   }
    280 }
    281