目錄html
第43章製做視頻
有兩種方法能夠給本身的應用程序配備視頻拍攝功能。第1種方法,也是較爲容易的一種方法,是建立默認的意圖並將其傳遞給startActivityForResult。第2種方法,是直接使用MediaRecorder。這個方法更難一些,可是它可使用設備相機的所有功能。
43.1使用內建意圖
1.若是要保存或處理捕獲的視頻,必須覆蓋onActivityResult方法。系統經過傳遞3個參數來調用onActivityResult方法。第1個參數是requestCode,這是調用startActivityForResult方法的時候傳入的請求代碼;onActivityResult方法的第二個參數是一個結果代碼;第三個參數包含了來自相機的數據。
2.代碼清單43.1AndroidManifest.xml文件java
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.videodemo" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="19" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" 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>
3.代碼清單43.2 菜單文件(menu_main.xml)android
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_camera" android:orderInCategory="100" android:showAsAction="always" android:title="@string/action_camera"/> </menu>
4.代碼清單43.3 activity_main.xml文件git
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center"> </VideoView> </FrameLayout>
5.代碼清單43.4MainActivity類android-studio
package com.example.videodemo; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.view.Menu; import android.view.MenuItem; import android.widget.MediaController; import android.widget.Toast; import android.widget.VideoView; public class MainActivity extends Activity { private static final int REQUEST_CODE = 200; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_camera: showCamera(); return true; default: return super.onContextItemSelected(item); } } private void showCamera() { // cannot set the video file Intent intent = new Intent( MediaStore.ACTION_VIDEO_CAPTURE); // check if the device has a camera: if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(intent, REQUEST_CODE); } else { Toast.makeText(this, "Opening camera failed", Toast.LENGTH_LONG).show(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE) { if (resultCode == RESULT_OK) { if (data != null) { Uri uri = data.getData(); VideoView videoView = (VideoView) findViewById(R.id.videoView); videoView.setVideoURI(uri); videoView.setMediaController( new MediaController(this)); videoView.requestFocus(); } } else if (resultCode == RESULT_CANCELED) { Toast.makeText(this, "Action cancelled", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "Error", Toast.LENGTH_LONG) .show(); } } } }
當用戶離開相機的時候,會調用onActivityResult方法。若是結果代碼是RESULT_OK而且data不爲空,該方法會在data上調用getData方法,以獲得一個指向視頻位置的Uri。接下來,它找到VideoView微件並設置其videoURI屬性,而後調用VideoView上的其餘兩個方法,即setMediaController和requestFocus。媒體控制器MediaController能夠用來播放和中止視頻,requestFocus()來爲微件設置焦點。
併發
!真機測試VideoDemo |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="33dp" android:layout_marginTop="22dp" android:onClick="startStopRecording" android:text="@string/button_start" /> <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
2.代碼清單43.6MainActivity類app
package com.example.videorecorder; import java.io.File; import java.io.IOException; import android.app.Activity; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Environment; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { private MediaRecorder mediaRecorder; private File outputDir; private boolean recording = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); File moviesDir = Environment .getExternalStoragePublicDirectory( Environment.DIRECTORY_MOVIES); outputDir = new File(moviesDir, "VideoRecorder"); outputDir.mkdirs(); setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); mediaRecorder = new MediaRecorder(); initAndConfigureMediaRecorder(); } @Override protected void onPause() { super.onPause(); if (recording) { try { mediaRecorder.stop(); } catch (IllegalStateException e) { } } releaseMediaRecorder(); Button button = (Button) findViewById(R.id.button1); button.setText("Start"); recording = false; } private void releaseMediaRecorder() { if (mediaRecorder != null) { mediaRecorder.reset(); mediaRecorder.release(); mediaRecorder = null; } } private void initAndConfigureMediaRecorder() { mediaRecorder.setAudioSource( MediaRecorder.AudioSource.CAMCORDER); mediaRecorder .setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setOutputFormat( MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setVideoFrameRate(10);// make it very low mediaRecorder.setVideoEncoder( MediaRecorder.VideoEncoder.MPEG_4_SP); mediaRecorder.setAudioEncoder( MediaRecorder.AudioEncoder.AMR_NB); String outputFile = new File(outputDir, System.currentTimeMillis() + ".mp4") .getAbsolutePath(); mediaRecorder.setOutputFile(outputFile); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView); SurfaceHolder surfaceHolder = surfaceView.getHolder(); mediaRecorder.setPreviewDisplay(surfaceHolder .getSurface()); } public void startStopRecording(View view) { Button button = (Button) findViewById(R.id.button1); if (recording) { button.setText("Start"); try { mediaRecorder.stop(); } catch (IllegalStateException e) { } releaseMediaRecorder(); } else { button.setText("Stop"); if (mediaRecorder == null) { mediaRecorder = new MediaRecorder(); initAndConfigureMediaRecorder(); } // prepare MediaRecorder try { mediaRecorder.prepare(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } mediaRecorder.start(); } recording = !recording; } }
其中有三個重要的方法:onCreate方法、onResume方法和onPause方法。
第44章聲音錄製
44.1MediaRecorder類
MediaRecorder類能夠採樣聲音或噪聲層級。MediaRecorder類用於記錄音頻和視頻,其輸出能夠寫入到一個文件,能夠很容易地選擇輸入源。有:start、stop、reset和release方法。
44.2示例
1.代碼清單44.1SoundMeter的清單框架
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.soundmeter" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.soundmeter.MainActivity" 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>
使用清單中的uses-permission元素來請求用戶許可錄製視頻。若是沒有包括這個元素的話,應用程序將沒法工做。此外,若是用戶不一樣意的話,應用程序將不會安裝。
2.代碼清單44.2SoundMeter中的res/layout/activity_main.xml文件異步
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:id="@+id/level" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button1" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/level" android:layout_below="@+id/level" android:background="#ff0000" android:layout_marginTop="30dp" /> </RelativeLayout>
該應用程序有兩個類。第一個類是一個名爲SoundMeter的類,它封裝了MediaRecorder而且暴露了3個方法來管理它。第一個方法是start,建立了MediaRecorder的一個實例,配置而且啓動它。第二個方法stop,中止了MediaRecorder。第3個方法是getAmplitude,返回一個double類型數據以代表採樣聲音的層級。
3.代碼清單44.3SoundMeter類ide
package com.example.soundmeter; import java.io.IOException; import android.media.MediaRecorder; public class SoundMeter { private MediaRecorder mediaRecorder; boolean started = false; public void start() { if (started) { return; } if (mediaRecorder == null) { mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource( MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat( MediaRecorder.OutputFormat.THREE_GPP); mediaRecorder.setAudioEncoder( MediaRecorder.AudioEncoder.AMR_NB); mediaRecorder.setOutputFile("/dev/null"); try { mediaRecorder.prepare(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } mediaRecorder.start(); started = true; } } public void stop() { if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.release(); mediaRecorder = null; started = false; } } public double getAmplitude() { return mediaRecorder.getMaxAmplitude() / 100; } }
4.代碼清單44.4SoundMeter中的MainActivity類
package com.example.soundmeter; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.Menu; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { Handler handler = new Handler(); SoundMeter soundMeter = new SoundMeter(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it // is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public void onStart() { super.onStart(); soundMeter.start(); handler.postDelayed(pollTask, 150); } @Override public void onPause() { soundMeter.stop(); super.onPause(); } private Runnable pollTask = new Runnable() { @Override public void run() { double amplitude = soundMeter.getAmplitude(); TextView textView = (TextView) findViewById(R.id.level); textView.setText("amp:" + amplitude); Button button = (Button) findViewById(R.id.button1); button.setWidth((int) amplitude * 10); handler.postDelayed(pollTask, 150); } }; }
MainActivity類覆蓋了兩個活動生命週期方法,onStart和onPause。當活動建立或者活動從新啓動以後,系統將調用onStart方法。當活動暫停或者因爲另外一個活動啓動了,或者因爲一個重要的事件發生了,系統將調用onPause方法。MainActivity類還使用了一個Handler來實現每150毫秒採樣一次聲音層級。
!真機測試 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.handlerdemo" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.handlerdemo.MainActivity" 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>
2.代碼清單45.2HandlerTest中的res/layout/activity_main.xml文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginLeft="51dp" android:layout_marginTop="58dp" android:src="@drawable/surprise" /> <Button android:id="@+id/button1" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignRight="@+id/imageView1" android:layout_below="@+id/imageView1" android:layout_marginRight="18dp" android:layout_marginTop="65dp" android:onClick="buttonClicked" android:text="Button"/> </RelativeLayout>
3.代碼清單45.3 HandlerDemo中的MainActivity類
package com.example.handlerdemo; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.Menu; import android.view.View; import android.widget.ImageView; public class MainActivity extends Activity { int counter = 0; Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getUserAttention(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it // is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } public void buttonClicked(View view) { counter = 0; getUserAttention(); } private void getUserAttention() { handler.post(task); } Runnable task = new Runnable() { @Override public void run() { ImageView imageView = (ImageView) findViewById(R.id.imageView1); if (counter % 2 == 0) { imageView.setVisibility(View.INVISIBLE); } else { imageView.setVisibility(View.VISIBLE); } counter++; if (counter < 8) { handler.postDelayed(this, 400); } } }; }
這個活動的核心是一個叫做task的Runnable,它實現了ImageView的動畫以及一個getUserAttention方法,該方法調用一個Handler上的postDelayed方法。這個Runnable,根據counter變量的值是奇數仍是偶數,將ImageView的可見性設置爲VISIBLE或INVISIABLE。
!真機測試 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.photoeditor" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.photoeditor.MainActivity" 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>
2.代碼清單46.2PhotoEditor中的res/layout/activity_main.xml文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp" > <LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent" android:orientation="horizontal" > <Button android:id="@+id/blurButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="doBlur" android:text="@string/blur_button_text" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="doInvert" android:text="@string/invert_button_text" /> </LinearLayout> <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="10dp" /> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|center" android:src="@drawable/photo1" /> </LinearLayout>
3.代碼清單46.3PhotoEditor中的MainActivity類
package com.example.photoeditor; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; public class MainActivity extends Activity { private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progressBar1); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it // is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } public void doBlur(View view) { BlurImageTask task = new BlurImageTask(); ImageView imageView = (ImageView) findViewById(R.id.imageView1); Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); task.execute(bitmap); } public void doInvert(View view) { InvertImageTask task = new InvertImageTask(); ImageView imageView = (ImageView) findViewById(R.id.imageView1); Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); task.execute(bitmap); } private class InvertImageTask extends AsyncTask<Bitmap, Integer, Bitmap> { protected Bitmap doInBackground(Bitmap... bitmap) { Bitmap input = bitmap[0]; Bitmap result = input.copy(input.getConfig(), /*isMutable'*/true); int width = input.getWidth(); int height = input.getHeight(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int pixel = input.getPixel(j, i); int a = pixel & 0xff000000; a = a | (~pixel & 0x00ffffff); result.setPixel(j, i, a); } int progress = (int) (100*(i+1)/height); publishProgress(progress); } return result; } protected void onProgressUpdate(Integer... values) { progressBar.setProgress(values[0]); } protected void onPostExecute(Bitmap result) { ImageView imageView = (ImageView) findViewById(R.id.imageView1); imageView.setImageBitmap(result); progressBar.setProgress(0); } } private class BlurImageTask extends AsyncTask<Bitmap, Integer, Bitmap> { protected Bitmap doInBackground(Bitmap... bitmap) { Bitmap input = bitmap[0]; Bitmap result = input.copy(input.getConfig(), /*isMutable=*/ true); int width = bitmap[0].getWidth(); int height = bitmap[0].getHeight(); int level = 7; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int pixel = bitmap[0].getPixel(j, i); int a = pixel & 0xff000000; int r = (pixel >> 16) & 0xff; int g = (pixel >> 8) & 0xff; int b = pixel & 0xff; r = (r+level)/2; g = (g+level)/2; b = (b+level)/2; int gray = a | (r << 16) | (g << 8) | b; result.setPixel(j, i, gray); } int progress = (int) (100*(i+1)/height); publishProgress(progress); } return result; } protected void onProgressUpdate(Integer... values) { progressBar.setProgress(values[0]); } protected void onPostExecute(Bitmap result) { ImageView imageView = (ImageView) findViewById(R.id.imageView1); imageView.setImageBitmap(result); progressBar.setProgress(0); } } }
!真機測試 |
位圖效果——
補充——主線程 |
(3)一般主線程必須最後完成執行,由於它執行各類關閉動做
(4)永遠不要在主線程中直接操做界面
https://gitee.com/EvelynYang/eleventh_week
在新建的AndroidProjects文件夾中運行腳本,第六週及以前都是在IdeaProjects文件夾裏運行。
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 200/200 | 2/2 | 20/20 | |
第二週 | 300/500 | 1/3 | 18/38 | |
第三週 | 500/1000 | 1/4 | 38/76 | |
第四周 | 1000/2000 | 1/5 | 20/96 | |
第五週 | 1000/3000 | 1/6 | 25/121 | |
第六週 | 1000/4000 | 1/7 | 25/146 | |
第七週 | 1000/5000 | 1/8 | 25/171 | |
第八週 | 1000/6000 | 1/9 | 15/186 | |
第九周 | 1000/7000 | 1/10 | 20/206 | |
第十週 | 1000/8000 | 1/11 | 20/226 | |
第十一週 | 1000/9000 | 1/12 | 10/236 |