Android多媒体编程

作者: wxyass 分类: Android 发布时间: 2014-10-13 16:21

导语

多媒体编程介绍

一、多媒体概念

  • 文字、图片、音频、视频

1.1、计算机图片大小的计算

图片大小 = 图片的总像素 * 每个像素占用的大小

  • 单色图:每个像素占用1/8个字节
  • 16色图:每个像素占用1/2个字节
  • 256色图:每个像素占用1个字节
  • 24位图:每个像素占用3个字节
    ##1.2、计算机图片详解
  • 图片的大小 = 图片的像素 * 每个像素占用的大小
  • 单色:只能表示两种颜色
    • 使用0和1表示黑与白
    • 使用一个长度为1的二进制数字即可表示一个像素的颜色信息
    • 每个像素占用1/8个字节
  • 16色:只能表示16种颜色
    • 使用16个数字对应这16种颜色
    • 0~15,0000 ~ 1111,也就是说需要长度为4的二进制数字
    • 每个像素占用1/2个字节
  • 256色:只能表示256种颜色
    • 使用256个数字对应256种颜色
    • 0~255,0000 0000 ~ 1111 1111,也就是说需要长度为8的二进制数字
    • 每个像素占用1个字节
  • 24位色:需要长度为24的二进制数字
    • 每个像素占用3个字节
    • R:0~255,占用1个字节
    • G:同上
    • B:同上

#二、加载大图片到内存
###2.1、出现内存溢出问题
* Android系统以ARGB表示每个像素,所以每个像素占用4个字节,很容易内存溢出
* android系统里面每个应用程序默认的vm虚拟机最大的heap空间 16M,如果应用程序占用的内存空间超过了16M 则OOM(out of memory)内存溢出。

###2.2 如何解决加载大图片内存溢出问题?
* 手机屏幕的分辨率要比图片的分辨率小很多。 只需要根据手机的分辨率把图片给压缩采样,加载到手机上就行了。步骤:
1. 计算手机屏幕的宽高
2. 计算图片的宽高
3. 计算图片的缩放比例
4. opts.inSampleSize = scale; 设置图片的缩放比例
5. 加载图片到内存
* 具体解析如下:
* Android内存中使用ARGB保存像素信息,每个像素占用4个字节
* SD卡中图片像素大小:2400 * 3200 = 7680000
* 手机的屏幕像素大小: 320 * 480 = 153600
* 把SD卡图片先缩小,再加载,先计算图片的缩小比例
* 2400 / 320 = 7.5
* 3200 / 480 = 6.67

##2.1对图片进行缩放
* 获取屏幕宽高

    //Display类提供关于屏幕尺寸和分辨率的信息。
    Display dp = getWindowManager().getDefaultDisplay();
    //获取屏幕的宽度尺寸,以像素为单位。(不建议使用,请使用getSize(Point) 代替)
    int screenWidth = dp.getWidth();
    //获取屏幕的高度尺寸,以像素为单位。(不建议使用,请使用getSize(Point) 代替)
    int screenHeight = dp.getHeight();

* 获取图片宽高 ????????????

    //创建请求权对象
    Options opts = new Options();
    //请求图片属性但不申请内存,//不解析像素信息,只解析图片宽高,那么就不会申请内存去保存像素信息
    opts.inJustDecodeBounds = true;
    //api会解析图片的所有像素信息,把像素信息保存在内存中
    BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
    //获取图片宽高
    int imageWidth = opts.outWidth;
    int imageHeight = opts.outHeight;

* 图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例

    //计算缩小比例
    int scale = 1;
    int scaleX = imageWidth / screenWidth;
    int scaleY = imageHeight / screenHeight;
    //取较大值
    if(scaleX >= scaleY && scaleX > 1){
        scale = scaleX;
    }
    else if(scaleY > scaleX && scaleY > 1){
        scale = scaleY;
    }

* 按缩放比例加载图片

    //设置缩放比例
    opts.inSampleSize = scale;
    //为图片申请内存
    opts.inJustDecodeBounds = false;
    //按照缩小后的比例来解析像素
    Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);

    //查找并初始化图片控件
    ImageView iv = (ImageView) findViewById(R.id.iv);
    //为图片重新添加内容
    iv.setImageBitmap(bm);

