您的位置:首页 > 移动开发 > Android开发

android 多媒体和相机详解(1-5)

2012-08-01 10:22 363 查看
 

android 多媒体和相机详解一

 

Android多媒体框架包含了获取和回放音频,视频和各种类形的图像的功能,所以你可以很容易的把它们整合到你的应用中.你可以从存储在资源中的文件,文件系统中的文件,或从网络数据流中播放音频或视频,这些都是用MediaPlayer或JetPlayerAPI实现.你也可以使用MediaRecorderand
Camera API来录制声音,视频或抓取图片.

  下面的主题向你演示了如何使用Android框架来实现多媒体获取和回放.

MediaPlayer

如何在你的应用中播放音视频.

JetPlayer

如何使用通过JetCreator创建的内容播放交互式的音视频.

Camera  

如何在你的应用中使用一个设备上的相机获取图片和视频.

AudioCapture

如何在你的应用中录制声音.

媒体回放

  Android多媒体框架支持播放很多常见的媒体类型,所以你可以使用MediaPlayerAPI很轻松的整合音视频和图像到你的应用中.你可以从资源中,从文件中,从网络上播放音视频.

  此文档向你演示了如何写一个媒体播放应用,如何与用户和系统交互,从而获得最好的性能和用户体验.

  注:你只能把音视频播放到标准的输出设备上.当前,它们是扬声器或蓝牙耳机.你不能在电话通话时播放音频文件.

基础

  以下类被用于播放音频和视频:

MediaPlayer

此类是播放音视频的主要API.

AudioManager

此类管理设备上的音频源和输出.

Manifest声明

  在使用MediaPlayer开发之前,确保你的manifest中声明了允许使用的相关特性.

InternetPermission -
如果你使用MediaPlayer来播放网络流中的内容,你的应用必须请求网络存取权限.

<uses-permissionandroid:name="android.permission.INTERNET" />

WakeLock Permission
-如果你的播放应用需要阻止屏幕变暗或阻止处理器睡眠,或使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()方法,你必须请求此权限.

<uses-permissionandroid:name="android.permission.WAKE_LOCK" />

使用MediaPlayer

  媒体框架中最重要的组件之一就是MediaPlayer类.此类的对象可以用少量的设置即能获取,解码和播放音视频.它支持多种媒体源,比如:

本地资源.

内部URI,比如你从ContentResolver取得的URI.

外部URI(流媒体)

要了解Android支持的媒体类型列表,请看AndroidSupported
Media Formats 文档.

下面是如何播放本地资源中的音频的例子(保存在你的应用的res/raw/文件夹下):

[java]
view plaincopyprint?

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);  
mediaPlayer.start(); //不需要调用prepare(); create()为你做了  

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); //不需要调用prepare(); create()为你做了


 

  在例子中,"raw"资源是一个系统不会以某种方式进行分析的文件.然而,此资源的内容不能是原始音频,它应是一种适当编码和格式化的媒体文件(当然是被支持的格式).

以下是如何播放一个本地URI的例子(URI是你用ContentResolver获取的):

[java]
view plaincopyprint?

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

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


从一个基于HTTP流的的远程URL播放看起来是这样的

[java]
view plaincopyprint?

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();  

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();


 

  注:如果你传入了一个URL来流式播放一个在线文件,这个文件必须前进式下载progressivedownload.

  警告:当使用setDataSource()时你必须捕获和传递IllegalArgumentException和IOException,因为你引用的文件可能不存在.

异步Preparation

  使用MediaPlayer,在其本质上来说可以是简单直接的.然而,对于一个典型android应用来说还有一些重要的事情要记住.例如,prepare()调用可能耗时很常,因为它可能需要获取并打开解码媒体数据.所以,由于有些方法会执行很长时间,那么你就不能从你的应用的UI线程中调用它.否则会导致UI挂起,直到此方法返回为止.这是很差劲的用户体验,还会导致一个ANR(应用没有反应)错误.即使你认为你的资源加载的很快,但记住界面中任何耗时超过10秒的东西将导致一个显著的暂停并使用户对你的应用产生慢的印像.

  要避免挂起UI线程,应产生另外的线程来"prepare"MediaPlayer并且在完成时通知主线程.然而,色虽然你可以亲自写线程中的逻辑,但是更常用的是使用框架所提供的一个方便的途径:使用prepareAsync().此方法在后台开始"准备"过程并立即返回.当媒体"准备"完成,MediaPlayer.OnPreparedListener的onPrepared()方法(通过setOnPreparedListener()设置的)被调用.

