天天看點

關于MediaRecord一邊錄制一邊拍照的問題

想想自己幹了android也有兩年了,但是一篇文章沒有發表過,不是沒有時間,隻是感覺自己真是水準有限.以後我有時間就回來了分享一下自己得心得.廢話不多說了,直接進入主題

昨天看到高通平台的手機可以一邊錄像一邊拍照,感覺很屌的樣子,于是就拿出camera2的源碼看了起來,上代碼

public interface VideoController extends OnShutterButtonListener, OnPauseButtonListener {

    public void onReviewDoneClicked(View view);
    public void onReviewCancelClicked(View viwe);
    public void onReviewPlayClicked(View view);

    public boolean isVideoCaptureIntent();
    public boolean isInReviewMode();
    public int onZoomChanged(int index);

    public void onSingleTapUp(View view, int x, int y);

    public void stopPreview();

    public void updateCameraOrientation();

    // Callbacks for camera preview UI events.
    public void onPreviewUIReady();
    public void onPreviewUIDestroyed();
}
           

這個好像是video 主要去實作的功能,由于是在錄像中點選螢幕拍照的  我猜想是  onSingleTapUp(View view, int x, int y)  這個方法才對,繼續看下一個類

public class VideoModule implements CameraModule,VideoController,
    CameraPreference.OnPreferenceChangedListener,
    ShutterButton.OnShutterButtonListener,
    MediaRecorder.OnErrorListener,
    MediaRecorder.OnInfoListener {
}
           

這個類貌似實作了上面的接口 ,  找到裡面的方法

// SingleTapListener
// Preview area is touched. Take a picture.
@Override
    public void onSingleTapUp(View view, int x, int y) {
        if (mMediaRecorderPausing) return;
        takeASnapshot();
    }

    private void takeASnapshot() {
        if (mParameters == null) return;
        // Only take snapshots if video snapshot is supported by device
        if (CameraUtil.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent
            && !is4KEnabled()) {
            if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress) {
                return;
            }
            MediaSaveService s = mActivity.getMediaSaveService();
            if (s == null || s.isQueueFull()) {
                return;
            }

            // Set rotation and gps data.
            int rotation = CameraUtil.getJpegRotation(mCameraId, mOrientation);
            mParameters.setRotation(rotation);
            Location loc = mLocationManager.getCurrentLocation();
            CameraUtil.setGpsParameters(mParameters, loc);
            mCameraDevice.setParameters(mParameters);

            Log.v(TAG, "Video snapshot start");
            mCameraDevice.takePicture(mHandler,
                    null, null, null, new JpegPictureCallback(loc));
            showVideoSnapshotUI(true);
            mSnapshotInProgress = true;
            UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
                    UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
        }
    }
           

很高興的發現竟然找到了這個拍照的方法,  想要進入這個方法貌似是要看硬體是否支援這種拍照方式 也就是這個方法  CameraUtil.isVideoSnapshotSupported(mParameters)

在CameraUtil中找到這個 這個方法

public static final String TRUE = "true";
public static boolean isVideoSnapshotSupported(Parameters params) {
        return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
}
           

如果你的裝置如果支援的話就可以接着往下看了,

 private CameraProxy mCameraDevice;類名


 mCameraDevice.takePicture(mHandler,
                    null, null, null, new JpegPictureCallback(loc));
           

就是拍照的方法 繼續往下找

public interface CameraProxy {
}
           

苦苦找到的結果竟然告訴我這是一個借口類 而且還是一個内部類, 沒辦法隻能接着往下看

public class AndroidCameraProxyImpl implements CameraManager.CameraProxy {

 @Override
public void takePicture(Handler handler,CameraShutterCallback shutter,CameraPictureCallback raw,CameraPictureCallback post,CameraPictureCallback jpeg) {
            mCameraHandler.requestTakePicture(
                    ShutterCallbackForward.getNewInstance(handler, this, shutter),
                    PictureCallbackForward.getNewInstance(handler, this, raw),
                    PictureCallbackForward.getNewInstance(handler, this, post),
                    PictureCallbackForward.getNewInstance(handler, this, jpeg));
        }

}
           

可算找到了竟然又把這個事件交給了handler來處理,接着找

public void requestTakePicture(
                final ShutterCallback shutter,
                final PictureCallback raw,
                final PictureCallback postView,
                final PictureCallback jpeg) {
            post(new Runnable() {
                @Override
                public void run() {
                    try {
                        mCamera.takePicture(shutter, raw, postView, jpeg);
                    } catch (RuntimeException e) {
                        // TODO: output camera state and focus state for debugging.
                        Log.e(TAG, "take picture failed.");
                        throw e;
                    }
                }
            });
        }
           

找來找去竟然告訴我直接用  mCamera.takePicture(shutter, raw, postView, jpeg);  本人親自測試這樣方法是行不通的啊,問題出在哪裡,沒辦法繼續去看看他的UI實作類

public class VideoUI implements PieRenderer.PieListener,
        PreviewGestures.SingleTapListener,
        CameraRootView.MyDisplayListener,
        SurfaceTextureListener, SurfaceHolder.Callback,
        PauseButton.OnPauseButtonListener {



 public VideoUI(CameraActivity activity, VideoController controller, View parent) {
        mActivity = activity;
        mController = controller;
        mRootView = parent;
        mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true);
        mPreviewCover = mRootView.findViewById(R.id.preview_cover);
        <span style="color:#FF0000;">mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);</span>
        mTextureView.setSurfaceTextureListener(this);
        mTextureView.addOnLayoutChangeListener(mLayoutListener);
        mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
        mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
        mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
        mSwitcher.setCurrentIndex(ModuleSwitcher.VIDEO_MODULE_INDEX);
        mSwitcher.setSwitchListener(mActivity);
        initializeMiscControls();
        initializeControlByIntent();
        initializeOverlay();
        initializePauseButton();
        mAnimationManager = new AnimationManager();
        mOrientationResize = false;
        mPrevOrientationResize = false;
    }


}
           

這個類主要就是就是初始化一下控件,調用各個實作類來處理各種操作,但是我發現一個有趣的地方,在camera2中視訊部分是用serfaceview和textuerview兩個類來實作的,但是這個textureview是個什麼鬼,是不是在調用Camera的時候把圖像交給他就不會出現界面卡死的情況,那麼surfaceview在mediaercord 的錄制過程中有什麼用呢, 各種查找文檔他隻不過做了一些界面的預覽,其實和真正的錄制沒有太多關系,但是隻要停止預覽,錄像就會停止!

隻能去試一下這個textureview能不能實作使用mediarecord 一邊錄制一邊拍照的功能了, 在我寫的過程中發現

使用surfaceview中想要mediarecord将預覽畫面顯示出來必須調用

prMediaRecorder.setPreviewDisplay(prSurfaceHolder.getSurface());這個方法  而textureview則不用,貌似就是這裡的問題了

  寫完這個demo的時候一測試果然可行,頓時感覺神清氣爽.但是經過我反複測試發現如果最開始在沒有使用錄像的過程中就調用camera.takepic的這個方法還是會到時界面卡死,但是隻要使用過一次錄像功能,不管是否正在錄像都不會出現卡死的情況,這讓我百思不得騎姐啊,沒辦法隻能列印一下系統日志看一下, 

C:\Users\Administrator>adb root

adb server is out of date.  killing...
* daemon started successfully *
adbd is already running as root
C:\Users\Administrator>adb remount
remount succeeded
C:\Users\Administrator>adb shell
[email protected]:/ # cat proc/kmsg  > /storage/sdcard0/111.log

           
C:\Users\Administrator>adb pull /storage/sdcard0/111.log d:/111.log
2091 KB/s (128500 bytes in 0.060s)
           

系統日志就這麼打出來了,

Failed to create client debugfs at /ion/clients/camera/jpeg-0
           

别說還真有一個關于驅動方面的錯誤,這個就不是我能解決的了,但是我想到了另一個方法把他繞過去上代碼