–加载图片到内存,并显示在手机桌面上的示例–
public class MainActivity extends Activity {

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

    public void click(View v){
        Options opts = new Options();
        //不解析像素信息,只解析图片宽高,那么就不会申请内存去保存像素信息
        opts.inJustDecodeBounds = true;
        //api会解析图片的所有像素信息,把像素信息保存在内存中
        BitmapFactory.decodeFile("sdcard/dog.jpg", opts);

        //获取图片宽高
        int imageWidth = opts.outWidth;
        int imageHeight = opts.outHeight;

        //获取屏幕宽高
        Display dp = getWindowManager().getDefaultDisplay();
        @SuppressWarnings("deprecation")
        int screenWidth = dp.getWidth();
        int screenHeight = dp.getHeight();

        //计算缩小比例
        int scale = 1;
        int scaleWidth = imageWidth / screenWidth;
        int scaleHeight = imageHeight / screenHeight;

        //取较大值
        if(scaleWidth >= scaleHeight && scaleWidth > 1){
            scale = scaleWidth;
        }
        else if(scaleWidth < scaleHeight && scaleHeight > 1){
            scale = scaleHeight;
        }

        //设置缩放比例
        opts.inSampleSize = scale;
        //为图片申请内存
        opts.inJustDecodeBounds = false;
        //按照缩小后的比例来解析像素
        Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);

        ImageView iv = (ImageView) findViewById(R.id.iv);
        iv.setImageBitmap(bm);
    }
}

#三、在内存中创建图片的副本

直接加载的bitmap对象是只读的,无法修改,要修改图片只能在内存中创建出一个一模一样的bitmap副本,然后修改副本

    //加载SD卡中的原图到内存,注意选个小的,不要内存溢出这个对象是只读的
    Bitmap srcBm = BitmapFactory.decodeFile("sdcard/photo3.jpg");
    //通过图片控件将原图显示到桌面,这个对象是只读的。
    iv_src.setImageBitmap(srcBm);

    //创建与原图大小一致的空白bitmap
    Bitmap copyBm = Bitmap.createBitmap(srcBm.getWidth(), srcBm.getHeight(), srcBm.getConfig());
    //定义画笔
    Paint paint = new Paint();
    //把纸铺在画版上
    Canvas canvas = new Canvas(copyBm);
    //把srcBm的内容绘制在copyBm上
    canvas.drawBitmap(srcBm, new Matrix(), paint);

    //将副本图片通过图片控件显示到桌面,这个对象是可读写的。
    iv_copy.setImageBitmap(copyBm);

–创建图片副本示例–

public class MainActivity extends Activity {

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

        //这个对象是只读的
        Bitmap bmSrc = BitmapFactory.decodeResource(getResources(), R.drawable.photo3);

        //1.创建与原图大小一致的bitmap对象,相当于创建了与原图大小一致的白纸
        Bitmap bmCopy = Bitmap.createBitmap(bmSrc.getWidth(), bmSrc.getHeight(), bmSrc.getConfig());
        //2.创建画笔对象
        Paint paint = new Paint();
        //3.创建画板对象,把白纸铺在画板上
        Canvas canvas = new Canvas(bmCopy);
        //4.开始作画,把原图内容画到白纸上
        canvas.drawBitmap(bmSrc, new Matrix(), paint);

        ImageView iv_src = (ImageView) findViewById(R.id.iv_src);
        iv_src.setImageBitmap(bmSrc);
        ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);
        iv_copy.setImageBitmap(bmCopy);
    }
}

##3.1对图片进行特效处理
* 首先定义一个矩阵对象

    Matrix mt = new Matrix();

* 缩放效果

    //x轴缩放1倍,y轴缩放0.5倍
    mt.setScale(1, 0.5f);

    mt.setScale(2.0f, 2.0f);
  • 旋转效果
    //以副本图片为轴点,顺时旋转30度
    
    mt.setRotate(30,copyBm.getWidth()/2, copyBm.getHeight()/2);
    
  • 平移
    //x轴坐标+10,y轴坐标+20
    mt.setTranslate(10, 20);
    
  • 镜面
    //把X坐标都变成负数
    mt.setScale(-1, 1);
    //matrix.setScale(-1.0f, 1);
    //图片整体向右移
    mt.postTranslate(copyBm.getWidth(), 0);
    
  • 倒影
    //把Y坐标都变成负数
    mt.setScale(1, -1);
    //图片整体向下移
    mt.postTranslate(0, copyBm.getHeight());
    

