Android 自定义Camera 使用TextureView GLSurfaceView预览

      前言:抱着最起码的要求尽力去做好每一件事 ! ——秋不白

     记录学习音视频的过程,目前到 了音视频的录制,后面再学习openGl ES,FFmpeg等

      前面有使用SurfaceView 来预览Camera以及拍照,根据重力传感器来动态设置Camera.setRotation(degree),横着拍摄的照片,应该是横着的,竖着拍摄的照片应该竖着的,对吧,符合常识。后来用TextureView GLSurfaceView 来预览Camera,我对CameraUtil进行了更改。TextureView GLSurfaceView来预览,工具类操作相同,主要是TextureView GLSurfaceView对应的回调处理方法不同,道理都是一样的,直接上代码吧

效果图:

      先说明,预览和保存的分辨率都是写死的,保存照片的分辨率可以修改,有兴趣去码云上获取完整代码,查看对应的代码https://gitee.com/redrose/Demo

package com.redrose.videodemo.camera;

import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.view.SurfaceHolder;

import com.redrose.videodemo.utils.LogUtils;

import java.io.IOException;
import java.util.List;
/**
 * Desc: Camera 工具类
 * @author: RedRose
 * Date: 2019/5/3
 * Email: yinsxi@163.com
 */
public class CameraUtil2 {
    public static final String TAG = "CameraUtil2";
    private static CameraUtil2 mInstance;
    private static boolean isPreviewing = false;
    /**
     * 相机参数对象
     */
    private Camera.Parameters mParams;
    private Camera.Parameters mParameters;
    /**
     * 闪光灯自动
     */
    public static final int FLASH_AUTO = 0;
    /**
     * 闪光灯关闭
     */
    public static final int FLASH_OFF = 1;
    /**
     * 闪光灯开启
     */
    public static final int FLASH_ON = 2;

    private CameraUtil2() {
    }

    private static final Object o = new Object();

    public static CameraUtil2 getInstance() {
        if (mInstance == null) {
            synchronized (o) {
                if (mInstance == null) {
                    mInstance = new CameraUtil2();
                }
            }
        }
        return mInstance;
    }

    private Camera mCamera;

    public void doStartPreview(SurfaceTexture surface) {
        LogUtils.i(TAG, "doStartPreview...");
        if (isPreviewing) {
            mCamera.stopPreview();
            return;
        }
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(surface);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            setProperty();
        }

    }

    public void doStartPreview(SurfaceHolder holder) {
        LogUtils.i(TAG, "doStartPreview...");
        if (isPreviewing) {
            mCamera.stopPreview();
            return;
        }
        if (mCamera != null) {
            try {
                mCamera.setPreviewDisplay(holder);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            setProperty();
        }


    }
    public Camera openCamera() {
        // 0 表示开启后置相机
        return openCamera(0);
    }

    private Camera openCamera(int id) {
        if (mCamera == null) {
            mCamera = Camera.open(id);
        }
//        setProperty();
        return mCamera;
    }

    /**
     * 相机属性设置
     */
    private void setProperty() {
        //设置相机预览页面旋转90°,(默认是横屏)
        mCamera.setDisplayOrientation(90);
        mParameters = mCamera.getParameters();
        //设置将保存的图片旋转90°(竖着拍摄的时候)
        mParameters.setRotation(90);
        mParameters.setPictureFormat(ImageFormat.NV21);
        mParameters.setPreviewFormat(ImageFormat.NV21);
        mParameters.setPreviewSize(1920, 1080);
        mParameters.setPictureSize(1920, 1080);
//        mParameters.setPictureSize(4608, 3456);
        mParameters.setPictureFormat(ImageFormat.JPEG);
        mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
//        mParameters.set(ImageFormat.YUV_444_888);
        mCamera.setParameters(mParameters);
        mCamera.startPreview();
        isRelease = false;
        isPreviewing = true;
    }

    /**
     * 选装图片的角度
     */
    public void setRotateDegree(int degree) {
//        boolean result = degree == 0 || degree == 90
//        mParameters.setRotation(90);
        if (mCamera != null) {
            mParameters = mCamera.getParameters();
            mParameters.setRotation(degree);
            mCamera.setParameters(mParameters);
        }

    }
    public boolean getIsPreview(){
        return isPreviewing;
    }
    /**
     * 获取支持的预览分辨率
     */
    public List<Camera.Size> getPreviewSizeList() {
        if (mCamera == null) {
            throw new NullPointerException("Camera can not be null");
        }
        return mCamera.getParameters().getSupportedPreviewSizes();
    }

    /**
     * 获取保存图片支持的分辨率
     */
    public List<Camera.Size> getPictureSizeList() {
        if (mCamera == null) {
            throw new NullPointerException("Camera can not be null");
        }
        return mCamera.getParameters().getSupportedPictureSizes();
    }

    /**
     * 设置闪光灯模式
     */
    public void setFlashMode(int mode) {
        if (mCamera == null) {
            return;
        }
        mParameters = mCamera.getParameters();
        String flashMode = mParameters.getFlashMode();
        switch (mode) {
            case FLASH_AUTO:
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
                break;
            case FLASH_OFF:
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                break;
            case FLASH_ON:
                mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
                break;
            default:
                break;
        }
        mCamera.setParameters(mParameters);
    }

    /**
     * 释放相机资源
     */
    public void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;
            isRelease = true;
            isPreviewing = false;
        }
    }

    /**
     * 是否旋转图片 true 选装
     */
    private boolean isRelease = false;

    public boolean getIsRelease() {
        return isRelease;
    }

    /**
     * 设置保存图片的分辨率
     */
    public void setSaveSize(Camera.Size saveSize) {
        mParameters.setPictureSize(saveSize.width, saveSize.height);
        mCamera.setParameters(mParameters);
    }
}
CameraGLSurfaceViewActivity(看名字就知道哦,这几个Activity 代码冗余量很大,也是本人懒,也是在学习阶段,有音频PCM录制和编码wav播放,有视频简易录制编码,会持续更新)
package com.redrose.videodemo.camera;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.hardware.Camera;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;

import com.redrose.videodemo.R;
import com.redrose.videodemo.base.BaseActivity;
import com.redrose.videodemo.bean.Photo;
import com.redrose.videodemo.bean.Time;
import com.redrose.videodemo.utils.BitmapCallBack;
import com.redrose.videodemo.utils.GlideUtils;
import com.redrose.videodemo.utils.IntentUtils;
import com.redrose.videodemo.utils.LogUtils;
import com.redrose.videodemo.utils.MySensorHelper;
import com.redrose.videodemo.utils.SPUtils;
import com.redrose.videodemo.utils.ToastUtil;
import com.redrose.videodemo.utils.ToolUtils;
import com.redrose.videodemo.view.PreViewGlsurfaceView;
import com.redrose.videodemo.view.PreviewTextureView;
import com.zhy.adapter.recyclerview.CommonAdapter;
import com.zhy.adapter.recyclerview.base.ViewHolder;

import org.litepal.crud.DataSupport;
import org.xutils.view.annotation.ContentView;
import org.xutils.view.annotation.Event;
import org.xutils.view.annotation.ViewInject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * Desc:
 * Author: RedRose
 * Date: 2019/3/20
 * Email: yinsxi@163.com
 */
@ContentView(R.layout.activity_camera_glsurfaceview)
public class CameraGLSurfaceViewActivity extends BaseActivity implements BitmapCallBack {
    @ViewInject(R.id.pre_text)
    private TextView mPreSize;
    @ViewInject(R.id.save_text)
    private TextView mSaveSize;
    @ViewInject(R.id.flashlight)
    private ImageView mFlashView;
    @ViewInject(R.id.photo_list)
    private ImageView mPreImageView;
    @ViewInject(R.id.take_photo)
    private View mTakePhoto;
    @ViewInject(R.id.camera_preview)
    private PreViewGlsurfaceView mPreview;
    private boolean showLocation = false;
    private PopupWindow mPopWindow;
    private List<Photo> mPhotoList;
    private Time mTime;
    private boolean isTakePhoto = true;
    /**
     * 重力传感器监听
     * 横着 竖着状态
     */
    private MySensorHelper mMySensor;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置全屏
//        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        initData();
    }

    private void initData() {
        mMySensor = new MySensorHelper(this);
        mMySensor.enable();
        int mode = SPUtils.getInt("Flash_mode");
        initFlashMode(mode);
        mPreview.setBitmapCallback(this);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar calendar = Calendar.getInstance();
        String format = sdf.format(calendar.getTime());
        mTime = new Time();
        mTime.setClassId(Long.parseLong(format));
        mTime.setDay(calendar.get(Calendar.DAY_OF_MONTH));
        mTime.setYear(calendar.get(Calendar.YEAR));
        mTime.setMonth(calendar.get(Calendar.MONTH) + 1);
        mPhotoList = new ArrayList<>();
    }

    private void showPopWindow(View view) {
        mPopWindow = new PopupWindow(view, ToolUtils.dp2px(mContext.getApplicationContext(),
                200), ToolUtils.dp2px(mContext.getApplicationContext(), 200));
        mPopWindow.setFocusable(true);
//        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3F51B5")));
        mPopWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        mPopWindow.setOutsideTouchable(true);
        mPopWindow.update();
        if (showLocation) {
            mPopWindow.showAsDropDown(mSaveSize);
        } else {
            mPopWindow.showAsDropDown(mPreSize);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        mPreview.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mPreview.onPause();
    }

    /**
     * 显示支持保存的分辨率
     */
    private void showSupportSize(List<Camera.Size> list) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        View view = inflater.inflate(R.layout.pop_size_layout, null);
        RecyclerView recycleView = view.findViewById(R.id.recycle_size);
        initRecycle(recycleView);
        SizeAdapter mAdapter = new SizeAdapter(mContext, R.layout.size_item, list);
        recycleView.setAdapter(mAdapter);
        showPopWindow(view);
    }

    @Event(value = {R.id.pre_text, R.id.save_text, R.id.flashlight, R.id.take_photo, R.id.photo_list,
            R.id.videoStop,
            R.id.videoStart,
            R.id.all_list})
    private void onclickView(View view) {
        int id = view.getId();
        switch (id) {
            /*暂时注释 动态设置预览分辨率*/
            case R.id.pre_text:
                /*预览分辨率,建议设置成屏幕分辨率,经测试,支持的最高就是屏幕分辨率 */
                showLocation = false;
                showSupportSize(CameraUtil.getInstance().getPreviewSizeList());
                break;
            //设置保存图片的分辨率
            case R.id.save_text:
                showLocation = true;
                showSupportSize(CameraUtil2.getInstance().getPictureSizeList());
                break;
            //切换闪光灯模式
            case R.id.flashlight:
                int mode = SPUtils.getInt("Flash_mode");
                if (mode == CameraUtil.FLASH_AUTO) {
                    mode = CameraUtil.FLASH_OFF;
                } else if (mode == CameraUtil.FLASH_OFF) {
                    mode = CameraUtil.FLASH_ON;
                } else if (mode == CameraUtil.FLASH_ON) {
                    mode = CameraUtil.FLASH_AUTO;
                }
                SPUtils.put("Flash_mode", mode);
                initFlashMode(mode);
                break;
            //拍照
            case R.id.take_photo:
                mPreview.takePhoto();


                break;
            //打开当前拍摄的照片图库
            case R.id.photo_list:
                int size = mPhotoList.size();
                if (size == 0) {
                    ToastUtil.show(mContext, "还未拍摄照片,可以点击右侧查看所有照片");
                    return;
                }
                Bundle bundle = new Bundle();
                bundle.putSerializable("photoList", (ArrayList<Photo>) mPhotoList);
                IntentUtils.goToPreviewActivity(mContext, bundle);
                break;
            //打开图库
            case R.id.all_list:
                IntentUtils.goToImageActivity(mContext, null);
                break;
            case R.id.videoStart:
                mPreview.startVideo();
                break;
            case R.id.videoStop:
                mPreview.stopVideo();
                break;
            default:
                break;
        }
    }

    @Event(type = View.OnLongClickListener.class, value = R.id.take_photo)
    private boolean onLongClickView(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.take_photo:
//                mPreview.startVideo();
                break;
            default:
                break;
        }
        return false;
    }

    private void initRecycle(RecyclerView recyclerView) {
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        //设置布局管理器
        recyclerView.setLayoutManager(layoutManager);
        //设置为垂直布局,这也是默认的
        layoutManager.setOrientation(OrientationHelper.VERTICAL);
        //设置Adapter
//        recyclerView.setAdapter(recycleAdapter);
        //设置分隔线
//        recyclerView.addItemDecoration(new DividerGridItemDecoration(this));
        //设置增加或删除条目的动画
        recyclerView.setItemAnimator(new DefaultItemAnimator());
    }

    @Override
    public void backByte(byte[] data) {
        GlideUtils.loadByte(mPreImageView, data);
        ToolUtils.saveImageFile(data, mPhotoList, mIsRotate);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar calendar = Calendar.getInstance();
        String format = sdf.format(calendar.getTime());
        List<Time> times = DataSupport.where("classId = ?", format).find(Time.class);
        mTime.setPhotoList(mPhotoList);
        if (times.size() == 0) {
            mTime.save();
        }
    }

    /**
     * Desc: 相机页面,拍摄照片保存分辨率 显示适配器
     * author: RedRose
     * Date: 2019/4/1
     * Email: yinsxi@163.com
     */
    private class SizeAdapter extends CommonAdapter<Camera.Size> {

        private SizeAdapter(Context context, int layoutId, List<Camera.Size> datas) {
            super(context, layoutId, datas);
        }

        @Override
        protected void convert(ViewHolder holder, Camera.Size size, int position) {
            LogUtils.d("----redrose 装载数据测试 ----");
            TextView textView = holder.getView(R.id.size_text);
            String sizeString = String.format("%d * % d", size.width, size.height);
            textView.setText(sizeString);
            if (showLocation) {
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        CameraUtil2.getInstance().setSaveSize(size);
                        if (mPopWindow != null) {
                            mPopWindow.dismiss();
                            mPopWindow = null;
                        }
                        ToastUtil.show(mContext, size.width + "x" + size.height);
                    }
                });
            }
        }
    }

    private void initFlashMode(int mode) {
        if (mode == CameraUtil.FLASH_AUTO) {
            mFlashView.setImageResource(R.mipmap.flash_auto);
        } else if (mode == CameraUtil.FLASH_OFF) {
            mFlashView.setImageResource(R.mipmap.flash_off);
        } else if (mode == CameraUtil.FLASH_ON) {
            mFlashView.setImageResource(R.mipmap.flash_on);
        }
        CameraUtil2.getInstance().setFlashMode(mode);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMySensor.disable();
        LogUtils.e(TAG, " ---onDestroy---");
    }

    /**
     * 是否旋转图片
     */
    @Override
    public void setIsRotate(boolean isRotate) {
        if (mIsRotate == isRotate) {
            return;
        }
        this.mIsRotate = isRotate;
        if (mIsRotate) {
            CameraUtil2.getInstance().setRotateDegree(0);
        } else {
            CameraUtil2.getInstance().setRotateDegree(90);
        }
    }

    @Override
    public boolean getIsRotate() {
        return mIsRotate;
    }

    @Override
    protected void onStop() {
        super.onStop();
        CameraUtil2.getInstance().releaseCamera();
    }

    private boolean supportH264Codec() {
        // 遍历支持的编码格式信息,并查询有没有支持H.264(avc)的编码
        if (Build.VERSION.SDK_INT >= 18) {
            //计算可用的编解码器数量
            int number = MediaCodecList.getCodecCount();
            for (int i = number - 1; i > 0; i--) {
                //获得指定的编解码器信息
                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
                //得到支持的类型
                String[] types = codecInfo.getSupportedTypes();
                //查询有没有支持H.264(avc)的编码
                for (int j = 0; j < types.length; j++) {
                    if (types[j].equalsIgnoreCase("video/avc")) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
PreViewGlsurfaceView
package com.redrose.videodemo.view;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.redrose.videodemo.camera.CameraUtil2;
import com.redrose.videodemo.camera.DirectDrawer;
import com.redrose.videodemo.camera.H264Encoder;
import com.redrose.videodemo.utils.BitmapCallBack;
import com.redrose.videodemo.utils.LogUtils;
import com.redrose.videodemo.utils.ToastUtil;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Desc:
 *
 * @author: RedRose
 * Date: 2019/4/17
 * Email: yinsxi@163.com
 */

public class PreViewGlsurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer,
        Camera.PictureCallback, Camera.PreviewCallback,
        SurfaceTexture.OnFrameAvailableListener {
    public static final String TAG = "PreViewGlsurfaceView";
    private Context mContext;
    /**
     * 以OpenGL ES纹理的形式从图像流中捕获帧,我把叫做纹理层
     */
    SurfaceTexture mSurface;
    /**
     * 使用的纹理id
     */
    int mTextureID = -1;
    DirectDrawer mDirectDrawer;
    private Camera mCamera;
    private BitmapCallBack mBitmapCallback;
//    int width = 1280;
//    int height = 720;
    int width = 1920;
    int height = 1080;
    int framerate = 30; //一秒30帧
    H264Encoder encoder; //自定义的编码操作类


    public PreViewGlsurfaceView(Context context) {
        this(context, null);
    }

    public PreViewGlsurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        setEGLContextClientVersion(2);
        setRenderer(this);
        //根据纹理层的监听,有数据就绘制 渲染模式
        //RENDERMODE_WHEN_DIRTY表示被动渲染,只有在调用requestRender或者onResume等方法时才会进行渲染。
        // RENDERMODE_CONTINUOUSLY表示持续渲染
        setRenderMode(RENDERMODE_WHEN_DIRTY);
        mCamera = CameraUtil2.getInstance().openCamera();

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        LogUtils.d(TAG, "--onSurfaceCreated--");
        //得到view表面的纹理id
        mTextureID = createTextureID();
        //使用这个纹理id得到纹理层SurfaceTexture
        mSurface = new SurfaceTexture(mTextureID);
        //监听纹理层
        mSurface.setOnFrameAvailableListener(this);
        mDirectDrawer = new DirectDrawer(mTextureID);
        //打开相机,并未预览
        CameraUtil2.getInstance().doStartPreview(mSurface);
        mCamera.setPreviewCallback(this);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        LogUtils.d(TAG, "--onSurfaceChanged--");
        GLES20.glViewport(0, 0, width, height);
        // 渲染窗口大小发生改变或者屏幕方法发生变化时候回调
        //如果还未预览,就开始预览
        if (CameraUtil2.getInstance().getIsRelease()) {
            mCamera = CameraUtil2.getInstance().openCamera();
            CameraUtil2.getInstance().doStartPreview(mSurface);
        }
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //系统在每次重画GLSurfaceView时调用这个方法
        //执行渲染工作
        LogUtils.d(TAG, "--onDrawFrame--");
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        //从图像流中将纹理图像更新为最近的帧
        mSurface.updateTexImage();
        mDirectDrawer.draw();
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        LogUtils.d(TAG, "--onFrameAvailable--");
        //回调接口,用于通知新的流帧可用
        //纹理层有新数据,就通知view绘制
        this.requestRender();
    }

    private int createTextureID() {
        int[] texture = new int[1];
        GLES20.glGenTextures(1, texture, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        return texture[0];
    }

    @Override
    public void onPause() {
        super.onPause();
        LogUtils.d(TAG, "--onPause--");
        //停止预览
        CameraUtil2.getInstance().releaseCamera();
    }

    public void setBitmapCallback(BitmapCallBack bitmapCallback) {
        this.mBitmapCallback = bitmapCallback;
    }

    public void takePhoto() {
        if (mCamera != null) {
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    //设置聚焦成功后再拍照,其实可以不用。看需求吧,可以直接调用takePicture()
                    //有些手机会聚焦失败,也就是success是false
                    if (success) {
                        camera.cancelAutoFocus();
                        mCamera.takePicture(null, null, PreViewGlsurfaceView.this);
                    }
                }
            });
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        /**
         * 触摸聚焦
         */
        mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                if (success) {
                    ToastUtil.show(getContext(), "聚焦成功");
                    camera.cancelAutoFocus();
                }
            }
        });
        return super.onTouchEvent(event);
    }

    private boolean isStartVideo = false;



    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        //这里就是预览返回的 NV21数据
        LogUtils.d(TAG, "--onPreviewFrame--");
        if (encoder != null && isStartVideo){
            encoder.putDate(data); //将一帧的数据传过去处理
        }

    }

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        if (mBitmapCallback != null) {
            mBitmapCallback.backByte(data);
        }
        camera.startPreview();
    }
    public void startVideo() {
        isStartVideo = true;
        ToastUtil.show(getContext(), "开始录像");
        encoder = new H264Encoder(width,height,framerate);
        encoder.startEncoder(); //开始编码

    }

    public void stopVideo() {
        isStartVideo = false;
        ToastUtil.show(getContext(), "停止录像");
        if (encoder != null){
            encoder.stopEncoder();
        }

    }
}
CameraTextureActivity
package com.redrose.videodemo.camera;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;

import com.redrose.videodemo.R;
import com.redrose.videodemo.base.BaseActivity;
import com.redrose.videodemo.bean.Photo;
import com.redrose.videodemo.bean.Time;
import com.redrose.videodemo.utils.BitmapCallBack;
import com.redrose.videodemo.utils.GlideUtils;
import com.redrose.videodemo.utils.IntentUtils;
import com.redrose.videodemo.utils.LogUtils;
import com.redrose.videodemo.utils.MySensorHelper;
import com.redrose.videodemo.utils.SPUtils;
import com.redrose.videodemo.utils.ToastUtil;
import com.redrose.videodemo.utils.ToolUtils;
import com.redrose.videodemo.view.PreviewTextureView;
import com.zhy.adapter.recyclerview.CommonAdapter;
import com.zhy.adapter.recyclerview.base.ViewHolder;

import org.litepal.crud.DataSupport;
import org.xutils.view.annotation.ContentView;
import org.xutils.view.annotation.Event;
import org.xutils.view.annotation.ViewInject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * Desc:
 * Author: RedRose
 * Date: 2019/3/20
 * Email: yinsxi@163.com
 */
@ContentView(R.layout.activity_camera_texture)
public class CameraTextureActivity extends BaseActivity implements BitmapCallBack {
    @ViewInject(R.id.pre_text)
    private TextView mPreSize;
    @ViewInject(R.id.save_text)
    private TextView mSaveSize;
    @ViewInject(R.id.flashlight)
    private ImageView mFlashView;
    @ViewInject(R.id.photo_list)
    private ImageView mPreImageView;
    @ViewInject(R.id.take_photo)
    private View mTakePhoto;
    @ViewInject(R.id.camera_preview)
    private PreviewTextureView mPreview;
    private boolean showLocation = false;
    private PopupWindow mPopWindow;
    private List<Photo> mPhotoList;
    private Time mTime;
    private boolean isTakePhoto = true;
    /**
     * 重力传感器监听
     * 横着 竖着状态
     */
    private MySensorHelper mMySensor;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置全屏
//        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        initData();
    }

    private void initData() {
        mMySensor = new MySensorHelper(this);
        mMySensor.enable();
        int mode = SPUtils.getInt("Flash_mode");
        initFlashMode(mode);
        mPreview.setBitmapCallback(this);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar calendar = Calendar.getInstance();
        String format = sdf.format(calendar.getTime());
        mTime = new Time();
        mTime.setClassId(Long.parseLong(format));
        mTime.setDay(calendar.get(Calendar.DAY_OF_MONTH));
        mTime.setYear(calendar.get(Calendar.YEAR));
        mTime.setMonth(calendar.get(Calendar.MONTH) + 1);
        mPhotoList = new ArrayList<>();
    }

    private void showPopWindow(View view) {
        mPopWindow = new PopupWindow(view, ToolUtils.dp2px(mContext.getApplicationContext(),
                200), ToolUtils.dp2px(mContext.getApplicationContext(), 200));
        mPopWindow.setFocusable(true);
//        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3F51B5")));
        mPopWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        mPopWindow.setOutsideTouchable(true);
        mPopWindow.update();
        if (showLocation) {
            mPopWindow.showAsDropDown(mSaveSize);
        } else {
            mPopWindow.showAsDropDown(mPreSize);
        }
    }

    /**
     * 显示支持保存的分辨率
     */
    private void showSupportSize(List<Camera.Size> list) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        View view = inflater.inflate(R.layout.pop_size_layout, null);
        RecyclerView recycleView = view.findViewById(R.id.recycle_size);
        initRecycle(recycleView);
        SizeAdapter mAdapter = new SizeAdapter(mContext, R.layout.size_item, list);
        recycleView.setAdapter(mAdapter);
        showPopWindow(view);
    }

    @Event(value = {R.id.pre_text, R.id.save_text, R.id.flashlight, R.id.take_photo, R.id.photo_list,
            R.id.videoSwtich,
            R.id.all_list})
    private void onclickView(View view) {
        int id = view.getId();
        switch (id) {
            /*暂时注释 动态设置预览分辨率*/
            case R.id.pre_text:
                /*预览分辨率,建议设置成屏幕分辨率,经测试,支持的最高就是屏幕分辨率 */
                showLocation = false;
                showSupportSize(CameraUtil.getInstance().getPreviewSizeList());
                break;
            //设置保存图片的分辨率
            case R.id.save_text:
                showLocation = true;
                showSupportSize(CameraUtil2.getInstance().getPictureSizeList());
                break;
            //切换闪光灯模式
            case R.id.flashlight:
                int mode = SPUtils.getInt("Flash_mode");
                if (mode == CameraUtil.FLASH_AUTO) {
                    mode = CameraUtil.FLASH_OFF;
                } else if (mode == CameraUtil.FLASH_OFF) {
                    mode = CameraUtil.FLASH_ON;
                } else if (mode == CameraUtil.FLASH_ON) {
                    mode = CameraUtil.FLASH_AUTO;
                }
                SPUtils.put("Flash_mode", mode);
                initFlashMode(mode);
                break;
            //拍照
            case R.id.take_photo:
                mPreview.takePhoto();


                break;
            //打开当前拍摄的照片图库
            case R.id.photo_list:
                int size = mPhotoList.size();
                if (size == 0) {
                    ToastUtil.show(mContext, "还未拍摄照片,可以点击右侧查看所有照片");
                    return;
                }
                Bundle bundle = new Bundle();
                bundle.putSerializable("photoList", (ArrayList<Photo>) mPhotoList);
                IntentUtils.goToPreviewActivity(mContext, bundle);
                break;
            //打开图库
            case R.id.all_list:
                IntentUtils.goToImageActivity(mContext, null);
                break;
            case R.id.videoSwtich:
                if (isTakePhoto) {
                    isTakePhoto = false;
                } else {
                    isTakePhoto = true;
                }
                break;
            default:
                break;
        }
    }

    @Event(type = View.OnLongClickListener.class, value = R.id.take_photo)
    private boolean onLongClickView(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.take_photo:

                mPreview.startVideo();

                break;
            default:
                break;
        }
        return false;
    }

    private void initRecycle(RecyclerView recyclerView) {
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        //设置布局管理器
        recyclerView.setLayoutManager(layoutManager);
        //设置为垂直布局,这也是默认的
        layoutManager.setOrientation(OrientationHelper.VERTICAL);
        //设置Adapter
//        recyclerView.setAdapter(recycleAdapter);
        //设置分隔线
//        recyclerView.addItemDecoration(new DividerGridItemDecoration(this));
        //设置增加或删除条目的动画
        recyclerView.setItemAnimator(new DefaultItemAnimator());
    }

    @Override
    public void backByte(byte[] data) {
        GlideUtils.loadByte(mPreImageView, data);
        ToolUtils.saveImageFile(data, mPhotoList, mIsRotate);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar calendar = Calendar.getInstance();
        String format = sdf.format(calendar.getTime());
        List<Time> times = DataSupport.where("classId = ?", format).find(Time.class);
        mTime.setPhotoList(mPhotoList);
        if (times.size() == 0) {
            mTime.save();
        }
    }

    /**
     * Desc: 相机页面,拍摄照片保存分辨率 显示适配器
     * author: RedRose
     * Date: 2019/4/1
     * Email: yinsxi@163.com
     */
    private class SizeAdapter extends CommonAdapter<Camera.Size> {

        private SizeAdapter(Context context, int layoutId, List<Camera.Size> datas) {
            super(context, layoutId, datas);
        }

        @Override
        protected void convert(ViewHolder holder, Camera.Size size, int position) {
            LogUtils.d("----redrose 装载数据测试 ----");
            TextView textView = holder.getView(R.id.size_text);
            String sizeString = String.format("%d * % d", size.width, size.height);
            textView.setText(sizeString);
            if (showLocation) {
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        CameraUtil2.getInstance().setSaveSize(size);
                        if (mPopWindow != null) {
                            mPopWindow.dismiss();
                            mPopWindow = null;
                        }
                        ToastUtil.show(mContext, size.width + "x" + size.height);
                    }
                });
            }
        }
    }

    private void initFlashMode(int mode) {
        if (mode == CameraUtil.FLASH_AUTO) {
            mFlashView.setImageResource(R.mipmap.flash_auto);
        } else if (mode == CameraUtil.FLASH_OFF) {
            mFlashView.setImageResource(R.mipmap.flash_off);
        } else if (mode == CameraUtil.FLASH_ON) {
            mFlashView.setImageResource(R.mipmap.flash_on);
        }
        CameraUtil2.getInstance().setFlashMode(mode);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMySensor.disable();
        LogUtils.e(TAG, " ---onDestroy---");
    }

    /**
     * 是否旋转图片
     */
    @Override
    public void setIsRotate(boolean isRotate) {
        if (mIsRotate == isRotate) {
            return;
        }
        this.mIsRotate = isRotate;
        if (mIsRotate) {
            CameraUtil2.getInstance().setRotateDegree(0);
        } else {
            CameraUtil2.getInstance().setRotateDegree(90);
        }
    }

    @Override
    public boolean getIsRotate() {
        return mIsRotate;
    }
}
PreviewTextureView
package com.redrose.videodemo.view;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.TextureView;

import com.redrose.videodemo.camera.CameraTextureActivity;
import com.redrose.videodemo.camera.CameraUtil;
import com.redrose.videodemo.camera.CameraUtil2;
import com.redrose.videodemo.camera.Preview;
import com.redrose.videodemo.utils.BitmapCallBack;
import com.redrose.videodemo.utils.LogUtils;
import com.redrose.videodemo.utils.ToastUtil;

import java.io.IOException;

/**
 * Desc: TextureView 预览Camera
 *
 * @author: RedRose
 * Date: 2019/4/16 0016
 * Email: yinsxi@163.com
 */
public class PreviewTextureView extends TextureView implements TextureView.SurfaceTextureListener,
        Camera.PictureCallback ,Camera.PreviewCallback{
    private static final String TAG = PreviewTextureView.class.getClass().getSimpleName();
    private BitmapCallBack mBitmapBack;
    private SurfaceTexture mSurfaceTexture;
    private Camera mCamera;

    public PreviewTextureView(Context context) {
        this(context, null);
    }

    public PreviewTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setSurfaceTextureListener(this);
        mCamera = CameraUtil2.getInstance().openCamera();
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        LogUtils.d(TAG, "--onSurfaceTextureAvailable--");
        mSurfaceTexture = surface;
        if (CameraUtil2.getInstance().getIsRelease()) {
            mCamera = CameraUtil2.getInstance().openCamera();
        }
        CameraUtil2.getInstance().doStartPreview(surface);
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        LogUtils.d(TAG, "--onSurfaceTextureSizeChanged--");
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        LogUtils.d(TAG, "--onSurfaceTextureDestroyed--");
        CameraUtil2.getInstance().releaseCamera();
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        LogUtils.d(TAG, "--onSurfaceTextureUpdated--");

    }

    public void takePhoto() {
        if (mCamera != null) {
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    //设置聚焦成功后再拍照,其实可以不用。看需求吧,可以直接调用takePicture()
                    //有些手机会聚焦失败,也就是success是false
                    if (success) {
                        camera.cancelAutoFocus();
                        mCamera.takePicture(null, null, PreviewTextureView.this);
                    }
                }
            });
        }
    }

    public void setBitmapCallback(BitmapCallBack bitmapCallback) {
        this.mBitmapBack = bitmapCallback;
    }

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        if (mBitmapBack != null) {
            mBitmapBack.backByte(data);
        }
        camera.startPreview();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        /**
         * 触摸聚焦
         */
        mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                if (success) {
                    ToastUtil.show(getContext(), "聚焦成功");
                    camera.cancelAutoFocus();
                }
            }
        });
        return super.onTouchEvent(event);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        //这里就是预览返回的 NV21数据
    }
    private boolean isStartVideo = false;
    public void startVideo() {
        isStartVideo = false;
        ToastUtil.show(getContext(),"长按监听,开始录像");
    }
}

另外在

PreViewGlsurfaceView中有bug,在拍照界面进入查看图片返回的时候,会闪到主界面或者崩溃,这个问题原因是没有释放Camera资源的问题,这个地方比较简单,我先把代码提交,下次我改了再提交到码云,仅供参考,完整代码可以去我的码云上去看,欢迎指正。
要在 Android 应用中使用 GLSurfaceViewCamera2 API 实现预览,可以参考以下步骤: 1. 在你的 Android 项目中添加 GLSurfaceView 控件,并在应用程序中初始化它。 2. 通过 Camera2 API 打开相机,并将相机输出连接到 GLSurfaceView 控件上。 3. 在 GLSurfaceView 控件中实现自定义的 Renderer,并在 Renderer 中实现图像渲染和处理逻辑。 4. 将渲染结果显示在 GLSurfaceView 控件上。 以下是一个简单的代码示例,演示如何使用 GLSurfaceViewCamera2 API 实现预览: ```java public class PreviewActivity extends AppCompatActivity { private CameraManager cameraManager; private CameraDevice cameraDevice; private CameraCaptureSession captureSession; private CaptureRequest.Builder previewRequestBuilder; private CaptureRequest previewRequest; private Size previewSize; private SurfaceTexture surfaceTexture; private GLSurfaceView glSurfaceView; private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { cameraDevice = camera; createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice camera) { cameraDevice.close(); cameraDevice = null; } @Override public void onError(@NonNull CameraDevice camera, int error) { cameraDevice.close(); cameraDevice = null; } }; private void openCamera() { cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { String cameraId = cameraManager.getCameraIdList()[0]; CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); previewSize = map.getOutputSizes(SurfaceTexture.class)[0]; surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface = new Surface(surfaceTexture); previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.addTarget(previewSurface); cameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { captureSession = session; updatePreview(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } private void createCameraPreviewSession() { try { surfaceTexture = glSurfaceView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); openCamera(); } catch (CameraAccessException e) { e.printStackTrace(); } } private void updatePreview() { previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); previewRequest = previewRequestBuilder.build(); try { captureSession.setRepeatingRequest(previewRequest, null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glSurfaceView = new GLSurfaceView(this); glSurfaceView.setEGLContextClientVersion(2); glSurfaceView.setRenderer(new PreviewRenderer()); setContentView(glSurfaceView); } @Override protected void onResume() { super.onResume(); if (glSurfaceView != null) { glSurfaceView.onResume(); } if (cameraDevice == null) { try { cameraManager.openCamera(cameraManager.getCameraIdList()[0], stateCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } } @Override protected void onPause() { if (glSurfaceView != null) { glSurfaceView.onPause(); } if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } super.onPause(); } private class PreviewRenderer implements GLSurfaceView.Renderer { private final float[] vertexData = { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f }; private final float[] textureData = { 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f }; private int textureId; private int program; private int aPositionLocation; private int aTextureLocation; private int uTextureMatrixLocation; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { textureId = createTexture(); program = createProgram(); aPositionLocation = glGetAttribLocation(program, "aPosition"); aTextureLocation = glGetAttribLocation(program, "aTextureCoord"); uTextureMatrixLocation = glGetUniformLocation(program, "uTextureMatrix"); glClearColor(0f, 0f, 0f, 0f); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { glViewport(0, 0, width, height); Matrix.scaleM(textureMatrix, 0, 1f, -1f, 1f); Matrix.translateM(textureMatrix, 0, 0f, -1f, 0f); Matrix.rotateM(textureMatrix, 0, 90f, 0f, 0f, 1f); } @Override public void onDrawFrame(GL10 gl) { glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); glEnableVertexAttribArray(aPositionLocation); glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, vertexBuffer); glEnableVertexAttribArray(aTextureLocation); glVertexAttribPointer(aTextureLocation, 2, GL_FLOAT, false, 0, textureBuffer); glUniformMatrix4fv(uTextureMatrixLocation, 1, false, textureMatrix, 0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(aPositionLocation); glDisableVertexAttribArray(aTextureLocation); } private int createTexture() { int[] textures = new int[1]; glGenTextures(1, textures, 0); int textureId = textures[0]; glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId); glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); return textureId; } private int createProgram() { String vertexShaderCode = "attribute vec4 aPosition;\n" + "attribute vec4 aTextureCoord;\n" + "uniform mat4 uTextureMatrix;\n" + "varying vec2 vTextureCoord;\n" + "void main() {\n" + " vTextureCoord = (uTextureMatrix * aTextureCoord).xy;\n" + " gl_Position = aPosition;\n" + "}"; String fragmentShaderCode = "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + "uniform samplerExternalOES uTexture;\n" + "varying vec2 vTextureCoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(uTexture, vTextureCoord);\n" + "}"; int vertexShader = loadShader(GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentShaderCode); int program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); glUseProgram(program); return program; } private int loadShader(int type, String code) { int shader = glCreateShader(type); glShaderSource(shader, code); glCompileShader(shader); return shader; } } } ``` 需要注意的是,这只是一个简单的示例,并且可能需要进行进一步的优化和改进,以满足你的实际需求和性能要求。同时,为了确保应用程序的稳定性,还需要进行充分的测试和错误处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值