管理状态

  对MediaPlayer要记住的另一个方面就是"基于状态".即,MediaPlayer有一个内部的状态,因为特定的操作只能在特定的状态时才有效,所以你必须在写代码时一直注意到它的变化.如果你在错误的状态下执行一个操作,系统可能抛出一个异常或导致一个意外的行为.

  MediaPlayer类文档中展示了一个完整的状态图例,它阐明了哪个方法使MediaPlayer从一个状态进入另一个状态.例如,当你创建一个新的MediaPlayer,它处于Idle状态.此时,你应调用setDataSource()初始化它,使它进入"已初始化"状态.之后,你应使用prepare()或prepareAsync()"准备"它.当MediaPlayer准备完成,它将进入Prepared状态,这表示你可以调用start()来播放了.此时,如图表所示,你可以调用start(),pause(),和seekTo()以及其它一些方法使MediaPlayer的状态在Started,Paused和PlaybackCompleted状态之间转换.当你调了stop(),注意你不能再调用start(),除非你重新prepare MediaPlayer.

  当你写代码与MediaPlayer交互时,要时刻记住MediaPlayer的状态变化图,因为在错误的状态下调用它的方法是常见的bug的原因.

 

 

 

android 多媒体和相机详解二

释放MediaPlayer

  MediaPlayer可能消耗大量的系统资源.因此你应该总是采取一些额外的措失来确保在一个MediaPlayer实例上不会挂起太长的时间.当你用完MediaPlayer时,你应该总是调用release()来保证任何分配给MediaPlayer的系统资源被正确地释放.例如,如果你正在使用MediaPlayer并且你的activity收到了一个对onStop()的调用,你必须释放MediaPlayer,因为当你的activtiy不再与用户交互时继续保持MediaPlayer会使用户有一点慢的感觉(除非你在后台播放媒体).当你的activityis
resumed或restarted,你理所当然的需要创建一个新的MediaPlayer并且在恢复播放前重新准备它.

下面是如何释放MediaPlayer:

[java]
view plaincopyprint?

mediaPlayer.release();  
mediaPlayer = null;  

mediaPlayer.release();
mediaPlayer = null;


  作为一个例子,想像一下如果当你的activitystopped时你忘记了释放MediaPlayer,而activity重新start时又创建了一个新的MediaPlayer这样的问题.就像你知道的,当用户改变屏幕的方向(或用另外的方法改变了设备的配置),系统处理的方式是重启activity(默认情况),于是当用户来回旋转设备时你可能消耗掉了所有的系统资源,因为在每次方向改变时,你都创建了一个新的MediaPlayer但是从不釋放它.

  你现在可能对如何在没有activity时仍然在后台播放媒体感兴趣了,请看下一章.

使用带有MediaPlayer的service

  如果你希望你的媒体在你的应用不出现在屏幕上时仍能在后台播放—也就是,你希望当用户与其它应用交互时仍能继续播放—那么你必须启动一个Service并且通过它控制MediaPlayer实例.但此方式下你应该小心慬慎,因为用户和系统都对一个应用运行一个后台service时应该如何与剩余的系统交互抱有期望值.如果你的应用不能满足这些期望,用户体验可能很坏.本节描述你应该注意的主要问题并且给出如何达到要求的建议.

异步运行

  首先,跟Activity一样,默认下所有的Service的工作都是在一个单独的线程中完成—实际上,如果你从同一个应用中运行一个activity和一个service,它们默认使用同一个线程("主线程").因此,service需要快速处理进入的intent并且永不对它们执行长时间的计算.如果要执行某些重型工作和阻塞调用,你必须异步地执行它们:可以在你自己实现的另外线程中,也可以使用框架的一些异步处理工具.

  例如,当在主线程中使用一个MediaPlayer,你应该调用prepareAsync()而不是prepare(),并且实现一个MediaPlayer.OnPreparedListener来监听"准备"完成通知并开始播放.例如:

[java]
view plaincopyprint?

public class MyService extends Service implements MediaPlayer.OnPreparedListener {  
    private static final 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 here
  