–图片特效示例–

public class MainActivity extends Activity {

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

        //这个对象是只读的
        Bitmap bmSrc = BitmapFactory.decodeResource(getResources(), R.drawable.photo3);

        //1.创建与原图大小一致的bitmap对象,相当于创建了与原图大小一致的白纸
        Bitmap bmCopy = Bitmap.createBitmap(bmSrc.getWidth(), bmSrc.getHeight(), bmSrc.getConfig());
        //2.创建画笔对象
        Paint paint = new Paint();
        //3.创建画板对象,把白纸铺在画板上
        Canvas canvas = new Canvas(bmCopy);
        //4.开始作画,把原图内容画到白纸上

        //4.1对图片进行特效处理
        //首先定义一个矩阵对象
        Matrix mt = new Matrix();
        //平移
        //mt.setTranslate(10, 20);

        //缩放
        //mt.setScale(0.5f,2);
        //mt.setScale(0.5f,2, bmCopy.getWidth()/2, bmCopy.getHeight()/2);

        //旋转
        //mt.setRotate(45,bmCopy.getWidth()/2, bmCopy.getHeight()/2);


        //镜面
        //mt.setScale(-1, 1); 
        //mt.postTranslate(bmCopy.getWidth(), 0);
        //倒影
        mt.setScale(1, -1);
        mt.postTranslate(0, bmCopy.getHeight());

        //4.2 把原图内容画到白纸上
        canvas.drawBitmap(bmSrc, mt, paint);

        ImageView iv_src = (ImageView) findViewById(R.id.iv_src);
        iv_src.setImageBitmap(bmSrc);
        ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);
        iv_copy.setImageBitmap(bmCopy);
    }
}

#四、画画板,在图片副本上进行读写操作
###4.1、实现功能:记录用户触摸事件的XY坐标,绘制直线
* 1、在手机屏幕上加载图片副本

    //1将原画加载到内存
    Bitmap bmSrc = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
    //2在内存中创建图片的副本
    bmCopy = Bitmap.createBitmap(bmSrc.getWidth(), bmSrc.getHeight(), bmSrc.getConfig());
    paint = new Paint();
    canvas = new Canvas(bmCopy);
    //作画
    canvas.drawBitmap(bmSrc, new Matrix(), paint);
    //3将副本图片显示在桌面上
    iv = (ImageView) findViewById(R.id.iv);
    iv.setImageBitmap(bmCopy);
  • 2、给ImageView图片副本设置触摸侦听,得到用户的触摸事件,并获知用户触摸ImageView的坐标,实现在副本图片上划线
    iv.setOnTouchListener(new OnTouchListener() {
    
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            switch (event.getAction()) {//判断手指触屏事件
            //触摸屏幕
            case MotionEvent.ACTION_DOWN:
                //得到触摸屏幕时手指的坐标
                startX = (int) event.getX();
                startY = (int) event.getY();
                break;
            //在屏幕上滑动
            case MotionEvent.ACTION_MOVE:
                //用户滑动手指,坐标不断的改变,获取最新坐标
                int newX = (int) event.getX();
                int newY = (int) event.getY();
                //用上次onTouch方法得到的坐标和本次得到的坐标绘制直线
                canvas.drawLine(startX, startY, newX, newY, paint);
                //刷新桌面图片
                iv.setImageBitmap(copyBm);
                //改变每次画线的开始坐标
                startX = newX;
                startY = newY;
                break;
            }
            //该触摸事件是否由此侦听处理
            return true;
        }
    });
    
  • 刷子效果,加粗画笔
    public void flush(View v){
        paint.setStrokeWidth(8);
    }
    
  • 调色板,改变画笔颜色
    public void green(View v){
        paint.setColor(Color.GREEN);
    }
    
  • 保存图片至SD卡
    public void save(View v){
        File file = new File("sdcard/dazuo2.png");
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(file);
            //保存
            bmCopy.compress(CompressFormat.PNG, 100, fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    
        //发送sd卡就绪广播,触发sd卡的遍历
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
        intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
        sendBroadcast(intent);
    }
    
  • 系统每次收到SD卡就绪广播时,都会去遍历sd卡的所有文件和文件夹,把遍历到的所有多媒体文件都在MediaStore数据库保存一个索引,这个索引包含多媒体文件的文件名、路径、大小
  • 图库每次打开时,并不会去遍历sd卡获取图片,而是通过内容提供者从MediaStore数据库中获取图片的信息,然后读取该图片

  • 系统开机或者点击加载sd卡按钮时,系统会发送sd卡就绪广播,我们也可以手动发送就绪广播

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
    intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
    sendBroadcast(intent);
    

#五、撕衣服
* 原理:把穿内衣和穿外衣的照片重叠显示,内衣照在下面,用户滑动屏幕时,触摸的是外衣照,把手指经过的像素都置为透明,内衣照就显示出来了
* 步骤:
* 1.相对布局 外衣图片与内衣图片重叠,内衣上外衣下
* 2.在手机屏幕上加载外衣原画,并显示外衣图片副本
* 3.设置触摸侦听,移动的位置颜色变透明,

    //初始化外衣图片控件
    iv = (ImageView) findViewById(R.id.iv);
    //为图片控件设置触摸侦听
    iv.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            switch (event.getAction()) {//判断手指触屏事件
            //手指在屏幕上滑动时
            case MotionEvent.ACTION_MOVE:
                //得到相对图片的坐标
                int x = (int) event.getX();
                int y = (int) event.getY();
                //设置坐标方圆5像素都变为透明
                for (int i = -5; i < 5; i++) {
                    for (int j = -5; j < 5; j++) {
                        if(Math.sqrt(i*i + j*j) <= 5){
                            //把指定坐标的像素置为指定颜色
                            if(x + i < bmCopy.getWidth() && x + i >= 0 && y + j < bmCopy.getHeight() && y + j >= 0){
                                bmCopy.setPixel(x + i, y + j, Color.TRANSPARENT);
                                iv.setImageBitmap(bmCopy);
                            }
                        }
                    }
                }
                break;
            }
            return true;
        }
    });
  • 每次只设置一个像素点太慢,以触摸的像素为圆心,半径为5画圆,圆内的像素全部置为透明
    for (int i = -5; i < 6; i++) {
        for (int j = -5; j < 6; j++) {
            if(Math.sqrt(i * i + j * j) <= 5)
                copyBm.setPixel(newX + i, newY + j, Color.TRANSPARENT);
        }
    }
    

