Android 静默拍摄

Posted 疼老婆的coder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 静默拍摄相关的知识,希望对你有一定的参考价值。

引言:
在做用户的头像时,忽然想到前段时间(可能是很久以前了),支付宝传出偷偷拍摄用户的生活照,真实头像,被喷的很厉害。然而作为android开发者的我第一反应竟然是握草,他是怎么实现的。在我印象中,ios对权限的控制是很严格的,偷偷调起摄像头这种行为应该是很困难的。然而Android4.2之前可以说开发者几乎拥有了系统权限,能力之强简直可怕。而现在Android已经到了7.0,虽然大多说用户还是在4.4到6.0的。我想我也来做一个静默拍摄的app。

正文:
所谓静默拍摄就是在用户毫无感知的情况下拍摄。
一般的拍照都会有预览区域,拍照声。去掉这些东西才算是真正意义上的静默拍摄。
首先,做了一个非常正常的自拍软件,就一个按钮。拍完之后存到文件夹的一个位置。然后我试了一下,完全ok并没有什么难度。然后就是清空surfaceView了。我首先想到的就是setVisiblity为gone,然后就报错了。很尴尬。下一个方案就是用高度和宽度都是0的方法,然而并没有什么卵用,更加尴尬。
然后想想没有有什么好办法了那就把这个surfaceView盖住好了,非常完美,随便搞一搞就盖住了,然后照片照样拍。合理。
但是“咔嚓”一声的拍照声实在令人尴尬,然后我就想到了静音,在页面打开的时候就设置静音。看上去这是一个非常稳健的方法,然后就发生了更加尴尬的事情。设置静音的时候,手机振动了一下,震一下也就算了,关键是还没有把拍照的声音去除。然后我就去查了查了相机音量应该是哪个。之后悲催的事情就发生了:
Google的Android开发者为了Android用户的用户体验,也为了避免开发者开发出静默拍摄的app从而侵犯了隐私,他们就把快门声音的播放函数写在了拍照的方法里面,还是写在framework层的。瞬间我就很难过了。作为一个平凡的第三方开发者,我并没有那么多权限去改变framework层的方法。
然后智慧的我决定曲线救国。因为在预览的时候,并没有进行拍照,但实际上我们已经拿到了相机带来的图片流。这很关键。然后我就把这个图片流变成了bitmap,然后保存到了本地,接着就把相机关了。神不知鬼不觉地把自拍拿到了。当然其中有一点小问题,比如图片编码,图片旋转,本地存储,获取帧图像都是各种各样的问题。但这些都是可以解决的。思路依旧是我上面提到的思路,各种表现方式可以由大家自己搞。