            mMediaPlayer.setOnPreparedListener(this);  
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
  
        }  
    }  
  
    /** Called when MediaPlayer is ready */  
    public void onPrepared(MediaPlayer player) {  
        player.start();  
    }  
}  

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final 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 here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}

/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}


 

处理异步错误

  在异步操作时,错误通常是用异常或错误码通知的,但是无论何时你使用异步资源,你都应确保你的应用能被正确的通知错误.在使用MediaPlayer时,你可以通过实现一个MediaPlayer.OnErrorListener并把它设置给你的MediaPlayer实例来达到此目的.

[java]
view plaincopyprint?

public class MyService extends Service implements MediaPlayer.OnErrorListener {  
    MediaPlayer mMediaPlayer;  
  
    public void initMediaPlayer() {  
        // ...initialize the MediaPlayer here...
  
  
        mMediaPlayer.setOnErrorListener(this);  
    }  
  
    @Override  
    public boolean onError(MediaPlayer mp, int what, int extra) {  
        // ... react appropriately ...
  
        // The MediaPlayer has moved to the Error state, must be reset!
  
    }  
}  

public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;

public void initMediaPlayer() {
// ...initialize the MediaPlayer here...

mMediaPlayer.setOnErrorListener(this);
}

@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}


 

  有一点很重要:当错误发生时,MediaPlayer变为错误状态,你必须在重新使用它之前重置它才行.

使用唤醒锁

  当设计在后台播放媒体的应用时,当你的service正在运行时,设备可能进入休眠.因为Android系统在休眠时会试着节省电能,那么系统会试着关闭电话的任何不必要的特性,包括CPU和WiFi.然而,如果你的service正在播放或接收音乐,你就想阻止系统干涉你的播放工作.

  为了在上述情况下保证你的service继续运行,你必须使用"wakelocks".一个wakelock是一种通知系统在手机空闲时也应为你的应用保留所用特性的途径.

  注意:你总是应该保守的使用wakelocks并且仅在真证需要时才持有它.因为它们会显著的减少设备电池的寿命.

  当你的MediaPlayer播放时,要保持CPU持续运行,在初始化MediaPlayer时需调用setWakeMode().一旦你这样做了,MediaPlayer就会在播放时持有一个特定的锁,并在暂停或停止时释放它:

[java]
view plaincopyprint?

mMediaPlayer = new MediaPlayer();  
// ... other initialization here ...
  
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);  

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);


 

然而,此例中所请求的wakelock只能保证CPU保持清醒.如果你正通过Wi-Fi从网络串流媒体数据,你可能也想持有WifiLock.对它你必须手动请求和释放.所以,当你使用远程URL准备MediaPlayer,你应该创建并请求Wi-Filock.例如:

[java]
view plaincopyprint?

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))  
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");  
  
wifiLock.acquire();  

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();


当你暂停或停止你的媒体或当你不现需要网络时,你应该释放这个锁:

[java]
view plaincopyprint?

wifiLock.release();  

wifiLock.release();


 

作为前台服务运行

  Services一般用于执行后台任务,比如获取邮件,同步数据,下载内容以及其它工作.这些情况下,用户不会太注意service的执行,并且可能跟本注意不到它们的中断以及重新运行.

  但是现在考虑一下用service播放音乐.很明显,用户会非常注意这个service并且一些中断会严重影响用户体验.另外,这种service还是用户在其执行期间想与之交互的.此情况下,此服务应作为一个"foregroundservice"运行.一个前台具有高重要性—系统永不会杀死它,因为它跟用户直接相关.当运行于前台时,service还必须在状态通知栏上提供一个通知来保证用户能看到service正在运行并且允许他们打开一个activity与service交互.

为了把你的service搞到前台,你必须为状态栏创建一个Notification并且调用startForeground().例如:

[java]
view plaincopyprint?

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);  

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);


  当你的service在前台运行时,你所配置的通知就出现在设备的通知区域.如果用户选择了这个通知,系统就会调用你提供的PendingIntent.在上例中,它打开了一个activity(MainActivity).

图 1演示了你的通知如何显示给用户:


 


图 1.前台service的通知截图,左图显示了状态栏的通知,右图显示了通知打开的view.

你应该只在用户需要注意service的执行情况时才使它保持"前台service"的状态,一旦此情况改变,你就应该调用stopForeground()把前台状态释放掉:

[java]
view plaincopyprint?

stopForeground(true);  

stopForeground(true);


 

 

android 多媒体和相机详解三

 

处理音频焦点

  尽管某个时刻只有一个activity可以运行,Android却是一个多任务环境.这对使用音频的应用带来了特殊的挑战,因为只有一个音频输出而可能多个媒体都想用它.在Android2.2之前,没有内建的机制来处理这个问题,所以可能在某些情况下导致坏的用户体验.例如,当一个用户正在听音乐而另一个应用需要通知用户一些重要的事情时,用户可能由于音乐声音大而不能听的通知.从Android2.2开始,平台为应用提供了一个协商它们如何使用设备音频输出的途径,这个机制叫做音频焦点.

  当你的应用需要输出像乐音和通知之类的音频时,你应该总是请求音频焦点.一旦应用具有了焦点,它就可以自由的使用音频输出.但它总是应该监听焦点的变化.如果被通知丢失焦点,它应该立即杀死声音或降低到静音水平(有一个标志表明应选择哪一个)并且仅当重新获得焦点后才恢复大声播放.

  将来的音频焦点是合作的.所以,应用被希望(并被强列鼓励)遵守音频焦点的方针,但是却不是被系统强制的.如果一个应用在丢失音频焦点后依然想大声播放音乐,系统不会去阻止它.然而用户却体验很坏并且很想把这鸟应用卸载.

要请求音频焦点,你必须从AudioManager调用requestAudioFocus(),如下所示:

[java]
view plaincopyprint?

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);  
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,  
    AudioManager.AUDIOFOCUS_GAIN);  
  
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {  
    // 不能获得音频焦点   
}  

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 不能获得音频焦点
}


  requestAudioFocus()的第一个参数是一个AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法在音频焦点发改变时被调用.因此,你也应该在你的service和activity上实现此接口.例如:

[java]
view plaincopyprint?

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

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


参数focusChange告诉你音频焦点如何发生了变化,它可以是以上几种值(它们都是定义在AudioManager中的常量):

AUDIOFOCUS_GAIN:你已获得了音频焦点.

AUDIOFOCUS_LOSS:你已经丢失了音频焦点比较长的时间了.你必须停止所有的音频播放.因为预料到你可能很长时间也不能再获音频焦点,所以这里是清理你的资源的好地方.比如,你必须释放MediaPlayer.

AUDIOFOCUS_LOSS_TRANSIENT:你临时性的丢掉了音频焦点,很快就会重新获得.你必须停止所有的音频播放,但是可以保留你的资源,因为你可能很快就能重新获得焦点.

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你临时性的丢掉了音频焦点,但是你被允许继续以低音量播放,而不是完全停止.

下面是一个例子:

[java]
view plaincopyprint?

public void onAudioFocusChange(int focusChange) {  
    switch (focusChange) {  
        case AudioManager.AUDIOFOCUS_GAIN:  
            // resume playback   
            if (mMediaPlayer == null) initMediaPlayer();  
            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();  
            mMediaPlayer.setVolume(1.0f, 1.0f);  
            break;  
  
        case AudioManager.AUDIOFOCUS_LOSS:  
            // Lost focus for an unbounded amount of time: stop playback and release media player
  
            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();  
            mMediaPlayer.release();  
            mMediaPlayer = null;  
            break;  
  
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:  
            // Lost focus for a short time, but we have to stop
  
            // playback. We don't release the media player because playback
  
            // is likely to resume
  
            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();  
            break;  
  
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:  
            // Lost focus for a short time, but it's ok to keep playing
  
            // at an attenuated level
  
            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);  
            break;  
    }  
}  

public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
break;

case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}


记住音频焦点API仅在APIlevel 8 (Android2.2)及更高版本上可以,所以如果你想支持更早的Android版本,你必须在可能时采取兼容性的策略使用特性,如果不可能,you
should adopt a backward compatibility strategy that allows you touse this feature if available, and fall back seamlessly if not.

  你可以用反射的方式调用音频焦点方法或自己在一个单独的类(叫做AudioFocusHelper)中实现所有的音频焦点功能来达到向前兼容.下面是一个这样的类:

[java]
view plaincopyprint?

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {  
    AudioManager mAudioManager;  
  
    // 这里是其它的字段,你可能要保存一个接口的引用,这个接口
  
    // 被用于与你的service通讯以报告焦点的变化.
  
  
    public AudioFocusHelper(Context ctx, /* 其它的参数 */) {  
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);  
        // ...   
    }  
  
    public boolean requestFocus() {  
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==  
            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,  
            AudioManager.AUDIOFOCUS_GAIN);  
    }  
  
    public boolean abandonFocus() {  
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==  
            mAudioManager.abandonAudioFocus(this);  
    }  
  
    @Override  
    public void onAudioFocusChange(int focusChange) {  
        // 让你的service知道焦点变化了
  
    }  
}  
  
  你可以仅在检测到系统运行的是API level 8 或更早的版本时才创建AudioFocusHelper类的实例.例如:  
if (android.os.Build.VERSION.SDK_INT >= 8) {  
    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);  
} else {  
    mAudioFocusHelper = null;  
}  

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;

// 这里是其它的字段,你可能要保存一个接口的引用,这个接口
// 被用于与你的service通讯以报告焦点的变化.

public AudioFocusHelper(Context ctx, /* 其它的参数 */) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// ...
}

public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
}

public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.abandonAudioFocus(this);
}

@Override
public void onAudioFocusChange(int focusChange) {
// 让你的service知道焦点变化了
}
}

  你可以仅在检测到系统运行的是API level 8 或更早的版本时才创建AudioFocusHelper类的实例.例如:
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}


 

清理

  前面提到过,一个MediaPlayer对象可以消耗掉大量的系统资源,所以你应该仅在需要它时保持它并在用完时立即释放.明确的调用清理方法而不是依靠系统的垃圾收集机制是很重要的,因为在被收集之前MediaPlayer可能会存在很长时间,虽然此时它只是占用内存而不影响其它的媒体相关的资源.所以,当你使用一个service时,你应该总四重写onDestroy()方法来保证释放MediaPlayer:

[java]
view plaincopyprint?

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

public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...

@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}


你也应该寻找其它需要释放你的MediaPlayer的时机.例如,如果你预料到长时间不能播放媒体(比如丢掉音频焦点以后),你应该明确地释放你的MediaPlayer,然后在后面重新创建它.反过来,如果你预测到只会短时间停止播放,你应该保持你的MediaPlayer来避免过多的创建,而不是重新"准备"它.

 

 

 

android 多媒体和相机详解四

 

处理AUDIO_BECOMING_NOISYIntent

  很多良好的音频播放的应用都会在那些导致声音变为噪音(通过外部扬声器输出)的事件发生时自动停止播放.例如,这可能发生在当一个用户用耳机听音乐时忽然断开了耳机连接.音频从扬声器播放可能不是用户期望的.

  你可以通过处理ACTION_AUDIO_BECOMING_NOISYintent
来保证你的应用在此情况下停止播放音乐,你可以把如下代码添加到你的manifest来注册一个receiver:

[java]
view plaincopyprint?

<receiver android:name=".MusicIntentReceiver">  
   <intent-filter>  
      <action android:name="android.media.AUDIO_BECOMING_NOISY" />  
   </intent-filter>  
</receiver>  

<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>


此段把MusicIntentReceiver类作为这个intent的一个广播接收器(broadcastreceiver)进行注册,下面就是要实现这个类:

[java]
view plaincopyprint?

public class MusicIntentReceiver implements android.content.BroadcastReceiver {  
   @Override  
   public void onReceive(Context ctx, Intent intent) {  
      if (intent.getAction().equals(  
                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {  
          // 通知你的service停止播放   
          // (比如通过一个Intent)
  
      }  
   }  
}  

public class MusicIntentReceiver implements android.content.BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(
android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// 通知你的service停止播放
// (比如通过一个Intent)
}
}
}

从一个ContentResolver获取媒体

  媒体播放应用的是另一个有用的特性是检索用户存放在设备上的音乐.你可以通过从ContentResolver查询媒体来做到:

[java]
view plaincopyprint?