#六、音乐播放器
##6.1播放服务
* 1.创建音乐服务(播放音频的代码应该运行在服务中,定义一个播放服务MusicService)
* 1.1 MusicService服务里定义play、stop、pause、continuePlay等方法

    //定义媒体播放器(MediaPlayer)控件的视图,此视图包含了一些典型的按钮,像"播放(Play)/暂停(Pause)", "倒带(Rewind)", "快进(Fast Forward)"与进度滑动器(progress slider)。

    //媒体播放器在服务启动时,就要被创建,要提全局
    MediaPlayer player;
    @Override
    public void onCreate() {
        super.onCreate();
        player = new MediaPlayer();

    }

    //服务内部定义开始音乐功能,
    private void play() {
        //重置,进入空闲状态
        player.reset();
        try {
            //设置音频文件来源,进入初始化状态
            player.setDataSource("sdcard/bzj.mp3");
            //player.setDataSource("http://192.168.15.77:8080/bzj.mp3");
            //进入准备状态
            //player.prepare();
            //异步准备
            player.prepareAsync();
            //异步准备需要设置准备侦听,准备好就开始播放
            player.setOnPreparedListener(new OnPreparedListener() {

                @Override
                public void onPrepared(MediaPlayer mp) {
                    //开始播放
                    player.start();
                    addTimer();
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    //服务内部定义暂停音乐功能
    private void pause() {
        player.pause();
    }

    private void stop() {
        player.stop();
    }

    //服务内部定义继续音乐功能
    private void continuePlay() {
        player.start();
    }
  • 1.2 把play、stop、pause、continuePlay等方法这几个方法抽取成一个接口MusicInterface
    public interface MusicControllerInterface {
    
        void play();
        void pause();
        void continuePlay();
        void seekTo(int progress);
    }
    
  • 1.3 服务中定义一个中间人类,继承Binder,实现MusicInterface
    class MusicController extends Binder implements MusicControllerInterface{
    
        @Override
        public void play() {
            MusicService.this.play();
    
        }
    
        @Override
        public void pause() {
            MusicService.this.pause();
        }
    
        @Override
        public void continuePlay() {
            MusicService.this.continuePlay();
    
        }
    
        @Override
        public void seekTo(int progress) {
            MusicService.this.seekTo(progress);
        }
    }
    
  • 1.4 为音乐服务进行清单配置
     <service android:name="com.itheima.musicplayer.MusicService"></service>
    
    1. Activity界面中混合启动startService服务(启动MusicService服务,再bind服务)
      //UI界面已启动,执行此方法
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      
          //启动音乐服务意图(很显然是显示启动)
          Intent intent = new Intent(this, MusicService.class);
          //创建通讯频道对象,将用与获取中间人
          MusicConnection conn = new MusicConnection();
      
          sb = (SeekBar) findViewById(R.id.sb);
          sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
      
              //停止划动
              @Override
              public void onStopTrackingTouch(SeekBar seekBar) {
                  //获取用户停止划动后的进度
                  int progress = seekBar.getProgress();
                  //改变音乐播放进度
                  mci.seekTo(progress);
              }
              //开始划动
              @Override
              public void onStartTrackingTouch(SeekBar seekBar) {
              }
      
              //正在划动
              @Override
              public void onProgressChanged(SeekBar seekBar, int progress,
                      boolean fromUser) {
              }
          });
      
          //------------------------------------------
          //混合启动
          //start是为了让进程变成服务进程
          startService(intent);
          //bind是为了拿到中间人对象
          bindService(intent, conn, BIND_AUTO_CREATE);
      }
      

##6.2根据播放进度设置进度条
* 获取当前的播放时间和当前音频的最长时间

    int currentPosition = player.getCurrentPosition();
    int duration = player.getDuration();

* 播放进度需要不停的获取,不停的刷新进度条,使用计时器每500毫秒获取一次播放进度
* 发消息至Handler,把播放进度放进Message对象中,在Handler中更新SeekBar的进度

    Timer timer = new Timer();
    timer.schedule(new TimerTask() {

        @Override
        public void run() {

            //获取歌曲当前播放的时间点
            int currentPosition = player.getCurrentPosition();
            //获取歌曲总时长
            int duration = player.getDuration();
            //获取消息对象
            //Message msg = Message.obtain();
            Message msg =MainActivity.handler.obtainMessage();
            //把播放进度存入Message中
            Bundle data = new Bundle();
            data.putInt("currentPosition", currentPosition);
            data.putInt("duration", duration);
            msg.setData(data);
            // 将消息发送到消息队列
            MainActivity.handler.sendMessage(msg);
        }
    }, 5, 500);
  • 在Activity中定义Handler
    static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            //取出消息携带的数据
            Bundle data = msg.getData();
            int currentPosition = data.getInt("currentPosition");
            int duration = data.getInt("duration");
    
            //设置播放进度
            sb.setMax(duration);
            sb.setProgress(currentPosition);
        };
    };
    

##6.3拖动进度条改变播放进度
* 1.在UI界面获取进度条控件,并对其设置拖动侦听,调用服务里的改变音乐播放进度方法(把进度参数传过去)

     //初始化SeekBar音乐滚动条
     sb = (SeekBar) findViewById(R.id.sb);
     //给sb设置一个拖动侦听
     sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
        //停止拖动时调用
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            //获取用户停止划动后的进度
            int progress = seekBar.getProgress();
            //改变音乐播放进度,调用音乐服务抽取出来的接口方法,去改变音乐播放进度
            mci.seekTo(progress);
        }
        //开始拖动时调用            
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }
        //拖动的时候不断调用            
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                boolean fromUser) {

        }
    });    
  • 2.定义方法:在音乐服务中定义根据进度参数改变音乐播放进度的方法
    public void seekTo(int progress){
        player.seekTo(progress);
    }
    

#七、视频播放器
##7.1SurfaceView
* 对画面的实时更新要求较高
* 双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面
* 播放视频也是用MediaPlayer,不过跟音频不同,要设置显示在哪个SurfaceView

    SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
    SurfaceHolder sh = sv.getHolder();

    MediaPlayer player = new MediaPlayer();
    player.reset();
    try {
        player.setDataSource("sdcard/2.3gp");
        player.setDisplay(sh);
        player.prepare();
    } catch (Exception e) {
        e.printStackTrace();
    }
    player.start();

* SurfaceView是重量级组件,可见时才会创建
* 给SurfaceHolder设置CallBack,类似于侦听,可以知道SurfaceView的状态

    //看做侦听,可以知道SurfaceView的状态,并执行相对应方法
    sh.addCallback(new Callback() {
        //SurfaceView一旦不可见,就会被销毁,SurfaceView销毁时调用
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub

        }
        //SurfaceView一旦可见,就会被创建SurfaceView创建时调用
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub

        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            // TODO Auto-generated method stub

        }
    });

* SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放

–视频播放器示例

public class MainActivity extends Activity {

    private MediaPlayer player;
    int progress = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获取显示视频的控件
        SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
        //获取控制器
        final SurfaceHolder holder = sv.getHolder();

//        Thread t = new Thread(){
//            @Override
//            public void run() {
//                try {
//                    sleep(200);
//                } catch (InterruptedException e) {
//                    // TODO Auto-generated catch block
//                    e.printStackTrace();
//                }
//                
//                runOnUiThread(new Runnable() {
//                    
//                    @Override
//                    public void run() {
//                        MediaPlayer player = new MediaPlayer();
//                        player.reset();
//                        try {
//                            player.setDataSource("sdcard/2.3gp");
//                            //指定视频画面播放在哪个组件
//                            player.setDisplay(holder);
//                            player.prepare();
//                            player.start();
//                        } catch (Exception e) {
//                            // TODO Auto-generated catch block
//                            e.printStackTrace();
//                        } 
//                        
//                    }
//                });
//            }
//        };
//        t.start();

        //给SurfaceHolder设置CallBack,类似于侦听,可以知道SurfaceView的状态
        holder.addCallback(new Callback() {

            //SurfaceView摧毁时调用
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if(player != null){
                    //每次SurfaceView摧毁时记录播放进度
                    progress = player.getCurrentPosition();
                    player.stop();
                    player.release();
                    player = null;
                }
            }

            //SurfaceView创建时调用
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if(player == null){
                    player = new MediaPlayer();
                    player.reset();
                    try {
                        //指定要播放的资源
                        player.setDataSource("sdcard/2.3gp");
                        //指定视频画面播放在哪个组件
                        player.setDisplay(holder);
                        //进入准备状态
                        player.prepare();
                        //跳到先前的进度
                        player.seekTo(progress);
                        //开始播放
                        player.start();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } 
                }
            }

            //SurfaceView结构改变时调用
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                    int height) {
                // TODO Auto-generated method stub
            }
        });
    }
}

##7.2 VideoView 使用方法简单播放视频

public class MainActivity extends Activity {

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

        VideoView vv = (VideoView) findViewById(R.id.vv);
        vv.setVideoPath("sdcard/2.3gp");
        vv.start();
    }
}

#八、摄像头
* 8.1启动系统提供的拍照程序

    public void click1(View v){
        //隐式启动系统提供的拍照Activity
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //设置照片的保存路径
        File file = new File(Environment.getExternalStorageDirectory(), "haha.jpg"); 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
        //arg1:请求码,用于匹配
        startActivityForResult(intent, 0);
    }

* 8.2启动系统提供的摄像程序

    public void click2(View v){
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        //设置摄像的保存路径
        File file = new File(Environment.getExternalStorageDirectory(), "haha.3gp"); 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
        //设置保存视频文件的质量
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        startActivityForResult(intent, 0);
    }

