多媒體編程
多媒體編程主要包括:文本、圖片、音頻、視頻等
圖片
圖片在計算機中的大小
圖片的總大小 = 圖片的總像素 * 每個像素佔用的大小
單色位圖:只能表示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位位圖 * R:0-255,需要一個長度爲8的二進制數字,佔用1個字節 * G:0-255,需要一個長度爲8的二進制數字,佔用1個字節 * B:0-255,需要一個長度爲8的二進制數字,佔用1個字節
加載大圖片到內存
Android系統以ARGB表示每個像素,所以每個像素佔用4個字節,很容易內存溢出
對圖片進行縮放
獲取屏幕寬高
Display dp = getWindowManager().getDefaultDisplay(); int screenWidth = dp.getWidth(); int screenHeight = dp.getHeight(); 獲取圖片寬高 Options opts = new Options(); //請求圖片屬性但不申請內存 opts.inJustDecodeBounds = true; 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); iv.setImageBitmap(bm);
在內存中創建圖片的副本
直接加載的bitmap對象是隻讀的,無法修改,要修改圖片只能在內存中創建出一個一模一樣的bitmap副本,然後修改副本
//加載原圖 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);
對圖片進行特效處理
首先定義一個矩陣對象
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());
畫畫板
記錄用戶觸摸事件的XY座標,繪製直線
給ImageView設置觸摸偵聽,得到用戶的觸摸事件,並獲知用戶觸摸ImageView的座標iv.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { 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; } });
刷子效果,加粗畫筆
paint.setStrokeWidth(8);
調色板,改變畫筆顏色
paint.setColor(Color.GREEN);
保存圖片至SD卡
FileOutputStream fos = null; try { fos = new FileOutputStream(new File("sdcard/dazuo.png")); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } //保存圖片 copyBm.compress(CompressFormat.PNG, 100, fos);
系統每次收到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);
音樂播放器
播放服務
播放音頻的代碼應該運行在服務中,定義一個播放服務MusicService
服務裏定義play、stop、pause、continuePlay等方法MediaPlayer player; private void play() { //重置 player.reset(); try { //加載資源 player.setDataSource("sdcard/bzj.mp3"); player.prepare(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } player.start(); } private void pause() { player.pause(); } private void stop() { player.stop(); } private void continuePlay() { player.start();//注意和最開始start的區別,start()前什麼也沒有,表示繼續播放 } public void seekTo(int progress){ player.seekTo(progress); } //注意當unBindService和stopService後服務雖然被銷燬了,但MediaPlayer組件沒有被銷燬,需要在服務的onDestroy()方法中手動銷燬 @Override public void onDestroy() { super.onDestroy(); //停止播放,此時如果點開始播放,會重新加載資源播放,和pause不同 player.stop(); //釋放佔用的資源,此時player對象已經廢掉了 player.release(); player=null;//從java語法角度回收player應用 }
把這幾個方法抽取成一個接口MusicInterface
定義一箇中間人類,繼承Binder,實現MusicInterface
先start啓動MusicService,再bindIntent intent = new Intent(this, MusicService.class); startService(intent); bindService(intent, conn, BIND_AUTO_CREATE);
根據播放進度設置進度條
獲取當前的播放時間和當前音頻的最長時間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中 Bundle data = new Bundle(); data.putInt("currentPosition", currentPosition); data.putInt("duration", duration); msg.setData(data); MainActivity.handler.sendMessage(msg); } }, 5, 500);//5 延遲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); }; };
拖動進度條改變播放進度
//給sb設置一個拖動偵聽 sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { //停止拖動時調用 @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub int progress = seekBar.getProgress(); mi.seekTo(progress); } //開始拖動時調用 @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } //拖動的時候不斷調用 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub } });
視頻播放器
SurfaceView
對畫面的實時更新要求較高
雙緩衝技術:內存中有兩個畫布,A畫布顯示至屏幕,B畫布在內存中繪製下一幀畫面,繪製完畢後B顯示至屏幕,A在內存中繼續繪製下一幀畫面
播放視頻也是用MediaPlayer,不過跟音頻不同,要設置顯示在哪個SurfaceViewSurfaceView sv = (SurfaceView) findViewById(R.id.sv); //拿到SurfaceView的控制器 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();
以上從設置播放開始的代碼如果放在onCreate()方法中,此時可能SurfaceView組件還不可見,也就是沒有創建出來,代碼是有問題的。
(SurfaceView是重量級組件,可見時纔會創建)給SurfaceHolder設置CallBack,類似於偵聽,可以知道SurfaceView的狀態
sh.addCallback(new Callback() { //SurfaceView銷燬時調用,點返回鍵或home鍵,SurfaceView就不可見,銷燬並調用該方法 @Override public void surfaceDestroyed(SurfaceHolder holder) { // SurfaceView雖然銷燬了,但MediaPlayer仍在播放,可以聽到視頻音樂聲音,所以也要同時銷燬player。 if(player!=null){ //每次退出時記錄一下播放的位置。可以定義一個全局變量記錄 currentPosition=player.getCurrentPosition(); player.stop(); player.release(); player=null; } } //SurfaceView創建時調用 @Override public void surfaceCreated(SurfaceHolder holder) { // 應當把代碼放置到這裏,每次SurfaceView創建時纔去播放視頻 if(player==null){ //設置播放 MediaPlayer player = new MediaPlayer(); player.reset(); try { player.setDataSource("sdcard/2.3gp"); player.setDisplay(sh); player.prepare(); } catch (Exception e) { e.printStackTrace(); } player.start(); player.seekTo(currentPosition);//從記錄的位置開始播放 } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } });
SurfaceView一旦不可見,就會被銷燬,一旦可見,就會被創建,銷燬時停止播放,再次創建時再開始播放
上面SurfaceView+MediaPlayer播放視頻的過程很麻煩,Android中將其封裝爲VideoView組件
VideoView vv=(VideoView)findViewById(R.id.vv); vv.setVideoPath("sdcard/2.3gp"); vv.start();//如果是網絡視頻,需要設置監聽,在監聽中start()
實際開發中原生的API幾乎不用,支持的類型太少了。
FFMPEG
開源免費的音視頻編解碼器
實際使用的話需要掌握C語言Vitamio
封裝了FFMPEG的視頻播放框架
對外提供的api全部都是javaApiVitamio使用方法
1.將vitamio_lib導入到項目中,如果導入後console中顯示紅色的報錯信息,可以點擊vitamio_lib項目Android Tools>Fix Project properties 2.將vitamio_lib設置成關聯類庫(勾選Is Library),給我們要使用的項目添加該類庫 3.將佈局文件中的VideoView換成vitamio_lib中的VideoView(記得全路徑名),同時設置代碼,如下 //VideoView導包的時候要導入vitamio_lib的包 VideoView vv=(VideoView)findViewById(R.id.vv); vv.setVideoPath("sdcard/2.3gp"); vv.start(); 4.因爲Vitamio封裝的FFMPEG是需要硬件編解碼的,所以使用前應當檢測手機硬件是否支持 //檢查Vitamio引擎的安裝,如果沒安裝,它會幫我安裝,如果安裝不了就會return if(!LibsChecker.checkVitamioLibs(this)){ return; } VideoView vv=(VideoView)findViewById(R.id.vv); vv.setVideoPath("sdcard/4.rmvb"); vv.start(); vv.setMediaController(new MediaController(this));//可以在屏幕上顯示一個默認的視頻播放控制器 //同時需要在清單文件中配置,檢查Vitamio引擎的安裝實際上是在InitActivity中完成的 <activity android:name="io.vov.vitamio.activity.InitActivity"></activity> 5.Vitamio不僅支持http協議,還支持很多其他的協議。 收音機 播放協議:MMS MMS(Microsoft Media Server protocol)是一種串流媒體傳輸協議,Android並不支持這種流媒體協議
其他的視頻播放框架
百度媒體雲,導入它的jar包使用
百度媒體雲服務(簡稱“媒體雲”)基於百度在視頻處理上的長期技術積累,爲廣大開發者提供的媒體相關的整體解決方案。