2018-2019-2 20189203 移动平台应用开发实践第十一周学习总结

第43,44,45,46章

第43章 制作视频

  • 制作视频主要有两种方法,一种是使用内建意图,一种是使用MediaRecorder。
    如果使用默认的Camera应用程序来制作视频,可以使用内建意图。
    如果选择直接处理API而不是使用Camera来为应用程序提供视频制作功能,需要使用MediaRecorder。
    当使用MediaRecorder录制视频时,必须以指定的顺序执行配置步骤,然后调用MediaRecorder.prepare()检查和使用配置。
private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mMediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mMediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mMediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

以下MediaRecorder的视频录制参数已经有了预设值,不过也可以用下面的方法调整以适用自己的应用:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
启动和停止MediaRecorder
当使用MediaRecorder启动和停止录制时,必须遵从指定的顺序:

  1. Camera.unlock()
  2. 如以上的代码示例配置MediaRecorder
  3. MediaRecorder.start()启动录制
  4. 录制视频
  5. MediaRecorder.stop()停止录制
  6. MediaRecorder.release()释放MediaRecorder
  7. Camera.lock()锁定摄像头
    以下代码演示了怎样使用Camera和MediaRecorder通过一个按钮正确的启动和停止视频录制
    当完成一个视频录制后,不要释放Camera,否则预览视图也会停止
private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mMediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mMediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

释放相机
一个设备上,摄像头是所有应用的共享资源,因此在自身的应用处于onPause状态时,要立即释放掉正在使用的Camera对象

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;

    ...

    @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;
        }
    }
}

第44章 声音录制

下边来介绍下这三种录制的方式;
1、通过Intent调用系统的录音器功能,然后在录制完毕保存以后在onActivityResult中返回录制的音频的uri,然后通过Mediaplayer进行播放
调用系统的录音器

 private final static int REQUEST_RECORDER = 100;
    private Uri uri;
    public void recorder_Intent(){
        Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
        startActivityForResult(intent,REQUEST_RECORDER);
    }
获取返回的信息
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK && REQUEST_RECORDER == requestCode){
        uri = data.getData();
    }
}
<span style="background-color: rgb(255, 255, 255);"><span style="font-size:24px;">通过Mediaplayer进行播放</span></span>
  if (uri != null){
                            if (mediaPlayer != null) {
                                try {
                                    mediaPlayer.reset();
                                    mediaPlayer.setDataSource(RecorderActivity.this, uri);
                                    mediaPlayer.prepare();

                                } catch (IOException e) {
                                    e.printStackTrace();
                                }

                            }else
                                Toast.makeText(RecorderActivity.this,"没有成功创建Mediaplayer",Toast.LENGTH_SHORT).show();
                        }

2、通过MediaRecorder来进行音频的录制:
MediaRecorder 类可用于音频和视频的捕获。再构造了一个MediaRecorder对象之后,为了捕获音频,必须调用setAudioEncoder和setAudioSource这俩个方法。
假设不调用这些方法,那么将不会录制音频(视频也相同不会),另外,MediaRecorder在准备录制之前通常还会调用setOutputFormat 和setOutputFile,
在实例化MediaRecorder之后。应该调用的第一个方法是setAudioSource。它採用一个AudioSource内部类中定义的常量作为參数,我们通常使用的常量是MediaRecorder。
AudioSource.MIC.
依据顺序,下一个调用的就是setOutputFormat ,这种方法採用在MediaRecorder.OutputFormat内部类中指定的常量作为參数:
(1)MediaRecorder.OutputFormat.MPEG_4:这个常量指定输出的文件将是一个MPEG_4文件,包括音频跟视频轨
(2)MediaRecorder.OutputFormat.RAW_AMR;这个常量表示输出一个没有不论什么容器类型的原始文件,仅仅有在捕获没有视频的音频且音频编码器是AMR_NB时才会使用这个常量。
(3)MediaRecorder.OutputFormat.THREE_GPP:这个常量指定输出的文件将是一个3gpp文件(.3gp)。它可能同一时候包括音频跟视频轨

MediaRecorder音频编码。在设置输出格式之后,能够调用setAudioEncoder方法来设置应该使用编码器,可能的值指定为MediaRecorder.AudioEncoder类中的常量。出来使用DEFAULT之外,仅仅存在一个其它的值:MediaRecorder.AudioEncoder.AMR_NB,这是自适应多速率窄带编解码器。
这样的编解码器针对语音进行了优化,因此不适应于语音之外的其它内容。默认情况下他的採样率是8kHz,码率在 4.75~12.2kbps之间。这个俩个数据对于录制语音之外的其它内容而言很低。可是,这是当前可用于MediaRecorder的唯一选择。