public class MainActivity extends AppCompatActivity 
    static final String TAG =  "CAMERA ACTIVITY";

    //Camera object
    Camera mCamera;
    //Preview surface
    SurfaceView surfaceView;
    //Preview surface handle for callback
    SurfaceHolder surfaceHolder;
    //Camera button
    Button btnCapture;
    //Note if preview windows is on.
    boolean previewing;

    int mCurrentCamIndex = 0;
    private AudioManager manager;
    private int volumn;
    private boolean canTake=false;
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnCapture = (Button) findViewById(R.id.btn_capture);
        imageView =(ImageView)findViewById(R.id.iv);
        btnCapture.setOnClickListener(new Button.OnClickListener() 
            public void onClick(View arg0) 
               canTake=true;
            
        );

        surfaceView = (SurfaceView) findViewById(R.id.surfaceView1);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(new SurfaceViewCallback());
        //surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    

    public void getSurfacePic(byte[] data, Camera camera,String name)
        Camera.Size size = camera.getParameters().getPreviewSize();
        YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
        if(image!=null)
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);

            Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());

            //**********************
            //因为图片会放生旋转,因此要对图片进行旋转到和手机在一个方向上
            rotateMyBitmap(bmp,name);
            //**********************************


        
    

    /** 保存方法 */
    public void saveBitmap(Bitmap bm,String name) 
        Log.e(TAG, "保存图片");
        File f = new File("/sdcard/namecard/", name);
        if (f.exists()) 
            f.delete();
        
        try 
            FileOutputStream out = new FileOutputStream(f);
            bm.compress(Bitmap.CompressFormat.PNG, 90, out);
            out.flush();
            out.close();
            Log.e(TAG, "已经保存");
         catch (FileNotFoundException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IOException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        

    

    /**
     * 保存图片到指定文件夹
     *
     * @param bmp
     * @return
     */
    private boolean saveBitmapTofile(byte[] bmp) 
        String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
                .toString()
                + File.separator
                + "PicTest_" + System.currentTimeMillis() + ".jpg";
        File file = new File(fileName);
        if (!file.getParentFile().exists()) 
            file.getParentFile().mkdir();
        

        try 
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream(file));
            bos.write(bmp);
            bos.flush();
            bos.close();
            scanFileToPhotoAlbum(file.getAbsolutePath());
            Toast.makeText(MainActivity.this, "[Test] Photo take and store in" + file.toString(),Toast.LENGTH_LONG).show();
         catch (Exception e) 
            Toast.makeText(MainActivity.this, "Picture Failed" + e.toString(),
                    Toast.LENGTH_LONG).show();
        
        return true;
    

    public void saveMyBitmap(Bitmap mBitmap,String bitName)  
        String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
                .toString()
                + File.separator
                + "PicTest_" + System.currentTimeMillis() + ".jpg";
        File file = new File(fileName);
        if (!file.getParentFile().exists()) 
            file.getParentFile().mkdir();
        
        FileOutputStream fOut = null;
        try 
            fOut = new FileOutputStream(file);
         catch (FileNotFoundException e) 
            e.printStackTrace();
        

        try 
            if (null != fOut) 
                mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
                fOut.flush();
                fOut.close();
            
         catch (Exception e) 
            e.printStackTrace();
        

    

    public void rotateMyBitmap(Bitmap bmp,String name)
        //*****旋转一下
        Matrix matrix = new Matrix();
        matrix.postRotate(270);

        Bitmap bitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), Bitmap.Config.RGB_565);

        Bitmap nbmp2 = Bitmap.createBitmap(bmp, 0,0, bmp.getWidth(),  bmp.getHeight(), matrix, true);

        saveMyBitmap(compressImage(nbmp2),"cool");

        //*******显示一下
        imageView.setImageBitmap(nbmp2);

    ;
    /**
     * 压缩图片
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        // 把压缩后的数据baos存放到ByteArrayInputStream中
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        // 把ByteArrayInputStream数据生成图片
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
        return bitmap;
    



    Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() 
        @Override
        public void onShutter() 
        
    ;

    Camera.PictureCallback rawPictureCallback = new Camera.PictureCallback() 
        @Override
        public void onPictureTaken(byte[] arg0, Camera arg1) 

        
    ;

    Camera.PictureCallback jpegPictureCallback = new Camera.PictureCallback() 
        @Override
        public void onPictureTaken(byte[] arg0, Camera arg1) 

            String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
                    .toString()
                    + File.separator
                    + "PicTest_" + System.currentTimeMillis() + ".jpg";
            File file = new File(fileName);
            if (!file.getParentFile().exists()) 
                file.getParentFile().mkdir();
            

            try 
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(file));
                bos.write(arg0);
                bos.flush();
                bos.close();
                scanFileToPhotoAlbum(file.getAbsolutePath());
                Toast.makeText(MainActivity.this, "[Test] Photo take and store in" + file.toString(),Toast.LENGTH_LONG).show();
             catch (Exception e) 
                Toast.makeText(MainActivity.this, "Picture Failed" + e.toString(),
                        Toast.LENGTH_LONG).show();
            
        ;
    ;

    public void setVolumnSilence()
        manager = (AudioManager) this
                .getSystemService(Context.AUDIO_SERVICE);
        manager.setStreamMute(AudioManager.STREAM_SYSTEM, false);
        volumn = manager.getStreamVolume(AudioManager.STREAM_SYSTEM);
        if (volumn != 0) 
            // 如果需要静音并且当前未静音(muteMode的设置可以放在Preference中)
            manager.setStreamVolume(AudioManager.STREAM_SYSTEM, 0,
                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
        
    

    public void scanFileToPhotoAlbum(String path) 

        MediaScannerConnection.scanFile(MainActivity.this,
                new String[]  path , null,
                new MediaScannerConnection.OnScanCompletedListener() 

                    public void onScanCompleted(String path, Uri uri) 
                        Log.i("TAG", "Finished scanning " + path);
                    
                );
    

    public void cameraRefresh(String picPath) 
        Toast.makeText(this,picPath,Toast.LENGTH_SHORT).show();
    

    private final class SurfaceViewCallback implements android.view.SurfaceHolder.Callback 
        public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
        
            if (previewing) 
                mCamera.stopPreview();
                previewing = false;
            

            try 
                mCamera.setPreviewDisplay(arg0);
                mCamera.startPreview();
                previewing = true;
                setCameraDisplayOrientation(MainActivity.this, mCurrentCamIndex, mCamera);
             catch (Exception e) 
        
        public void surfaceCreated(SurfaceHolder holder) 
//              mCamera = Camera.open();
            //change to front camera
            mCamera = openFrontFacingCameraGingerbread();
            // get Camera parameters
            Camera.Parameters params = mCamera.getParameters();

            List<String> focusModes = params.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) 
                // Autofocus mode is supported
            
            mCamera.setPreviewCallback(new Camera.PreviewCallback() 
                @Override
                public void onPreviewFrame(byte[] bytes, Camera camera) 
                    Log.e("stuart","onPreviewFrame "+canTake);
                    if(canTake) 
                        getSurfacePic(bytes, camera, "hahahaah");
                        canTake=false;
                    
                
            );
        

        public void surfaceDestroyed(SurfaceHolder holder) 
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
            previewing = false;
        


    

    private Camera openFrontFacingCameraGingerbread() 
        int cameraCount = 0;
        Camera cam = null;
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        cameraCount = Camera.getNumberOfCameras();

        for (int camIdx = 0; camIdx < cameraCount; camIdx++) 
            Camera.getCameraInfo(camIdx, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 
                try 
                    cam = Camera.open(camIdx);
                    mCurrentCamIndex = camIdx;
                 catch (RuntimeException e) 
                    Log.e(TAG, "Camera failed to open: " + e.getLocalizedMessage());
                
            
        

        return cam;
    

    private static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera)
    
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();

        //degrees  the angle that the picture will be rotated clockwise. Valid values are 0, 90, 180, and 270.
        //The starting position is 0 (landscape).
        int degrees = 0;
        switch (rotation)
        
            case Surface.ROTATION_0: degrees = 0; break;
            case Surface.ROTATION_90: degrees = 90; break;
            case Surface.ROTATION_180: degrees = 180; break;
            case Surface.ROTATION_270: degrees = 270; break;
        
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
        
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        
        else
        
            // back-facing
            result = (info.orientation - degrees + 360) % 360;
        
        camera.setDisplayOrientation(result);
    

基本上呢,这一个代码就能实现简单的静默拍照了。
依旧存在的问题:
图片质量实在有点低。
目前来看这也是没有办法的,因为我只能取到surfaceView的帧图像,而显示在preview中的帧图像质量又是非常感人的。所以不得不说这真是没什么办法。
本文地址:http://blog.csdn.net/u011767404/article/details/64919459
osChina :http://git.oschina.net/stuartPounds/SilenceTakingPicture

以上是关于Android 静默拍摄的主要内容,如果未能解决你的问题,请参考以下文章

android静音相机快门声音?

Android 应用程序在应用程序启动时设置为静默模式

如何检测iOS设备是否处于静默模式?

iOS 13 和静音通知

有没有办法让应用程序将通知更改为静默通知(不是服务器)以进行推送通知

续集静音模式