Camera開發系列之二-相機預覽數據回調android
Camera開發系列之四-使用MediaMuxer封裝編碼後的音視頻到mp4容器github
Camera開發系列之五-使用MediaExtractor製做一個簡易播放器緩存
Camera開發系列之七-使用GLSurfaceviw繪製Camera預覽畫面 ide
本篇文章主要實現的功能以下:函數
先上效果圖:post
相機拍照功能的實現主要依賴這兩個方法:優化
void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg) void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) 複製代碼
第二個方法等同於void takePicture( shutter, raw, null, jpeg),因此這裏直接看第一個方法,先看第一個參數的官方文檔解釋:
Called as near as possible to the moment when a photo is captured from the sensor. This is a good opportunity to play a shutter sound or give other feedback of camera operation. This may be some time after the photo was triggered, but some time before the actual data is available.
大概就是能夠在這個方法裏進行拍照前的一些設置,好比播放快門聲音之類的。
第二個參數的官方文檔解釋:
the callback for image capture moment, or null
在原始圖像數據可用時觸發,這裏的原始數據是指未經處理的yuv數據,若是須要本身編碼圖片,可使用該回調獲取數據。
第三個參數的官方文檔解釋:
callback with postview image data, may be null
postview圖像數據的回調,不是全部硬件都支持這個,可能爲空。
第四個參數的官方文檔解釋:
The jpeg callback occurs when the compressed image is available
JPEG圖像數據的回調,通過android底層處理好的數據,可能爲空。
這裏我就直接拿到jpeg數據作存儲操做了,這裏須要注意的是照片數據多是旋轉過的,須要將照片旋轉回來。
拍照並保存爲jpeg格式的文件:
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File file = null;
try {
if (mPicListener != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
//由於照片有多是旋轉的,這裏要作一下處理
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraId, info);
Bitmap realBmp = FileUtil.rotaingBitmap(info.orientation, bitmap);
file = FileUtil.saveFile(realBmp, mFileName, mFileDir + "/");
mPicListener.onPictureTaken("", file);
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.i("錯誤: " + e.getMessage());
if (mPicListener != null) {
mPicListener.onPictureTaken("保存失敗:" + e.getMessage(), file);
}
}
mCamera.startPreview(); //拍照以後須要從新設置預覽畫面
}
});
複製代碼
按角度旋轉bitmap:
public static Bitmap rotaingBitmap(int angle, Bitmap bitmap) {
Bitmap returnBm = null;
// 根據旋轉角度,生成旋轉矩陣
Matrix matrix = new Matrix();
matrix.postRotate(angle);
try {
// 將原始圖片按照旋轉矩陣進行旋轉,並獲得新的圖片
returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (returnBm == null) {
returnBm = bitmap;
}
if (bitmap != returnBm && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
return returnBm;
}
複製代碼
嗯...好像是那麼回事兒了,趕忙拍張照壓壓驚:
恩,很完美,看看前置的攝像頭拍的效果如何:
仍是很完美,有人就會說我在這兒胡扯了,你這拍出來的效果根本就不對好伐!沒看見文字都不同嗎??
兄dei別激動,先放下你手裏的刀,聽我慢慢跟你解釋清楚,解釋不清楚你再動手也不遲:
通常前置攝像頭有270度的旋轉,並且作了鏡像翻轉。鏡像翻轉指的是將屏幕進行水平的翻轉,達到全部內容顯示都會反向的效果,就像是在鏡子中看到的界面同樣。若是不想要這樣的效果,能夠拿到拍照的原始數據進行旋轉270度和鏡像翻轉:
private byte[] rotateYUVDegree270AndMirror(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate and mirror the Y luma
int i = 0;
int maxY = 0;
for (int x = imageWidth - 1; x >= 0; x--) {
maxY = imageWidth * (imageHeight - 1) + x * 2;
for (int y = 0; y < imageHeight; y++) {
yuv[i] = data[maxY - (y * imageWidth + x)];
i++;
}
}
// Rotate and mirror the U and V color components
int uvSize = imageWidth * imageHeight;
i = uvSize;
int maxUV = 0;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
maxUV = imageWidth * (imageHeight / 2 - 1) + x * 2 + uvSize;
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[maxUV - 2 - (y * imageWidth + x - 1)];
i++;
yuv[i] = data[maxUV - (y * imageWidth + x)];
i++;
}
}
return yuv;
}
複製代碼
首先不要忘記添加錄音權限:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
複製代碼
這裏錄像功能的實現我依賴於android自帶的MediaRecorder類,MediaRecorder是Android系統自帶的一種很是強大的音頻錄製的控件,能夠錄製聲音,也能夠經過調用Camera達到錄製視頻的效果。MediaRecorder包含了Audio和video的記錄功能,在Android的系統裏,Music和Video兩個應用程序都是調用MediaRecorder實現的。
本篇文章主要講camer,對於的一些具體實現和方法就不在這兒具體贅述,有想了解的同窗能夠看這篇文章:Android系統的錄音功能MediaRecorder,我這裏就直接上代碼了:
public boolean initRecorder(String filePath, SurfaceHolder holder) {
if (!mInitCameraResult) {
LogUtil.i("相機未初始化成功");
return false;
}
try {
// TODO init button
//mCamera.stopPreview();
mediaRecorder = new MediaRecorder();
mCamera.unlock();
mediaRecorder.setCamera(mCamera);
if (mCameraId == 1) {
mediaRecorder.setOrientationHint(270);
} else {
mediaRecorder.setOrientationHint(90);
}
// 這兩項須要放在setOutputFormat以前,設置音頻和視頻的來源
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);//攝錄像機
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//相機
// 設置錄製完成後視頻的封裝格式THREE_GPP爲3gp.MPEG_4爲mp4
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//這兩項須要放在setOutputFormat以後 設置編碼器
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 設置錄製的視頻編碼h263 h264
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 設置視頻錄製的分辨率。必須放在設置編碼和格式的後面,不然報錯
mediaRecorder.setVideoSize(mWidth, mHeight);
// 設置視頻的比特率 (清晰度)
mediaRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);
// 設置錄製的視頻幀率。必須放在設置編碼和格式的後面,不然報錯
/*if (defaultVideoFrameRate != -1) { mediaRecorder.setVideoFrameRate(defaultVideoFrameRate); }*/
// 設置視頻文件輸出的路徑 .mp4
mediaRecorder.setOutputFile(filePath);
mediaRecorder.setMaxDuration(30000);
mediaRecorder.setPreviewDisplay(holder.getSurface());
mediaRecorder.prepare();
mediaRecorder.start(); //開始
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
複製代碼
這裏要注意的一個地方是上面備註提到的順序!順序!順序!重要的事兒說三遍,順序千萬不能亂!若是順序不對,可能會出現沒法調用start()方法或者調用start()後閃退的狀況。
和拍照函數相似的,主要看下面兩個方法:
setPreviewCallback(Camera.PreviewCallback cb)
setPreviewCallbackWithBuffer(Camera.PreviewCallback cb)
複製代碼
看看官方文檔的解釋:
對第一個方法:
Installs a callback to be invoked for every preview frame in addition to displaying them on the screen. The callback will be repeatedly called for as long as preview is active. This method can be called at any time, even while preview is live. Any other preview callbacks are overridden.
除了在屏幕上顯示預覽以外,還增長一個回調函數,在每一幀出現時調用。只要預覽處於活動狀態,就會重複調用回調。這種方法能夠隨時調用,保證預覽是實時的。
第二個方法:
Installs a callback to be invoked for every preview frame, using buffers supplied with addCallbackBuffer(byte[]), in addition to displaying them on the screen.
在攝像頭開啓時增長一個回調函數,在每一幀出現時調用.經過addCallbackBuffer(byte[])使用一個緩存容器來顯示這些數據.
這裏推薦使用第二個方法,第二個方法其實就是經過內存複用來提升預覽的效率,可是若是沒有調用這個方法addCallbackBuffer(byte[]),幀回調函數就不會被調用,也就是說在每一次回調函數調用後都必須調用addCallbackBuffer(byte[])。(因此能夠直接在onPreviewFrame中調用addCallbackBuffer(byte[]),即camera.addCallbackBuffer(data);),複用這個原來的內存地址便可。是否是聽懵了?不要緊,直接看代碼更容易理解:
//1.設置回調:系統相機某些核心部分不走JVM,進行特殊優化,因此效率很高
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] datas, Camera camera) {
//回收緩存處理
camera.addCallbackBuffer(datas);
}
});
//2.增長緩衝區buffer: 這裏指定的是yuv420sp格式
mCamera.addCallbackBuffer(new byte[((width * height) *
ImageFormat.getBitsPerPixel(ImageFormat.NV21)) / 8]);
複製代碼
onPreviewFrame這個回調函數是在Camera.open(int)從中調用的事件線程上調用的,它的第一個參數byte[] datas就是咱們須要的實時視頻流數據。注意:若是Camera.Parameters.setPreviewFormat(int) 從未被調用,則datas數據默認爲YCbCr_420_SP(NV21)格式。
如今獲得了相機的實時視頻流,就能夠進行編碼封裝格式保存了,不過須要注意的仍是旋轉角度問題,這裏要根據以前的旋轉角度將視頻流數據進行相應的旋轉鏡像操做,若是是前置攝像頭,前面拍照已經給出解決方案,若是是後置攝像頭,則可能須要旋轉90度:
private byte[] rotateYUVDegree90(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate the Y luma
int i = 0;
for (int x = 0; x < imageWidth; x++) {
for (int y = imageHeight - 1; y >= 0; y--) {
yuv[i] = data[y * imageWidth + x];
i++;
}
}
// Rotate the U and V color components
i = imageWidth * imageHeight * 3 / 2 - 1;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i--;
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i--;
}
}
return yuv;
}
複製代碼
關於相機開發,最坑的地方仍是旋轉角度的問題,不一樣手機可能有不一樣的旋轉角度。鑑於本章篇幅有限,下篇再介紹如何利用Android自帶的編碼類Mediacodec硬編碼yuv數據爲h264。博主剛開始寫博客,文字表達能力不是很好,若是有表述不清或者錯誤的地方,歡迎你們指出==
參考文章:
分享幾個Android攝像頭採集的YUV數據旋轉與鏡像翻轉的方法
Android系統自帶的MediaRecorder結合Camera實現視頻錄製及播放功能
項目地址:camera開發從入門到入土 歡迎start和fork