3 使用AudioRecord录制原始音频:
这就是第三种捕获音频的方法。使用AudioRecord的类。AudioRecord是三个方法中最灵活的(由于他同意訪问原始音频流),可是他是拥有最少的内置功能。如不会自己主动压缩音频等等。
使用AudioRecord的基础知识很easy。我们仅仅须要构造一个AudioRecord类型的对象,并传入各种不同配置參数。
须要制定的第一个值就是音频源。以下使用值与之前用于MediaRecorder的值同样,其在MediaRecorder.AudioSource 中定义。实际上。这意味着能够使用MediaRecorder.AudioSource.MIC;
int audiosource = MediaRecorder.AudioSource.MIC;
须要指定的下一个值是录制的採样率,应以赫兹为单位,我们知道。MediaRecorder採样的音频是8000赫兹。
而CD质量的音频一般是44100赫兹(44100Hz)。Hz或赫兹是每秒的样本数量。
不同的Android手机硬件将可以以不同的採样率进行採样。对于我的这个样例将以11025Hz的採样率来进行採样,这是一个经常使用的採样率。
int sampleRateInHz = 11025;

   接下来,须要指定捕获的音频通道的数量,在AudioFormat类中指定了用于此參数的常量。并且可依据名称理解他们。

下面将使用单声道配置。
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
随后。须要指定音频格式。
在AudioFormat类中也指定了一下各种可能的常量。
AudioFormat.ENCODING_DEFAULT
AudioFormat.ENCODING_INVALID
AudioFormat.ENCODING_PCM_16BIT
AudioFormat.ENCODING_PCM_8BIT

在这四个选择中,选择PCM_16位和PCM 8位。

PCM代表脉冲编码调制(Pulse Code Modulation) ,这个实际上是原始的音频样本。
因此能够设置每一个样本的分辨率为16位或8位。16位将占用很多其它的控件和处理能力,但表示的音频将更接近真实。
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
最后将须要指定缓冲区大小。
时间上能够查询AudioRecord类以获得最小缓冲区大小。查询方式是调用getMinBufferSize的静态方法,同一时候传入採样率,通道配置以及音频格式。

第45章 处理Handler

Handler的主要用途

  1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
    方法一,通过Handler + Message的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
 
    private Handler mHandler ;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
 
        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //通过Handler + Message的方式实现倒计时
                for (int i = 1; i <= 10; i++) {
                    Message message = Message.obtain(mHandler);
                    message.what = 10 - i;
                    mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
                }
            }
        });
 
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息
            }
        };
    }
}

可以了解到Handler的一个作用就是,在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。
方法二,通过Handler + Runnable的方式实现倒计时。代码如下:

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
 
        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i = 1; i <= 10; i++) {
                    final int fadedSecond = i;
                    //每延迟一秒,发送一个Runnable对象
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mBinding.time.setText((10 - fadedSecond) + "");
                        }
                    }, 1000 * i);
                }
            }
        });
    }
}
  1. 在子线程把需要在另一个线程执行的操作加入到消息队列中去。

第46章 异步工具

AsyncTask提供了方便的接口实现工作线程和主线程的通信,代码如下:

public class MyAsyncTask extends AsyncTask<Integer, String, String> {
        private Button btn;
        public MyAsyncTask(Button btn) {
            super();
            this.btn = btn;
        }
  
        @Override
        protected String doInBackground(Integer... integers) {
            Log.e("xxxxxx","xxxxxxexecute传入参数="+integers[0]);
            try {
                Thread.sleep(1000);
                publishProgress("过了一秒");
                Thread.sleep(1000);
                publishProgress("过了两秒");
                Thread.sleep(1000);
                publishProgress("过了三秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "doInBackground的返回";
        }
        /**
         * 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
         * 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
         */
        @Override
        protected void onPostExecute(String result) {
            btn.setText("线程结束" + result);
        }
        //该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
        @Override
        protected void onPreExecute() {
            btn.setText("开始执行异步线程");
        }
        /**
         * 这里的Intege参数对应AsyncTask中的第二个参数
         * 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
         * onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
         */
        @Override
        protected void onProgressUpdate(String... values) {

            String vlaue = values[0]+"";
            Log.e("xxxxxx","xxxxxx vlaue="+vlaue);
            btn.setText(vlaue+"");


        }
    }

AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
对应到上面的demo就是Integer, String, String。
doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
onProgressUpdate(String... Progress)这里对应的是doInBackground中调用的publicProgress,在这里进行处理,这里是UI主线程可以进行界面的更新
onPreExecute()这里相当于线程的开始,可以进行UI的处理
onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回。
我们再来看一下如何开启
myAsyncTask.execute(1000);
execute的参数对应的就是上面提到过的Params。

原文地址:https://www.cnblogs.com/23du/p/10853703.html