多媒体的概念:
文字、图片、音频、视频
图片
常见的图片格式:
png:无损保存图片,高质量的图片,.BMP格式保存的图像质量不变,文件也比较大,因为要保存每个像素的信息.
JPEG――是一种较常用的有损压缩图片,文件压缩变小,不保存每个像素的信息。
PNG--压缩不失真,它综合了JPG和GIF格式的优点
GIF―是一种图像交换格式,可提供压缩功能,但只支持256色,很少用于照片级图像处理工作.在PhotoShop中把对颜色数要求不高的图片变为索引色,再以GIF格式保存,使文件缩小后用更快的速度在网上传输.
单色位图:只能表示2种颜色 使用两个数字:0和1 使用一个长度为1的二进制数字就可以表示了 每个像素占用1/8个字节。
16色位图:能表示16种颜色 需要16个数字:0-15,0000 - 1111 使用一个长度为4的二进制数组就可以表示了 每个像素占用1/2个字节
256色位图:能表示256种颜色 需要256个数字:0 - 255,0000 0000 - 1111 1111 使用一个长度为8的二进制数字 每个像素占用1个字节
24位位图: 每个像素占用24位,也就是3个字节,所在叫24位位图 1600多万种
R:0-255,需要一个长度为8的二进制数字,占用1个字节
G:0-255,需要一个长度为8的二进制数字,占用1个字节
B:0-255,需要一个长度为8的二进制数字,占用1个字节
图片在计算机中的大小
图片的总大小 = 图片的总像素 * 每个像素占用的大小
加载大图片
计算机把图片所有像素信息全部解析出来,保存至内存
Android保存图片像素信息,是用ARGB保存 ,所以每个像素占用4个字节,很容易内存溢出。
在安卓程序中,加载大图片可以采用缩放的方式。
原理:第一步 :在解析图片的时候,我们使用位图工厂的api BitmapFactory.decodeFile(pathName, opts) ,在这个api中可以传入一个Options的对象,而我们解析图片时所需要的参数都封装进这个api中,把Options对象的inJustDecodeBounds的属性设为 true,通过文档可知,这样通过位图工场解析图图片,不会反悔位图,也不会为图像的像素申请内存。 =
第二步:通过Options对象拿到图片的宽高opt.outWidth;opt.outHeight; getWindowManager().getDefaultDisplay();拿到手机屏幕返回一个Display对象,通过display对象获得屏幕的宽高getWidth() getHeight();
第三步:拿图片的像素除以屏幕的像素,取大的值作为缩放比例,然后给options对象的inSampleSize属性设置缩放比例 再将opt.inJustDecodeBounds = false;这时候通过位图工厂解析图片,传入的options对象所需要解析的参数,就是我们需要的图片。
代码体现:
public void click(View v) { //位图工场解析图片的参数封装在Options对象中 Options opts = new Options(); //不为像素申请内存,不返回位图对象 opts.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(),R.drawable.person,opts); //拿到图片宽高 int imageHeight = opts.outHeight; int imageWidth = opts.outWidth; //拿到手机屏幕 Display dis = getWindowManager().getDefaultDisplay(); //获得手机屏幕的宽高 int screenHeight = dis.getHeight(); int screenWidth = dis.getWidth(); //计算缩放比例 int scale = 1; int scaleHeight = imageHeight / screenHeight; int scaleWidth = imageWidth / screenWidth; if(scaleHeight >= scaleWidth && scaleHeight >= 1) { scale = scaleHeight; }else if(scaleWidth>=scaleHeight && scaleWidth>=1) { scale = scaleWidth; } //设置缩放属性 opts.inSampleSize = scale; opts.inJustDecodeBounds = false; Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.person,opts); ImageView iv = (ImageView) findViewById(R.id.iv); iv.setImageBitmap(bm); }
在内存中创建图片的副本
由于我们直接加载的bitmap对象是只读的,我们无法对其做一些修改,要修改图片只能在内存中创建出一个一模一样的bitmap副本,然后修改副本
思路:第一步通过位图工厂拿到位图对象
第二步.在内存中创建一个与原图一模一样大小的bitmap对象,创建与原图大小一致的白纸Bitmap.createBitmap()
第三 步 创建画笔对象Paint
第四步 .创建画板对象,把白纸铺在画板上Canvas
第四步 开始作画,把原图的内容绘制在白纸上canvas.drawBitmap
代码 : 在两个imageView中通过创建图片副本,显示两个一样的图片
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Bitmap bt = BitmapFactory.decodeResource(getResources(), R.drawable.person); //创建一个与原图一样大小的白纸 Bitmap btCopy = Bitmap.createBitmap(bt.getWidth(), bt.getHeight(), bt.getConfig()); Paint pt = new Paint(); //创建画板对象,把白纸铺在画板上Canvas Canvas cv = new Canvas(btCopy); //4.开始作画,把原图的内容绘制在白纸上 cv.drawBitmap(bt, new Matrix(), pt); ImageView iv_bitmap = (ImageView) findViewById(R.id.iv1); ImageView iv_copyMap = (ImageView) findViewById(R.id.iv2); iv_bitmap.setImageBitmap(bt); iv_copyMap.setImageBitmap(btCopy); } }
通过图片副本完成画画板的案例
需求: 在activity上有一块画板 画板下方有四个Button 分别可以设置画笔的颜色为红色 绿色 设置画笔变粗 保存图片(保存的图片可以在安卓自带的图片应用中查看)
思路: 第一步 加载画画板的背景图
第二步 通过图片副本 创建一个与背景图一样的画板 显示在imageView上
第三步 由于我们在画板上作画。通过我们在屏幕上触摸移动,把我们触摸到的像素位置设置颜色。 所以我们需要给图片设置触摸侦听(setOnTouchListener)
在我们触摸屏幕时会触发onTouch方法,手指在屏幕上触摸有三种情况 分别为MotionEvent.ACTION_DOWN 手指摸到屏幕 MotionEvent.ACTION_MOVE手指在滑动 MotionEvent.ACTION_UP:
第四步 我们在手指摸到屏幕的时候记录位置,在手指移动的时候记录每次移动的位置,把每次移动的位置和手指触摸屏幕的位置之间划线drawLine 注意是每次绘制完毕之后,本次绘制的结束坐标变成下一次绘制的初始坐标,画完后将绘制后的图片设置到imageView
(监听事件内部类的返回值问题
true:告诉系统,这个触摸事件由我来处理
false:告诉系统,这个触摸事件我不处理,这时系统会把触摸事件传递给imageview的父节点
)
第五步 改变颜色 就是给画笔设置颜色
第六步 画笔加粗可以调用画笔setStrokeWidth(); 传入int 数值给画笔设置粗细
第七步 保存图片 调用bitmap的方法compress 可以保存图片
第八步 我们需要在保存图片的时候,就可以在系统的图片应用中看到图片 ,系统每次收到SD卡就绪广播时,都会去遍历sd卡的所有文件和文件夹,把遍历到的所有多媒体文件都在MediaStore数据库保存一个索引,这个索引包含多媒体文件的文件名、路径、大小。 图库每次打开时,并不会去遍历sd卡获取图片,而是通过内容提供者从MediaStore数据库中获取图片的信息,然后读取该图片,系统开机或者点击加载sd卡按钮时,系统会发送sd卡就绪广播,我们也可以手动发送就绪广播
代码:
public class MainActivity extends Activity { private Canvas cancas; private Paint paint; private ImageView iv_drawBoard; private Bitmap bt_copy; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
//使用图片副本拿到可修改的副本图片 Bitmap bt = BitmapFactory.decodeResource(getResources(), R.drawable.bg); bt_copy = Bitmap.createBitmap(bt.getWidth(), bt.getHeight(), bt.getConfig()); paint = new Paint(); cancas = new Canvas(bt_copy); cancas.drawBitmap(bt, new Matrix(), paint); iv_drawBoard = (ImageView) findViewById(R.id.iv_drawBoard); iv_drawBoard.setImageBitmap(bt_copy);
//设置触摸监听 iv_drawBoard.setOnTouchListener(new OnTouchListener() { private int startX; private int startY; //完成滑动画笔功能 @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN : startX = (int) event.getX(); startY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE : int X = (int) event.getX(); int Y = (int) event.getY(); cancas.drawLine(startX, startY, X, Y, paint); startX = X; startY = Y; iv_drawBoard.setImageBitmap(bt_copy); break; case MotionEvent.ACTION_UP : break; } return true; } }); } //改变画笔颜色 public void click_red(View v) { paint.setColor(Color.RED); } public void click_green(View v) { paint.setColor(Color.GREEN); }
//改变画笔粗细 public void click_rough(View v) { paint.setStrokeWidth(7); }
//将图片保存 public void click_save(View v) { File file = new File(Environment.getExternalStorageDirectory(), "dazuo.PNG"); FileOutputStream stream = null; try { stream = new FileOutputStream(file); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }
//位图对象的compress方法可以保存图片 bt_copy.compress(CompressFormat.PNG, 100, stream);
//如果需要在图片应用看到图片,需要在保存图片之后发送广播,告知系统去遍历sd卡,把图片添加到MediaStore数据库 Intent intent = new Intent(); intent.setAction(Intent.ACTION_MEDIA_MOUNTED); intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); sendBroadcast(intent); } }
图片的简单特效功能
我们在画板上通过绘制图片功能
canvas.drawBitmap(Bm, new Matrix(), paint);
通过设置矩阵模型来改变,通过矩阵模型可以做很多特效,有些简单特效通过安卓api可以做到
Matrix mt = new Matrix(); 缩放效果 //x轴缩放1倍,y轴缩放0.5倍 mt.setScale(1, 0.5f); 旋转效果 //以copyBm.getWidth() / 2, copyBm.getHeight() / 2点为轴点,顺时旋转30度 mt.setRotate(30, copyBm.getWidth() / 2, copyBm.getHeight() / 2);* 平移 //x轴坐标+10,y轴坐标+20 mt.setTranslate(10, 20); 镜面 //把X坐标都变成负数 mt.setScale(-1, 1); //图片整体向右移 mt.postTranslate(copyBm.getWidth(), 0); 倒影 //把Y坐标都变成负数 mt.setScale(1, -1); //图片整体向下移 mt.postTranslate(0, copyBm.getHeight());
刮刮卡案例
原理两张图片跌在一起 设置触摸监听,当触摸移动时,将移动的位置的图片通过方法setPixel(x, y, Color.TRANSPARENT); 颜色设置为透明 Color.TRANSPARENT是为透明
public class MainActivity extends Activity { private Bitmap bt_copy; private ImageView awaiyi; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //创建图片副本 Bitmap bt = BitmapFactory.decodeResource(getResources(), R.drawable.awaiyi); bt_copy = Bitmap.createBitmap(bt.getWidth(), bt.getHeight(), bt.getConfig()); Paint paint = new Paint(); Canvas canvas = new Canvas(bt_copy); canvas.drawBitmap(bt, new Matrix(), paint); awaiyi = (ImageView) findViewById(R.id.awaiyi); awaiyi.setImageBitmap(bt_copy); // 给imageView设置触摸监听 awaiyi.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_MOVE: int x = (int) event.getX(); int y = (int) event.getY(); for(int i=-20; i<20; i++) { for(int j=-20; j<20; j++) {
//设置为圆形 if(Math.sqrt(i*i+j*j)<=20) {
//防止越界 if(y+j<bt_copy.getHeight() && x+i<bt_copy.getWidth() && x+i>=0 && y+j>=0) {
//将图片的像素点设置颜色为透明 bt_copy.setPixel(x+i, y+j, Color.TRANSPARENT); awaiyi.setImageBitmap(bt_copy); } } } } break; } return true; } }); } }
多媒体之音乐
安卓提供的api完成音乐播放,支持的格式少。实际开发中可以使用开源的框架,这里我们通过代码可以理解一下音乐播放的原理
需求:在界面上可以开始播放 暂停播放,继续播放,退出 ,拥有进度条可以查看音乐播放进度,同时可以拖动进度条来快进和快退音乐。
代码 第一步 由于音乐播放,在我们手机推出界面的时候依然可以听见,所以要使用服务,而音乐服务中定义的方法,我们需要在前台界面调用,我们要使用绑定服务,在这里为了达到这两种效果我们使用混合启动服务
服务的代码
public class MusicService extends Service { MediaPlayer player; private Timer timer; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return new MusicController(); } //首先定义接口 接口中有5个方法 分别是play() pause() continuePlay() seekTo() class MusicController extends Binder implements MusicInterface{ @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); } } //在服务一创建的时候就创建MediaPlayer对象 @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); player = new MediaPlayer(); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); //停止播放 player.stop(); //释放占用的资源,此时player对象已经废掉了 player.release(); player = null; if(timer != null){ timer.cancel(); timer = null; } } //播放音乐 public void play(){ //第一步重置 player.reset(); try { //第二部加载多媒体文件 可以通过setDataSource加载本地的资源 或者网络上的资源 player.setDataSource("sdcard/zxmzf.mp3"); // player.setDataSource("http://192.168.13.119:8080/bzj.mp3");
//准备 // player.prepare();
//当访问网络资源的时候采用异步准备 player.prepareAsync(); // player.start();
//给mediaPlayer对象设置准备监听,当准备完成后才调用start方法 player.setOnPreparedListener(new OnPreparedListener() { //准备完毕时,此方法调用 @Override public void onPrepared(MediaPlayer mp) { player.start();
//不断的发送当前进度 addTimer(); } }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //继续播放 public void continuePlay(){ player.start(); } //暂停播放 public void pause(){ player.pause(); } //通过传入的进度,去改变当前播放进度 public void seekTo(int progress){ player.seekTo(progress); }
//定义计时器方法,可以不断的将当前进度发送给主线程的handle对象 public void addTimer(){ if(timer == null){
//创建计时器对象,他可以开启一个子线程 timer = new Timer();
timer.schedule(new TimerTask() { @Override public void run() { //获取歌曲总时长 int duration = player.getDuration(); //获取歌曲当前播放进度 int currentPosition= player.getCurrentPosition(); //在mainActivity中将handler对象设置成静态的,这边就可以通过类名.对象名调用
//使用消息队列机制,将消息发送给handle对象 Message msg = MainActivity.handler.obtainMessage(); //把进度封装至消息对象中 Bundle bundle = new Bundle(); bundle.putInt("duration", duration); bundle.putInt("currentPosition", currentPosition); msg.setData(bundle); MainActivity.handler.sendMessage(msg); } //开始计时任务后的5毫秒,第一次执行run方法,以后每500毫秒执行一次 }, 5, 500); } } }
//清单文件注册服务
<service android:name="com.itheima.musicplayer.MusicService"></service>
public class MainActivity extends Activity { //定义成静态,在服务中需要访问,不断接受当前的进度 static Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { Bundle bundle = msg.getData(); int duration = bundle.getInt("duration"); int currentPostition = bundle.getInt("currentPosition"); //刷新进度条的进度 sb.setMax(duration); sb.setProgress(currentPostition); } }; MusicInterface mi; private MyserviceConn conn; private Intent intent; private static SeekBar sb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
//seekBar 可以拖动的进度条 sb = (SeekBar) findViewById(R.id.sb); sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override
//当拖动停止,客户想要重新启用推进SeekBar时调用这个方法 public void onStopTrackingTouch(SeekBar seekBar) { //根据拖动的进度改变音乐播放进度 int progress = seekBar.getProgress(); //改变播放进度 mi.seekTo(progress); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } }); intent = new Intent(this, MusicService.class); startService(intent); conn = new MyserviceConn(); bindService(intent, conn, BIND_AUTO_CREATE); } class MyserviceConn implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { mi = (MusicInterface) service; } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } } public void play(View v){ mi.play(); } public void continuePlay(View v){ mi.continuePlay(); } public void pause(View v){ mi.pause(); } public void exit(View v){ unbindService(conn); stopService(intent); } }
多媒体编程之播放视频
SurfaceView 是一个重量级组件,只要不可见,就不会创建,可见时,才会创建,只要不可见,就会销毁,使用双缓冲技术。但是只可以支持的3gp视频格式,正式开发通常可以使用开源的框架
mediaPlayer和surfaceView组合播放视频文件
public class MainActivity extends Activity { private MediaPlayer player; static int currentPosition; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //拿到SurfaceView对象 SurfaceView sv = (SurfaceView) findViewById(R.id.sv); //拿到surfaceview的控制器 final SurfaceHolder sh = sv.getHolder(); sh.addCallback(new Callback() { //surfaceView销毁时调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { //每次surfaceview销毁时,同时停止播放视频 if(player != null){ currentPosition = player.getCurrentPosition(); player.stop(); player.release(); player = null; } } //surfaceView创建时调用 @Override public void surfaceCreated(SurfaceHolder holder) { //每次surfaceView创建时才去播放视频 if(player == null){ player = new MediaPlayer(); player.reset(); try { player.setDataSource("sdcard/2.3gp");
//设置视频显示在哪个组件上 player.setDisplay(sh); player.prepare();
//如果是异步准备,要设置准备监听,准备完成后才开启,但是这里是同步准备不需要 player.start(); player.seekTo(currentPosition); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //surfaceView结构改变时调用 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } }); } }
第二种方式 使用VideoView 它其实也是mediaPlayer和surfaceView的组合,只是谷歌做了封装 ,使用起来更加方便
在开发我们想要播放更多的视频格式的文件或者播放更多的音乐格式文件,我们可以使用很多的成熟的开源框架,
FFMPEG
开源免费的音视频编解码器
Vitamio
封装了FFMPEG的视频播放框架
对外提供的api全部都是javaApi
Vitamio是一款功能强大的影视播放开源框架,使用方法api帮助文档和下载可以再官网可见 https://www.vitamio.org/
多媒体编程之拍照和摄影
启动系统提供的拍照activity和摄像activity
从谷歌api中可以得知调用摄像头意图程序遵循这些步骤:
意图
这 请求的图像或视频,使用这些意图类型:
mediastore.action_image_capture
-从现有的摄像头应用程序请求图像 意图行动型。mediastore.action_video_capture
-从现有的摄像头应用程序请求一个视频 意图行动型。startactivityforresult()
执行摄像意图的方法。在你开始的意图,相机应用的用户 界面出现在屏幕,用户可以拍摄照片或视频。onactivityresult()
在你的应用程序从照相机意图接收回调和数据的方法 。当用户 完成拍照或录像(或取消),系统调用这个方法。 intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void image(View v){ //启动系统提供的拍照activity Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
//这里传入的第二个参数指定存入的路径和文件名 fileUri 可以通过Uri.fromFile 返回一个fileUri对象 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "haha.jpg"))); startActivityForResult(intent, 10); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if(requestCode == 10){ Toast.makeText(this, "拍照成功", 0).show(); } else if(requestCode == 20){ Toast.makeText(this, "摄像成功", 0).show(); } } public void video(View v){ //启动系统提供的摄像activity Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "haha.3gp"))); intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); startActivityForResult(intent, 20); } }
安卓开发者,产品爱好者
个人微博