takepic.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				
				if(isVideoSnapshotSupported(mCamera.getParameters())){
					try {
						mCamera.takePicture(null, null, new PictureCallback() {

							@Override
							public void onPictureTaken(byte[] data, Camera arg1) {
								try {
									mCamera.reconnect();
								} catch (IOException e) {
									e.printStackTrace();
								}
								mCamera.startPreview();
								Bitmap bit = BitmapFactory.decodeByteArray(data, 0,
										data.length, options);
								if (bit == null) {
									Toast.makeText(MainActivity.this, "無法擷取圖檔",
											Toast.LENGTH_SHORT).show();
									return;
								}
								iv_takepic.setImageBitmap(bit);
							}
						});
						
					} catch (Exception e) {
						e.printStackTrace();
					}
				}else {
					Toast.makeText(MainActivity.this, "不支援", Toast.LENGTH_SHORT).show();
				}
			}
           

也就是在拍完照的時候重新連接配接一下camera ,再重新預覽  這個會有明顯的卡頓,但是隻要執行過一次錄像功能就不會出現這種情況了,

說了這麼多到此結束附上代碼

MainActivity.java  代碼

package com.example.tsmrecord;

import java.io.IOException;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;


public class MainActivity extends Activity implements SurfaceTextureListener {

    private Camera mCamera;
    private TextureView mPreview;
    private Button captureButton;
    private Button takepic;
    private ImageView iv_takepic;
    private Options options;
    private LmMediaRecord record;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        mPreview = (TextureView) findViewById(R.id.texture_content);
        mPreview.setSurfaceTextureListener(this);
        captureButton = (Button) findViewById(R.id.btn_cap);
        captureButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                if(record.isRecording()){
                    record.stopRecord();
                    captureButton.setText("錄像");
                }
                else {
                    captureButton.setText("停止");
                    record.startRecord();
                }
            }
        });
        takepic = (Button) findViewById(R.id.btn_takepic);
        iv_takepic = (ImageView) findViewById(R.id.iv_takepic);
        options = new BitmapFactory.Options();
        takepic.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                
                if(isVideoSnapshotSupported(mCamera.getParameters())){
                    try {
                        mCamera.takePicture(null, null, new PictureCallback() {

                            @Override
                            public void onPictureTaken(byte[] data, Camera arg1) {
                                try {
                                    mCamera.reconnect();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                mCamera.startPreview();
                                Bitmap bit = BitmapFactory.decodeByteArray(data, 0,
                                        data.length, options);
                                if (bit == null) {
                                    Toast.makeText(MainActivity.this, "無法擷取圖檔",
                                            Toast.LENGTH_SHORT).show();
                                    return;
                                }
                                iv_takepic.setImageBitmap(bit);
                            }
                        });
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }else {
                    Toast.makeText(MainActivity.this, "不支援", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
    public static final String TRUE = "true";
    private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
    public static boolean isVideoSnapshotSupported(Parameters params) {
        return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
    }
    
        
    private void startPreview(){
        mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
        this.record=new LmMediaRecord(mCamera);
        // We need to make sure that our preview and recording video size are
        // supported by the
        // camera. Query camera to find all the sizes and choose the optimal
        // size given the
        // dimensions of our preview surface.
//        Camera.Parameters parameters = mCamera.getParameters();
//        List<Camera.Size> mSupportedPreviewSizes = parameters
//                .getSupportedPreviewSizes();
//        Camera.Size optimalSize =mCamera.getParameters().getPreviewSize();
                mPreview.getHeight());
//        // Use the same size for recording profile.
//        CamcorderProfile profile = CamcorderProfile
//                .get(CamcorderProfile.QUALITY_HIGH);
//        profile.videoFrameWidth = optimalSize.width;
//        profile.videoFrameHeight = optimalSize.height;
//        // likewise for the camera object itself.
//        parameters.setPreviewSize(profile.videoFrameWidth,
//                profile.videoFrameHeight);
//        mCamera.setParameters(parameters);
        try {
            // Requires API level 11+, For backward compatibility use {@link
            // setPreviewDisplay}
            // with {@link SurfaceView}
            mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
            mCamera.startPreview();
            mCamera.lock();
        } catch (IOException e) {
            Log.e("info",
                    "Surface texture is unavailable or unsuitable"
                            + e.getMessage());
        }
    }


    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1,
            int arg2) {
        startPreview();        
    }


    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {
        if (mCamera != null) {
            // release the camera for other applications
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
        return false;
    }


    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,
            int arg2) {
        // TODO Auto-generated method stub
        
    }


    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
        // TODO Auto-generated method stub
        
    }
    
    
    
}


           
package com.example.tsmrecord;

import java.io.File;
import java.io.IOException;

import android.hardware.Camera;
import android.media.MediaRecorder;
import android.media.MediaRecorder.AudioEncoder;
import android.os.Environment;

public class LmMediaRecord {
	private Camera mCamera;
	private MediaRecorder mMediaRecorder;
	private boolean isRecording;

	public LmMediaRecord(Camera camera) {
		this.mCamera = camera;
		isRecording = false;
	}

	public void startRecord() {
		mMediaRecorder = new MediaRecorder();
		// Step 1: Unlock and set camera to MediaRecorder
		mCamera.unlock();

		mMediaRecorder.setCamera(mCamera);
//		if (false) {
//			try {
//				mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//				mMediaRecorder.setAudioEncoder(AudioEncoder.AAC);
//			} catch (Exception e) {
//				e.printStackTrace();
//			}
//		}

		mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
		mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

		mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

		File file = new File(Environment.getExternalStorageDirectory()
				+ File.separator + "tsmtexture" + File.separator
				+ System.currentTimeMillis() + ".mp4");
		if (!file.getParentFile().exists())
			file.getParentFile().mkdirs();
		mMediaRecorder.setOutputFile(file.getAbsolutePath());

//	        mMediaRecorder.setVideoSize(1920, 1080);
		mMediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024);
		mMediaRecorder.setOrientationHint(0);

		try {
			mMediaRecorder.prepare();
			mMediaRecorder.start();
			isRecording = true;
		} catch (IllegalStateException e) {

		} catch (IOException e) {

		}
	}

	/**
	 * @return the isRecording
	 */
	public boolean isRecording() {
		return isRecording;
	}

	public void stopRecord() {
		if (mMediaRecorder != null) {
			mMediaRecorder.stop();
			// clear recorder configuration
			mMediaRecorder.reset();
			// release the recorder object
			mMediaRecorder.release();
			this.isRecording = false;
			mMediaRecorder = null;
			// Lock camera for later use i.e taking it back from MediaRecorder.
			// MediaRecorder doesn't need it anymore and we will release it if
			// the activity pauses.
			mCamera.lock();
		}
	}
}
           

布局檔案

<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">
	<TextureView 
	    android:id="@+id/texture_content"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"/>
	<Button 
	    android:id="@+id/btn_takepic"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="拍照"
	    android:layout_alignParentTop="true"
	    android:layout_centerHorizontal="true"/>
	<Button 
	    android:id="@+id/btn_cap"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="錄像"
	    android:layout_centerHorizontal="true"
	    android:layout_alignParentBottom="true"/>
	<ImageView 
	    android:id="@+id/iv_takepic"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    />
</RelativeLayout>
           

權限随便從公司的項目裡弄的,能用就好,沒有仔細看

<uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" >
    </uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

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

    <!-- 懸浮視窗權限 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <!-- GPS權限 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- 藍牙權限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />

    <!-- 調節螢幕亮度權限 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />

    <!-- 2G3G信号權限 -->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" >
    </uses-permission>
    <!-- 往SDCard寫入資料權限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
    </uses-permission>
    <uses-permission android:name="android.permission.RESTART_PACKAGES" >
    </uses-permission>
    <!-- <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
           

http://download.csdn.net/detail/qq_22256027/9492763     這是源碼下載下傳位址

http://download.csdn.net/detail/qq_22256027/9492761  opengl的解決方式,個人感覺比camera2的好,很簡單

下一篇文章我打算分享一下我在github中學到的一個非常好的架構  MVP  利用接口和回調将邏輯部分和界面分離,