ContentResolver contentResolver = getContentResolver();  
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  
Cursor cursor = contentResolver.query(uri, null, null, null, null);  
if (cursor == null) {  
    // 查询失败,处理错误   
} else if (!cursor.moveToFirst()) {  
    // 设备上没有媒体   
} else {  
    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);  
    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);  
    do {  
       long thisId = cursor.getLong(idColumn);  
       String thisTitle = cursor.getString(titleColumn);  
       // ...process entry...   
    } while (cursor.moveToNext());  
}  

ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// 查询失败,处理错误
} else if (!cursor.moveToFirst()) {
// 设备上没有媒体
} else {
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...process entry...
} while (cursor.moveToNext());
}


 

要在MediaPlayer中播放获取到的媒体,你可以这样做:

[java]
view plaincopyprint?

long id = /* 上面获取到的某个条目的id */;  
Uri contentUri = ContentUris.withAppendedId(  
        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);  
  
mMediaPlayer = new MediaPlayer();  
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);  
  
// ...prepare and start...  

long id = /* 上面获取到的某个条目的id */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...


JetPlayer

  Android平台包含一个JET引擎,用它可以添加交互式播放的JET音频内容到你的应用.你可以使用SDK所带的JetCreator应用来创建JET内容.要播放和管理JET内容,使用JetPlayer类.

播放JET内容

  本节教给你如何写出,配置和播放JET内容.JET的概念,介绍以及如何使用JetCreator工具创建JET内容,请看JetCreator用户手册,这里不叨叨.这个工具可以在Windows,OS
X, 和Linux平台(Linux上不能像Windows和OSX上那样对导入的资产进行试听)上使用.

下面是如何对存储在SD卡上的一个.jet文件配置JET回放:

[java]
view plaincopyprint?

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();  

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();


 

SDK包含一个例子—
JetBoy —它向你演示了如何使用JetPlayer在你的游戏中创建一个交互式音轨.它也演示了如何使用JET事件来同步音乐和游戏逻辑.

 

 

 

 

android 多媒体和相机详解五

 

相机

  Android框架架包含了各种相机和相机功能的支持,使你可以在你的应用中捕获图像和视频.本文档讨论一个简单快速的获取图像和视频的方法,并概述一个创建自定义用户相机体验的高级方法.

想一想

  在使你的应用能使用设备上的相机之前,你应该先想一想你的应用将来会如何使用此硬件.

Camera必须-相机是必须的,你不希望你的应用安装到一个没有相机的设备上.你应该在manifest文件中声明需要相机.

快速图像或自定义特性-你的应用将如何使用相机?你仅仅是抓取一个快速图片或视频剪辑,还是提供一个新的使用方式?前者请考虑使用现有的相机应用.后者请阅读后面的"创建一个相机应用"一节.

存储 -你的应用产生的图像和视频要给自己看还是共享给其它应用?你想在你的应用被删除后所创建的图像和视频仍然存在吗?请阅读后面的"保存媒体文件"一节来学习如何实现这些操作.

基础知识

  Android框架支持通过CameraAPI或Cemeraintent来抓取图像和视频.下面就是相关的类们:

Camera

此类是控制设备相机的主要API.此类用于在创建相机应用时获取图片和视频.

SurfaceView

此类为用户提供camera的实时图像预览.

MediaRecorder

此类用于从camera录制视频.

Intent

一个MediaStore.ACTION_IMAGE_CAPTURE或MediaStore.ACTION_VIDEO_CAPTURE型的intent,可以使用它来抓取图像或视频,而不用操作Camera对象们.

Manifest中的声明

  在使用CameraAPI开发你的应用之前,你需保证在你的manifest中声明了合适的条目使得有权使用相机和其它相关特性.

CameraPermission -
你的应用必须请求使用设备相机的取限.

<uses-permissionandroid:name="android.permission.CAMERA" />

注:如果你通intent使用camera,你的应用不必请求此权限.

CameraFeatures -
你的应用必须也要声明要使用的相机特性,比如:

<uses-featureandroid:name="android.hardware.camera" />

对于相机特性列表,请见manifestFeatures Reference.

  添加相机特性到你的manifest导致Android市场不会将你的应用安装到没有相机相机特性达不到你所声明要求的设备上.

  如果你的应用要使用相机或相机的一些特性,但又不是必须的,你应该在manifest中指定这些需求,但把android:required属性置为false:

<uses-featureandroid:name="android.hardware.camera"android:required="false" />

StoragePermission -
如果你的应用要存储图像或视频到外部存储上(SD卡),你必须也声明此权限.

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

