Android多媒体开发系列文章

  1. Android多媒体开发:录音机
  2. Android多媒体开发:照相机
  3. TextureView+MediaPlayer实现在线短视频播放
  4. Android多媒体开发:第三方框架

一、什么是多媒体

  • 多媒体(duō méi tǐ) 的英文单词是Multimedia,它由media和multi两部分组成。一般理解为多种媒体的综合

  • 多媒体是计算机和视频技术的结合,实际上它是两个媒体;声音和图像,或者用现在的术语:音响和电视。

  • 多媒体(Multimedia),在计算机系统中,组合两种或两种以上媒体的一种人机交互式信息交流和传播媒体

  • 使用的媒体包括文字、图片、照片、声音 (包含音乐、语音旁白、特殊音效)、动画和影片,以及程式所提供的互动功能

  • 多媒体是超媒体(Hypermedia)系统中的一个子集,而超媒体系统是使用超链接 (Hyperlink)构成的全球信息系统

  • 全球信息系统是因特网上使用 TCP/IP 协议和 UDP/IP 协议

二、音乐播放器

Android 官方提供了MediaPlayer 核心类,用于播放音乐,其状态流程如下图所示。MediaPlayer 必须严格按照状态图操作,否则就会出现错误,这些错误都是底层抛出,严格按照状态图操作的话一般就不会出问题。

MediaPlayer,原生的API,可以播放音视频,但是支持的格式比较少,实际开发中用的比较少,但是还是很有必要学习,熟悉API,因为Vitamio框架的API大部分跟原生的API是一样的

1、MediaPlayer使用流程图

2、MediaPlayer核心方法

方法 说明
create() 播放本地res/raw/目录下的资源
reset() 重置为初始状态
setAudioStreamType() 设置音乐格式,例如:AudioManager.STREAM_MUSIC
setDataSource() 设置音频源,本地网络资源均可
prepare() 播放前的准备工作
prepareAsync() 异步进行准备工作,播放网络音频的时候使用
start() 开始或恢复播放
pause() 暂停播放
stop() 停止播放
release() 释放资源
getDuration() 获取音乐最大长度(毫秒单位)
getCurrentPosition() 获取当前的播放进度
seekTo() 拖拽进度
setDisplay() 设置输出画面
setOnPreparedListener() 设置准备监听

 
为了演示MediaPlayer 的使用,我们需要提前准备一个mp3 文件放到sdcard 中

需求:制作一个播放器,能够播放/暂停/停止音乐文件,并且添加一个SeekBar(可以拖拽的ProgressBar),当音乐播放时SeekBar 也会不断的跟新当前的进度,当用户拖动SeekBar 时可以更改播放的进度

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Button
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="play"android:text="播放"/><Button
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="pause"android:text="暂停"/><Button
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="stop"android:text="停止"/></LinearLayout><SeekBar
        android:id="@+id/sb"android:layout_width="match_parent"android:layout_height="wrap_content"/>
</LinearLayout>

代码实现

public class MainActivity extends Activity implements OnSeekBarChangeListener {private SeekBar     sb;private MediaPlayer player;private int         duration;// 播放器的几个状态private static final int PLAYING  = 1;// 播放状态private static final int PAUSING  = 2;// 暂停状态private static final int STOPPING = 3;// 停止状态private volatile     int CURRENT  = 0;// 当前状态private Timer timer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sb = (SeekBar) findViewById(R.id.sb);//设置拖动监听sb.setOnSeekBarChangeListener(this);}//播放public void play(View view) {if (player != null) {if (CURRENT == PLAYING) {Toast.makeText(this, "音乐已经在播放了", Toast.LENGTH_SHORT).show();return;} else if (CURRENT == PAUSING) {player.start();CURRENT = PLAYING;return;}}try {//创建一个播放器对象player = new MediaPlayer();//给播放器设置音乐路径player.setDataSource("/mnt/sdcard/test.mp3");//设置音乐格式player.setAudioStreamType(AudioManager.STREAM_MUSIC);//准备player.prepare();//获取音乐最大长度(毫秒单位)duration = player.getDuration();//给SeekBar 设置最大值sb.setMax(duration);//音乐开始播放player.start();//设置当前的状态为播放CURRENT = PLAYING;if (timer == null) {//创建定时器timer = new Timer();}/*** 参数1:匿名内部类,相当于Runnable 类* 参数2:第一次延时多长时间(毫秒)后执行,0 则代表立即执行* 参数3:每隔多长时间(毫秒)执行一次*/timer.schedule(new TimerTask() {@Overridepublic void run() {//该方法每1 秒被调用一次if (CURRENT == PLAYING) {runOnUiThread(new Runnable() {@Overridepublic void run() {//双重判断,尽可能避免线程问题,因为该段代码时在主线程中的,//第一次判断是在子线程中进行的if (player != null && CURRENT == PLAYING) {//获取当前的播放进度int currentPosition = player.getCurrentPosition();//设置给SeekBarsb.setProgress(currentPosition);}}});}}}, 0, 1000);} catch (Exception e) {e.printStackTrace();Toast.makeText(this, "音乐播放失败" + e, 0).show();}}/*** 暂停*/public void pause(View view) {if (player != null && CURRENT == PLAYING) {player.pause();CURRENT = PAUSING;}}/*** 停止*/public void stop(View view) {if (player != null) {if (CURRENT == PLAYING || CURRENT == PAUSING) {CURRENT = STOPPING;//取消定时器timer.cancel();timer = null;player.stop();player.reset();player.release();player = null;sb.setProgress(0);}}}/** 拖动过程中回调多次*/@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {if (player == null) {sb.setProgress(0);} else {player.seekTo(progress);}}/** 开始拖动前回调一次*/@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {if (player == null) {Toast.makeText(this, "音乐播放器还未开始", Toast.LENGTH_SHORT).show();}}/** 结束拖动后回调一次*/@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}@Overrideprotected void onDestroy() {super.onDestroy();stop(null);}}

3、播放本地res/raw/目录下的资源

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

4、播放本地URI资源

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

5、播放网络资源

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

6、异步准备

String url = "http://........"; // your URL hereMediaPlayer mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(url);mediaPlayer.prepareAsync();mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}});

7、在后台Service异步执行播放任务

public class MyService extends Service implements MediaPlayer.OnPreparedListener {MediaPlayer mMediaPlayer;private static final String ACTION_PLAY = "com.example.action.PLAY";MediaPlayer mMediaPlayer = null;public int onStartCommand(Intent intent, int flags, int startId) {...if (intent.getAction().equals(ACTION_PLAY)) {mMediaPlayer = ... // initialize it heremMediaPlayer.setOnPreparedListener(this);mMediaPlayer.prepareAsync(); // prepare async to not block main thread}}/** Called when MediaPlayer is ready */public void onPrepared(MediaPlayer player) {player.start();}
}public void initMediaPlayer() {// ...initialize the MediaPlayer here...mMediaPlayer.setOnErrorListener(this);}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// ... react appropriately ...// The MediaPlayer has moved to the Error state, must be reset!}

8、在手机睡眠时使用唤醒锁

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//wifi锁
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");wifiLock.acquire();//当暂停,不再需要网络时释放锁
wifiLock.release();

9、在前台服务运行播放任务

String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,new Intent(getApplicationContext(), MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample","Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);

10、处理音频焦点

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {// could not get audio focus.
}

requestAudioFocus() 方法的第一个参数是一个 AudioManager.OnAudioFocusChangeListener,当任何时候音频焦点发生变化的时候,会回调 OnAudioFocusChangeListener的onAudioFocusChange()方法

class MyService extends Serviceimplements AudioManager.OnAudioFocusChangeListener {// ....public void onAudioFocusChange(int focusChange) {// Do something based on focus change...}
}

11、播放完毕时手动释放资源

public class MyService extends Service {MediaPlayer mMediaPlayer;// ...@Overridepublic void onDestroy() {if (mMediaPlayer != null) mMediaPlayer.release();}
}

三、Mp3文件简介

ID3

一般是位于一个mp3文件的开头或末尾的若干字节内,附加了关于该mp3的歌手,标题,专辑名称,年代,风格等信息,该信息就被称为ID3信息,ID3信息分为两个版本,v1和v2版。 其中:v1版的ID3在mp3文件的末尾128字节,以TAG三个字符开头,后面跟上歌曲信息。 v2版一般位于mp3的开头,可以存储歌词,该专辑的图片等大容量的信息。

V1与V2

  • ID3V1记录在MP3文件的末尾,长度固定
  • ID3V2就记录在MP3文件的首部。 ID3V2一共有4个版本,但流行的播放软件一般只支持第3版,既ID3v2.3。
  • 对ID3V2的操作比ID3V1要慢。而且ID3V2结构比ID3V1的结构要复杂得多,但比ID3V1全面且可以伸缩和扩展。

四、视频播放器

1、SurfaceView

SurfaceView提供了一个绘画的界面,你可以控制该界面的格式和大小,SurfaceView 负责在屏幕正确位置安置一个界面。SurfaceView的其中一个目的,是为了在子线程渲染屏幕,但需要注意几点

  • 所有SurfaceView 和SurfaceHolder.Callback 的方法,都必须在主线程调用
  • 必须保证绘制线程,必须在SurfaceView 有效的情况下才能使用,也就是在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间调用

SurfaceView是View的子类,等同于TextView、ImageView等一系列控件。核心功能可以通过子线程进行界面的绘制,绘制需要注意的内容:

所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。

由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface

  • 双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面
  • SurfaceView 是双缓冲机制,一个用来缓冲数据,另一个用来展现数据,可以提高数据展示的速度,单缓冲需要先加载数据再去展示数据,但是双缓冲在展示第一个页面数据的时候已经把第二个页面的数据加载好了
  • 对画面的实时更新要求较高,重量级组件,可见时才创建
  • SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放
  • 播放视频也是用MediaPlayer,不过跟音频不同,要设置显示在哪个SurfaceView

2、使用MediaPlayer+SurfaceView 播放视频

在该节中,视频播放依然使用MediaPlayer 类,为了方便演示,我们直接使用本文中创建的工程,只需在布局文件添加SurfaceView 控件即可

布局文件

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context=".MainActivity"><SurfaceView
            android:id="@+id/sv"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>

实现代码

public class MainActivity extends Activity {private MediaPlayer player;static int currentPosition;private SurfaceView sv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sv = (SurfaceView) findViewById(R.id.sv);//拿到surfaceview的控制器final SurfaceHolder sh = sv.getHolder();//      Thread t = new Thread(){//          @Override//          public void run() {//              try {//                  sleep(200);//              } catch (InterruptedException e) {//                  e.printStackTrace();//              }//              runOnUiThread(new Runnable() {//                  @Override//                  public void run() {//                      MediaPlayer player = new MediaPlayer();//                      player.reset();//                      try {//                          player.setDataSource("sdcard/2.3gp");//                          player.setDisplay(sh);//                          player.prepare();//                          player.start();//                      } catch (Exception e) {//                          e.printStackTrace();//                      } //                      //                  }//              });//              //          }//      };//      t.start();//SurfaceView是重量级组件,可见时才会创建//给SurfaceHolder设置CallBack,类似于侦听,可以知道SurfaceView的状态sh.addCallback(new Callback() {//surfaceView销毁时调用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {//每次surfaceview销毁时,同时停止播放视频if(player != null){currentPosition = player.getCurrentPosition();player.stop();player.release();player = null;}}//surfaceView创建时调用@Overridepublic 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) {e.printStackTrace();}}}//surfaceView结构改变时调用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}});}}

使用VideoView 播放视频

VideoView 跟MediaPlayer 相比播放视频步骤要简单的多,因为VideoView 原生提供了播放,暂停、快进、快退、进度条等方法。使用起来要方便的很多

1、设置布局文件,布局文件比较简单,因此这里只给你VideoView 标签

 <VideoView
 android:id="@+id/vv"android:layout_width="match_parent"android:layout_height="match_parent" />

2、设置VideoView 的播放文件路径和媒体控制器,调用start 方法即可播放媒体文件

//实例化VideoView 对象
vv = (VideoView) findViewById(R.id.vv);
//从界面获取播放路径
et_path = (EditText) findViewById(R.id.et_path);//给VideoView 设置视频路径
vv.setVideoPath(et_path.getText().toString());
//设置VideoView 控制器,我们当前类实现了MediaPlayerControl 接口
vv.setMediaController(new MediaController(this));
//开始播放vv.start();//设置当前播放器窗口设置为焦点vv.requestFocus();

3、覆写MediaPlayerControl 接口中的抽象方法

 @Overridepublic void start() {}@Overridepublic void pause() {}@Overridepublic int getDuration() {return 0;}@Overridepublic int getCurrentPosition() {return 0;}@Overridepublic void seekTo(int pos) {}@Overridepublic boolean isPlaying() {return false;}@Overridepublic int getBufferPercentage() {return 0;}@Overridepublic boolean canPause() {return false;}@Overridepublic boolean canSeekBackward() {return false;}@Overridepublic boolean canSeekForward() {return false;}@Overridepublic int getAudioSessionId() {return 0;}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {return super.onKeyMultiple(keyCode, repeatCount, event);}

注意:上面的方法都是回调方法,我们可以在这些方法里面实现我们的业务逻辑。只有当我们给VideoView设置setMediaController 后控制器才会出现

收音机

  • 播放协议: MMS
  • MMS(Microsoft Media Server protocol)是一种串流媒体传送协议 ,android并不支持这种流媒体协议

引入Vitamo框架进行播放

  • 核心类:io.vov.vitamio.MediaPlayer
  • 操作:同系统的MediaPlayer,代码编写与播放网络音乐相近

视频处理

电影文件有很多基本的组成部分。首先,文件本身被称为容器Container,容器的类型决定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接着,你有一组流,例如,你经常有的是一个音频流和一个视频流。(一个流只是一种想像出来的词语,用来表示一连串的通过时间来串连的数据元素)。在流中的数据元素被称为帧Frame。每个流是由不同的编码器来编码生成的。编解码器描述了实际的数据是如何被编码Coded和解码DECoded的,因此它的名字叫做CODEC。接着从流中被读出来的叫做包Packets。包是一段数据,它包含了一段可以被解码成方便我们最后在应用程序中操作的原始帧的数据。

七个模块分别为:读文件模块,解复用模块 ,视频解码模块,音频解码音频,颜色空间转换模块,视频显示模块,音频播放模块

粗略的分为五类,分别是 Source filer, Demux flter, Decoder filter, Color Space converter filter,Render filter,各类 filter的功能与作用简述如下

Source filter

Source filter 源过滤器的作用是为下级 demux filter 以包的形式源源不断的提供数据流。在通常情况下,我们有多种方式可以获得数据流,一种是从本地文件中读取,一种是从网上获取,Sourcefilter 另外一个作用就是屏蔽读本地文件和获取网络数据的差别,在下一级的 demux filter 看来,本地文件和网络数据是一样的。

Demux filter

解复用过滤器的作用是识别文件类型,媒体类型,分离出各媒体原始数据流,打上时钟信息后送给下级 decoder filter。为识别出不同的文件类型和媒体类型,常规的做法是读取一部分数据,然后遍历解复用过滤器支持的文件格式和媒体数据格式,做匹配来确定是哪种文件类型,哪种媒体类型;有些媒体类型的原始数据外面还有其他的信息,比如时间,包大小,是否完整包等等。demux filter 解析数据包后取出原始数据,有些类型的媒体不管是否是完整包都立即送往下级 decoder filter,有些类型的媒体要送完整数据包,此时可能有一些数据包拼接的动作;当然时钟信息的计算也是 demux filter 的工作内容,这个时钟用于各媒体之间的同步。在本例中,AVI Splitter 是 Demux filter。

Decoder filter

解码过滤器的作用就是解码数据包,并且把同步时钟信息传递下去。对视频媒体而言,通常是解码成 YUV 数据,然后利用显卡硬件直接支持 YUV 格式数据 Overlay 快速显示的特性让显卡极速显示。YUV格式是一个统称,常见的有 YV12,YUY2,UYVY 等等。有些非常古老的显卡和嵌入式系统不支持 YUV 数据显示,那就要转换成 RGB 格式的数据,每一帧的每一个像素点都要转换,分别计算 RGB 分量,并且因为转换是浮点运算,虽然有定点算法,还是要耗掉相当一部分 CPU,总体上效率底下;对音频媒体而言,通常是解码成 PCM 数据,然后送给声卡直接输出。在本例中,AVI Decompress 和 ACM Warper 是 decoder filter。

Color space converter filter

颜色空间转换过滤器的作用是把视频解码器解码出来的数据转换成当前显示系统支持的颜色格式。通常视频解码器解码出来的是 YUV 数据,PC 系统是直接支持 YUV 格式的,也支持 RGB 格式,有些嵌入式系统只支持 RGB 格式的。在本例中,视频解码器解码出来的是 RGB8 格式的数据,Color space converter filter 把 RGB8 转换成 RGB32 显示。

Render filter

渲染过滤器的作用就是在适当的时间渲染相应的媒体,对视频媒体就是直接显示图像,对音频就是播放声音。视音频同步的策略方法有好几种,其中最简单的一种就是默认视频和音频基准时间相同,这时音频可以不打时钟信息,通过计算音频的采样频率,量化 bit 数,声道数等基本参数就知道音频 PCM 的数据速率,按照这个速率往前播放即可;视频必须要使用同步时钟信息来决定什么时候显示。DirectShow 采用一个有序链表 ,把接收到的数据包放进有序链表中,启动一个定时器,每次定时器时间到就扫描链表,比较时钟信息,或者显示相应的帧,或者什么也不做,每次接收到新的数据帧,首先判断时钟信息,如果是历史数据帧就丢弃,如果是将来显示数据帧就进有序链表,如果当前时间帧就直接显示。如此这样,保持视频和音频在人体感觉误差范围内相对的动态同步。在本例中 VideoRender 和 Default DirectSound Device 是 Render filter,同时也是 Sink filter

JetPlayer

JetPlayer jetPlayer = JetPlayer.getJetPlayer();
jetPlayer.loadJetFile("/sdcard/level1.jet");
byte segmentId = 0;// queue segment 5, repeat once, use General MIDI, transpose by -1 octave
jetPlayer.queueJetSegment(5, -1, 1, -1, 0, segmentId++);
// queue segment 2
jetPlayer.queueJetSegment(2, -1, 0, 0, 0, segmentId++);jetPlayer.play();

TextureView

TextureView、Surfaceview、SurfaceTexture、GLSurfaceView

画中画

https://github.com/googlesamples/android-PictureInPicture

https://developer.android.google.cn/training/tv/playback/picture-in-picture.html?hl=zh-cn#handling_ui

Android多媒体开发相关推荐

  1. Android多媒体开发技术

    周末参加了线下安卓巴士论坛组织的"安卓开发者的修炼之道",几位嘉宾分享的内容都很赞.没时间整理文字版,直接分享嘉宾的PPT了. 第二位嘉宾何俊林,前爱奇艺多媒体开发,据说活动现场很 ...

  2. android开发高级开发,Android多媒体开发高级编程 (Shawn Van Every) pdf扫描版

    <android多媒体开发高级编程--为智能手机和平板电脑开发图形.音乐.视频和富媒体应用>使用清晰.直观的示例介绍了android sdk中丰富的多媒体功能,有助于您开发能够创建.播放和 ...

  3. android编程高级教程 pdf,Android多媒体开发高级编程

    在多媒体功能方面,今天的手机同时是照相机.相册.摄像机.电影播放器.音乐播放器.听写机,且可能具备更多功能. 特别是,Android在SDK中具有非常丰富的功能,本书将试图通过讨论和实例对该SDK进行 ...

  4. Android多媒体开发介绍

    Android多媒体开发介绍 一.       多媒体架构 基于第三方PacketVideo公司的OpenCORE来实现,支持所有通用的音频/视频/静态图像格式,包括:MPEG4.H.264.MP3. ...

  5. Android多媒体开发(3)————使用Android NKD编译havlenapetr-FFMpeg-7c27aa2

    1. 使用NDK去编译官方的FFmpeg原版的话,还得自己实现JNI层与Java层,工程量比较大.所以移植FFmpeg到Android平台时,可以移植一些已经实现JNI与JAVA层的开源项目,毕竟软件 ...

  6. Android多媒体开发-stagefright及AwesomePlayer相关知识梳理

    android的多媒体框架中, stagefright其实是AwesomePlayer的代理,就是个皮包公司. status_t StagefrightPlayer::setDataSource( c ...

  7. Android 多媒体开发 MediaPlayer|SoundPool |MediaRecorder |Camera2| MediaProjectionManager

    android支持音频格式:  mp3.wav.3gp  视频: MP4.g3p 等等.... 1. MediaPlayer|SoundPool 使用   1.1. MediaPlayer 加载资源 ...

  8. 【Android 多媒体开发】 MediaPlayer 状态机 接口 方法 解析

    作者 : 韩曙亮 转载请著名出处 :  http://blog.csdn.net/shulianghan/article/details/38487967 一. MediaPlayer 状态机 介绍 ...

  9. Android多媒体开发:录音机

    我们可以使用Android提供的MediaRecorder类的相关方法来实现一个录音机的功能.步骤如下 创建一个MediaRecorder实例 使用 MediaRecorder的setAudioSou ...

最新文章

  1. linux命令see的用法,Linux一些基本命令与用法
  2. qlineedit文本改变时_行文本编辑框QLineEdit及自动补全
  3. 山东省2O2021年普通高考成绩查询,2021山东高考报名人数公布
  4. 【职场经验】算法同学从学校到实习,需要改变什么
  5. UML轻松入门--类和对象
  6. .gitkeep文件的作用
  7. JavaScript==比较的规则
  8. 3.11 框架和样式表
  9. 转换PHP脚本成为windows的执行程序
  10. python jupyter
  11. 警察招计算机科学与技术专业,山西警官高等专科学校计算机科学与技术系
  12. 证券交易2-券商柜台系统
  13. ion orphaned memory
  14. 计算机二级vb考试教材,计算机二级考试VB基础题教材.doc
  15. 全网征集对六如偈的理解:一切有为法,如梦幻泡影,如露亦如电,应作如是观
  16. 0 在c语言中有什么作用,\0在c语言中代表什么?
  17. Python解压.gz,.zip文件
  18. 防火墙设计和部署解析
  19. 给你心爱的 npm 包上个『北京户口』
  20. 增强 Jupyter Notebook 的功能,这里有 4 个妙招

热门文章

  1. 转载 为什么要有ibgp
  2. 实验问题总结与分享之DNS(Domain Name Server)
  3. [Swift]LeetCode513. 找树左下角的值 | Find Bottom Left Tree Value
  4. docker 部署mysql服务之后,执行sql脚本
  5. 程序员编程能力7个能力提升阶段分析
  6. 局域网打印机共享怎么设置?如何设置打印机共享?
  7. 在Source Insight中看Python代码
  8. [博客之路]如何增加一个博客的PR值(一)
  9. 中国智能语音产业发展白皮书十大观点发布!科大讯飞市占率国内第一
  10. Chrome用户请尽快更新:谷歌发现两个严重的零日漏洞