* 8.3 启动系统提供的拍照Activity和启动系统提供的摄像Activity示例:

    public class MainActivity extends Activity {

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

        public void click1(View v){
            //启动系统提供的拍照Activity
            Intent intent = new Intent();    
            intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("sdcard/haha.jpg")));
            startActivityForResult(intent, 10);//开启拍照并设置请求码
        }
        public void click2(View v){
            //启动系统提供的摄像Activity
            Intent intent = new Intent();    
            intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("sdcard/haha2.3gp")));
            intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
            startActivityForResult(intent, 20);//开启摄像并设置请求码
        }

        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            //判断请求码,进行操作
            if(requestCode == 10){
                Toast.makeText(this, "拍照完成", 0).show();
            }
            else if(requestCode == 20){
                Toast.makeText(this, "摄像完成", 0).show();
            }
        }
    }

#9 Camera照相机

  • 1.设置UI界面的Activity

public class MainActivity extends Activity {
private static final String TAG = “Camera”;
private Camera mCamera;
private CameraPreview mPreview;

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

                if(checkCameraHardware(this)){
                    // 创建摄像头实例
                    mCamera = getCameraInstance();

                    // 创建预览界面
                    mPreview = new CameraPreview(this, mCamera);


                    FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
                    //把SurfaceView设置为帧布局的子节点
                    preview.addView(mPreview);

                 // 给拍照按钮设置点击侦听
                    Button captureButton = (Button) findViewById(R.id.button_capture);
                    captureButton.setOnClickListener(
                        new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //自动对焦
                                mCamera.autoFocus(new AutoFocusCallback() {

                                    //自动对焦完成时,此方法调用
                                    @Override
                                    public void onAutoFocus(boolean success, Camera camera) {
                                        // 拍照
                                        mCamera.takePicture(null, null, mPicture);

                                    }
                                });

                            }
                        }
                    );
                }
                else{
                    Toast.makeText(this, "连摄像头都没有,穷逼", 0).show();
                }
            }


        /** 检测是否有摄像头 */
        private boolean checkCameraHardware(Context context) {
            //检测是否具有指定的系统功能
            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
                // 有摄像头
                return true;
            } else {
                // 没有摄像头
                return false;
            }
        }

        /** 这是一个获取摄像头实例的安全的途径 */
        public static Camera getCameraInstance(){
            Camera c = null;
            try {
                //返回后置摄像头的实例,如果没有后置摄像头,返回空
                c = Camera.open(); // attempt to get a Camera instance
            }
            catch (Exception e){
                // Camera is not available (in use or does not exist)
            }
            return c; // returns null if camera is unavailable
        }

        private PictureCallback mPicture = new PictureCallback() {

            //摄像头照相方法执行时,会调用此方法
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {

                //指定相片保存的路径和文件名
                File pictureFile = new File("sdcard/ohohoho2.jpg");

                try {
                    FileOutputStream fos = new FileOutputStream(pictureFile);
                    fos.write(data);
                    fos.close();
                } catch (FileNotFoundException e) {
                    Log.d(TAG, "File not found: " + e.getMessage());
                } catch (IOException e) {
                    Log.d(TAG, "Error accessing file: " + e.getMessage());
                }
            }
        };
    }
  • 2.设置摄像头预览类CameraPreview

    /** 摄像头预览类 */
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = “Camera”;
    private SurfaceHolder mHolder;
    private Camera mCamera;

        @SuppressWarnings("deprecation")
        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
    
            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            // 获取SurfaceView的holder
            mHolder = getHolder();
            // 设置侦听,侦听SurfaceView的摧毁和创建
            mHolder.addCallback(this);
            // 3.0之后,会自动设置
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    
        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                //指定摄像头的预览界面显示在哪个SurfaceView中
                mCamera.setPreviewDisplay(holder);
                //开始预览
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }
    
        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }
    
        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
    
            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }
    
            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }
    
            // set preview size and make any resize, rotate or
            // reformatting changes here
    
            // start preview with new settings
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
    
            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }
    
  • 3.设置清单文件



  • 4.布局文件



      <Button
        android:id="@+id/button_capture"
        android:text="拍照"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        />
    </LinearLayout>
    

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注