AudioRecording Permission -
在使用视频捕获设备来录制音频时,你的应用必须请求音频捕获权限.

<uses-permissionandroid:name="android.permission.RECORD_AUDIO" />

使用现有的相机应用

  一个不用写代码来获取图片和视频的快速方法是使用intent来调用现有的Androidcamera
应用.一个cameraintent调用现存的相机应用抓取图片或视频剪辑然后返回的你的应用.本节向你演示如何使用此技术获取图片和视频.

  调用一个相机intent,主要有以下步骤:

组建一个相机Intent–
创建一个请求图片或视频的Intent,使用以下intent类型中的一个:

MediaStore.ACTION_IMAGE_CAPTURE-
从已存在的相机应用中请求一个图片.

MediaStore.ACTION_VIDEO_CAPTURE-
从已存在的相机应用中请求一个视频.

启动这个相机Intent-使用startActivityForResult()方法来执行相机intent.在你启动intent后,相机应用的界面会出现在设备屏幕上,然后用户就可以用它来获取图片或视频.

接收Intent结果-在你的应用中设置一个onActivityResult()方法来接收从相机intent来的回调和数据.当用户获取了一个图或视频之后(或取消了操作),系统就会调用此方法.

 

图像获取intent

  使用相机intent获取图像是使用最少代码获取图像的捷迳.一个图像获取intent包含以下额外信息:

MediaStore.EXTRA_OUTPUT-此设置需要一个Uri对象,这个对象指定了一个保存图像的路径和文件名.此设置是可选的,但是强烈建议使用之.如果你没有指定此值,相机应用就会把图像以默认的名字保存到默认的位置.

  下面的例子演示了形成一个图像获取intent并执行的方法.此例子中的getOutputMediaFileUri()方法是引用的”保存媒体文件”一节中的例子代码.

[java]
view plaincopyprint?

private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;  
private Uri fileUri;  
  
@Override  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.main);  
  
    // 创建一个获取图像的Intent
  
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);  
  
    fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // 创建一个文件来保存图像
  
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // 设置图像文件名
  
  
    // 开始图像获取Intent   
    startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);  
}  

private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private Uri fileUri;

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

// 创建一个获取图像的Intent
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // 创建一个文件来保存图像
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // 设置图像文件名

// 开始图像获取Intent
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}


 

  当startActivityForResult()方法被执行,看到一个相机应用的界面.当用户获取了一个图像(或取消了操作),用户界面返回到你的应用,你必须拦截onActivityResult()方法来接收intent的结果然后再继续执行你的应用.

视频获取intent

  使用相机intent获取视频是使用最少代码使得你的应用获取视频的捷径.一个视频获取intent可以包含以下额外信息:

MediaStore.EXTRA_OUTPUT-此设置需要一个保存视频的路径和文件名的Uri.此设置是可选的但是强列推荐的.如果你不指定此值,相机应用就把请求到的图像以默认的文件名保存到默认的文件夹下,这些信息保存在返回的intent的Intent.getData()字段中.

MediaStore.EXTRA_VIDEO_QUALITY-
此值在最低质量最小文件尺寸时是0,在最高质量最大文件尺寸时是1.

MediaStore.EXTRA_DURATION_LIMIT-
此值设置获取视频的长度,以秒为单位.

MediaStore.EXTRA_SIZE_LIMIT-
此值设置获取视频文件的大小,以字节为单位.

  下面的例子演示了如何构建一个视频获取intent并执行它.此例子中的getOutputMediaFileUri()方法是引用的”保存媒体文件”一节中的例子代码.

[java]
view plaincopyprint?

private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;  
private Uri fileUri;  
  
@Override  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.main);  
  
    //create new Intent
  
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);  
  
    fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);  // create a file to save the video
  
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);  // set the image file name
  
  
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high
  
  
    // start the Video Capture Intent
  
    startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);  
}  

private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
private Uri fileUri;

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

//create new Intent
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);  // create a file to save the video
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);  // set the image file name

intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high

// start the Video Capture Intent
startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
}


 

  当startActivityForResult()方法执行后,用户看到了一个改良的相机应用界面.在用户完成视频获取(或取消了操作)之后,用户界面返回到你的应用,你必须拦截onActivityResult()方法来接收intent的结果并且继续执行你的应用.

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: