Android6.0 Audio系统代码流程
Z:\workspace\MT3561\frameworks\base\media\java\android\media\AudioManager.java
/** The audio stream for phone calls */ public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL; /** The audio stream for system sounds */ public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM; /** The audio stream for the phone ring */ public static final int STREAM_RING = AudioSystem.STREAM_RING; /** The audio stream for music playback */ public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC; /** The audio stream for alarms */ public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM; /** The audio stream for notifications */ public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION; /** @hide The audio stream for phone calls when connected to bluetooth */ public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO; /** @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */ public static final int STREAM_SYSTEM_ENFORCED = AudioSystem.STREAM_SYSTEM_ENFORCED; /** The audio stream for DTMF Tones */ public static final int STREAM_DTMF = AudioSystem.STREAM_DTMF; /** @hide The audio stream for text to speech (TTS) */ public static final int STREAM_TTS = AudioSystem.STREAM_TTS; /** @hide The audio stream for navigation Gis */ public static final int STREAM_GIS = AudioSystem.STREAM_GIS; /** @hide The audio stream for navigation Gis */ public static final int STREAM_BACKCAR = AudioSystem.STREAM_BACKCAR;/** @hide The audio stream for voice recognition */ public static final int STREAM_VOICE_RECO = AudioSystem.STREAM_VOICE_RECO; public AudioManager(Context context) { setContext(context); mUseVolumeKeySounds = getContext().getResources().getBoolean( com.android.internal.R.bool.config_useVolumeKeySounds); mUseFixedVolume = getContext().getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); } private static IAudioService getService() { if (sService != null) { return sService; } IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); sService = IAudioService.Stub.asInterface(b); return sService; } /** * Sets the volume index for a particular stream. * This method has no effect if the device implements a fixed volume policy * as indicated by {@link #isVolumeFixed()}. * @param streamType The stream whose volume index should be set. * @param index The volume index to set. See * {@link #getStreamMaxVolume(int)} for the largest valid value. * @param flags One or more flags. * @see #getStreamMaxVolume(int) * @see #getStreamVolume(int) * @see #isVolumeFixed() */ public void setStreamVolume(int streamType, int index, int flags) { if (AudioSystem.isNaviProcType(Binder.getCallingPid()) == AudioSystem.AUD_NAVIGATION_TYPE) { streamType = AudioManager.STREAM_GIS; } if (DEBUG) { Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); } Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); IAudioService service = getService(); try { service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamVolume", e); } }
Z:\workspace\MT3561\frameworks\base\services\core\java\com\android\server\audio\AudioService.java
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] { AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL AudioSystem.STREAM_SYSTEM, // STREAM_SYSTEM AudioSystem.STREAM_RING, // STREAM_RING AudioSystem.STREAM_MUSIC, // STREAM_MUSIC AudioSystem.STREAM_ALARM, // STREAM_ALARM AudioSystem.STREAM_RING, // STREAM_NOTIFICATION AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED AudioSystem.STREAM_RING, // STREAM_DTMF AudioSystem.STREAM_MUSIC, // STREAM_TTS AudioSystem.STREAM_GIS, // STREAM_GIS AudioSystem.STREAM_BACKCAR, // STREAM_BACKCAR AudioSystem.STREAM_VOICE_RECO,// STREAM_VOICE_RECO }; private int[] mStreamVolumeAlias; /** @see AudioManager#setStreamVolume(int, int, int) */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } private void setStreamVolume(int streamType, int index, int flags, String callingPackage, String caller, int uid) { Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); if (mUseFixedVolume) { return; } ensureValidStreamType(streamType); int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamType); int oldIndex; // skip a2dp absolute volume control request when the device // is not an a2dp device if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } // If we are being called by the system (e.g. hardware keys) check for current user // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); } if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } synchronized (mSafeMediaVolumeState) { // reset any pending volume command mPendingVolumeCommand = null; oldIndex = streamState.getIndex(device); index = rescaleIndex(index * 10, streamType, streamTypeAlias); if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { synchronized (mA2dpAvrcpLock) { if (mA2dp != null && mAvrcpAbsVolSupported) { mA2dp.setAvrcpAbsoluteVolume(index / 10); } } } if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags); } flags &= ~AudioManager.FLAG_FIXED_VOLUME; if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // volume is either 0 or max allowed for fixed volume devices if (index != 0) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { index = mSafeMediaVolumeIndex; } else { index = streamState.getMaxIndex(); } } } if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { mVolumeController.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); Log.d(TAG, "checkSafeMediaVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); } else { Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); onSetStreamVolume(streamType, index, flags, device, caller); index = mStreamStates[streamType].getIndex(device); } } sendVolumeUpdate(streamType, oldIndex, index, flags); }
private void onSetStreamVolume(int streamType, int index, int flags, int device, String caller) { final int stream = mStreamVolumeAlias[streamType]; Log.d(TAG, "onSetStreamVolume: StreamType = " + streamType + ", index = " + index + ", flags = " + flags); setStreamVolumeInt(stream, index, device, false, caller); // setting volume on ui sounds stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (stream == getUiSoundsStreamType())) { int newRingerMode; if (index == 0) { newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE : mVolumePolicy.volumeDownToEnterSilent ? AudioManager.RINGER_MODE_SILENT : AudioManager.RINGER_MODE_NORMAL; } else { newRingerMode = AudioManager.RINGER_MODE_NORMAL; } setRingerMode(newRingerMode, TAG + ".onSetStreamVolume", false /*external*/); } // setting non-zero volume for a muted stream unmutes the stream and vice versa //mStreamStates[stream].mute(index == 0); } /** * Sets the stream state's index, and posts a message to set system volume. * This will not call out to the UI. Assumes a valid stream type. * * @param streamType Type of the stream * @param index Desired volume index of the stream * @param device the device whose volume must be changed * @param force If true, set the volume even if the desired volume is same * as the current volume. */ private void setStreamVolumeInt(int streamType, int index, int device, boolean force, String caller) { VolumeStreamState streamState = mStreamStates[streamType]; Log.d(TAG, "setStreamVolumeInt: StreamType = " + streamType + ", index = " + index); if (streamState.muteCount() != 0) { Log.d(TAG, "setStreamVolumeInt: StreamType = " + streamType + ", mutecount = " + streamState.muteCount()); streamState.setIndex(index, device, caller); sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, device, 0, streamState, PERSIST_DELAY); } else { Log.d(TAG, "setStreamVolumeInt: StreamType = " + streamType + ", mutecount = 0"); if (streamState.setIndex(index, device, caller) || force) { // Post message to set system volume (it in turn will post a message // to persist). sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } } }
private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { if (existingMsgPolicy == SENDMSG_REPLACE) { handler.removeMessages(msg); } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { Log.d(TAG, "sendMsg: Msg " + msg + " existed!"); return; } synchronized (mLastDeviceConnectMsgTime) { long time = SystemClock.uptimeMillis() + delay; handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) { mLastDeviceConnectMsgTime = time; } } }
/** Handles internal volume messages in separate volume thread. */ private class AudioHandler extends Handler { private void setDeviceVolume(VolumeStreamState streamState, int device) { synchronized (VolumeStreamState.class) { // Apply volume streamState.applyDeviceVolume_syncVSS(device); // Apply change to all streams using this one as alias int numStreamTypes = AudioSystem.getNumStreamTypes(); for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && mStreamVolumeAlias[streamType] == streamState.mStreamType) { // Make sure volume is also maxed out on A2DP device for aliased stream // that may have a different device selected int streamDevice = getDeviceForStream(streamType); if ((device != streamDevice) && mAvrcpAbsVolSupported && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { mStreamStates[streamType].applyDeviceVolume_syncVSS(device); } mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice); } } } // Post a persist volume msg sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, SENDMSG_QUEUE, device, 0, streamState, PERSIST_DELAY); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_DEVICE_VOLUME: setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1); break; ...... } }
// must be called while synchronized VolumeStreamState.class public void applyDeviceVolume_syncVSS(int device) { int index; if (mIsMuted) { index = 0; } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) || ((device & mFullVolumeDevices) != 0)) { index = (mIndexMax + 5)/10; } else { index = (getIndex(device) + 5)/10; } Log.d(TAG, "applyDeviceVolume_syncVSS" + "mStreamType:" + mStreamType + ",device:" + device); AudioSystem.setStreamVolumeIndex(mStreamType, index, device); }
// UI update and Broadcast Intent private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { streamType = mStreamVolumeAlias[streamType]; if (streamType == AudioSystem.STREAM_MUSIC) { flags = updateFlagsForSystemAudio(flags); } mVolumeController.postVolumeChanged(streamType, flags); /// M: When ringer volume changed, notify AudioProfile oldIndex = (oldIndex + 5) / 10; index = (index + 5) / 10; if (DEBUG_VOL) Log.d(TAG, "sendVolumeUpdate: StreamType = " + streamType + ", oldIndex = " + oldIndex + ", newIndex = " + index); if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) { notifyRingerVolumeChanged(oldIndex, index, null); } }
Z:\workspace\MT3561\frameworks\base\media\java\android\media\AudioSystem.java
public static native int initStreamVolume(int stream, int indexMin, int indexMax); public static native int setStreamVolumeIndex(int stream, int index, int device); public static native int getStreamVolumeIndex(int stream, int device); public static native int setMasterVolume(float value); public static native float getMasterVolume(); public static native int setMasterMute(boolean mute); public static native boolean getMasterMute();
Z:\workspace\MT3561\frameworks\base\core\jni\android_media_AudioSystem.cpp
static jintandroid_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env, jobject thiz, jint stream, jint index, jint device){ return (jint) check_AudioSystem_Command( AudioSystem::setStreamVolumeIndex(static_cast (stream), index, (audio_devices_t)device));}
Z:\workspace\MT3561\frameworks\av\media\libmedia\AudioSystem.cpp
status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t device){ const sp& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return PERMISSION_DENIED; return aps->setStreamVolumeIndex(stream, index, device);}// establish binder interface to AudioPolicy serviceconst sp AudioSystem::get_audio_policy_service(){ sp ap; sp apc; { Mutex::Autolock _l(gLockAPS); if (gAudioPolicyService == 0) { sp sm = defaultServiceManager(); sp binder; do { binder = sm->getService(String16("media.audio_policy")); if (binder != 0) break; ALOGW("AudioPolicyService not published, waiting..."); usleep(500000); // 0.5 s } while (true); if (gAudioPolicyServiceClient == NULL) { gAudioPolicyServiceClient = new AudioPolicyServiceClient(); } binder->linkToDeath(gAudioPolicyServiceClient); gAudioPolicyService = interface_cast(binder); LOG_ALWAYS_FATAL_IF(gAudioPolicyService == 0); apc = gAudioPolicyServiceClient; } ap = gAudioPolicyService; } if (apc != 0) { ap->registerClient(apc); } return ap;}
class AudioPolicyServiceClient: public IBinder::DeathRecipient, public BnAudioPolicyServiceClient { public: AudioPolicyServiceClient() { } int addAudioPortCallback(const sp& callback); int removeAudioPortCallback(const sp& callback); // DeathRecipient virtual void binderDied(const wp& who); // IAudioPolicyServiceClient virtual void onAudioPortListUpdate(); virtual void onAudioPatchListUpdate(); virtual void onDynamicPolicyMixStateUpdate(String8 regId, int32_t state); private: Mutex mLock; Vector mAudioPortCallbacks; };
Z:\workspace\MT3561\frameworks\av\services\audiopolicy\service\AudioPolicyService.cpp
void AudioPolicyService::onFirstRef(){ char value[PROPERTY_VALUE_MAX]; const struct hw_module_t *module; int forced_val; int rc; { Mutex::Autolock _l(mLock); // start tone playback thread mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this); // start audio commands thread mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this); // start output activity command thread mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);#ifdef USE_LEGACY_AUDIO_POLICY ALOGI("AudioPolicyService CSTOR in legacy mode"); /* instantiate the audio policy manager */ rc = hw_get_module(AUDIO_POLICY_HARDWARE_MODULE_ID, &module); if (rc) { return; } rc = audio_policy_dev_open(module, &mpAudioPolicyDev); ALOGE_IF(rc, "couldn't open audio policy device (%s)", strerror(-rc)); if (rc) { return; } rc = mpAudioPolicyDev->create_audio_policy(mpAudioPolicyDev, &aps_ops, this, /**/&mpAudioPolicy); ALOGE_IF(rc, "couldn't create audio policy (%s)", strerror(-rc)); if (rc) { return; } rc = mpAudioPolicy->init_check(mpAudioPolicy); ALOGE_IF(rc, "couldn't init_check the audio policy (%s)", strerror(-rc)); if (rc) { return; } ALOGI("Loaded audio policy from %s (%s)", module->name, module->id);#else ALOGI("AudioPolicyService CSTOR in new mode"); mAudioPolicyClient = new AudioPolicyClient(this); mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);#endif } // load audio processing modules spaudioPolicyEffects = new AudioPolicyEffects(); { Mutex::Autolock _l(mLock); mAudioPolicyEffects = audioPolicyEffects; }#ifdef MTK_AUDIO mHeadsetDetect = new HeadsetDetect(this,&AudioEarphoneCallback); //earphone callback if(mHeadsetDetect) mHeadsetDetect->start();#endif}
Z:\workspace\MT3561\frameworks\av\services\audiopolicy\service\AudioPolicyInterfaceImpl.cpp
status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t device){ if (mAudioPolicyManager == NULL) { return NO_INIT; } if (!settingsAllowed()) { return PERMISSION_DENIED; } if (uint32_t(stream) >= AUDIO_STREAM_PUBLIC_CNT) { return BAD_VALUE; } Mutex::Autolock _l(mLock); return mAudioPolicyManager->setStreamVolumeIndex(stream, index, device);}
Z:\workspace\MT3561\frameworks\av\services\audiopolicy\manager\AudioPolicyFactory.cpp
namespace android {extern "C" AudioPolicyInterface* createAudioPolicyManager( AudioPolicyClientInterface *clientInterface){ return new AudioPolicyManager(clientInterface);}extern "C" void destroyAudioPolicyManager(AudioPolicyInterface *interface){ delete interface;}}; // namespace android
Z:\workspace\MT3561\frameworks\av\services\audiopolicy\managerdefault\AudioPolicyManager.cpp
status_t AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t device){#ifdef MTK_AUDIO ALOGD("setStreamVolumeIndex stream = %d index = %d device = 0x%x",stream,index,device); if (stream == AUDIO_STREAM_VOICE_CALL) { if (mNeedRemapVoiceVolumeIndex == true) { index--; ALOGV("Correct stream = %d index = %d device = 0x%x",stream,index,device); } }#endif if ((index < mStreams.valueFor(stream).getVolumeIndexMin()) || (index > mStreams.valueFor(stream).getVolumeIndexMax())) { return BAD_VALUE; } if (!audio_is_output_device(device)) { return BAD_VALUE; } // Force max volume if stream cannot be muted if (!mStreams.canBeMuted(stream)) index = mStreams.valueFor(stream).getVolumeIndexMax(); ALOGV("setStreamVolumeIndex() stream %d, device %04x, index %d", stream, device, index); // if device is AUDIO_DEVICE_OUT_DEFAULT set default value and // clear all device specific values if (device == AUDIO_DEVICE_OUT_DEFAULT) { mStreams.clearCurrentVolumeIndex(stream); } mStreams.addCurrentVolumeIndex(stream, device, index);// ywjreturn mAutoPolicy->intercept_setStreamVolumeIndex(stream, index, device); // update volume on all outputs whose current device is also selected by the same // strategy as the device specified by the caller audio_devices_t strategyDevice = getDeviceForStrategy(getStrategy(stream), true /*fromCache*/);#ifdef MTK_AUDIO#ifdef MTK_LOSSLESS_BT_SUPPORT if (stream == AUDIO_STREAM_MUSIC) { //adjust volume will affect LosslessBT behavior ALOGD("LosslessBT audio set %s %d", AudioParameter::keyLosslessBTVolumeSatisfied, device); AudioParameter param = AudioParameter(); param.addInt(String8(AudioParameter::keyLosslessBTVolumeSatisfied), (index == mStreams.valueFor(stream).getVolumeIndexMax()) ? 1 : 0); mpClientInterface->setParameters(0, param.toString()); }#endif#endif //FIXME: AUDIO_STREAM_ACCESSIBILITY volume follows AUDIO_STREAM_MUSIC for now audio_devices_t accessibilityDevice = AUDIO_DEVICE_NONE; if (stream == AUDIO_STREAM_MUSIC) { mStreams.addCurrentVolumeIndex(AUDIO_STREAM_ACCESSIBILITY, device, index); accessibilityDevice = getDeviceForStrategy(STRATEGY_ACCESSIBILITY, true /*fromCache*/); } if ((device != AUDIO_DEVICE_OUT_DEFAULT) && (device & (strategyDevice | accessibilityDevice)) == 0) { return NO_ERROR; } status_t status = NO_ERROR; for (size_t i = 0; i < mOutputs.size(); i++) { sp desc = mOutputs.valueAt(i); audio_devices_t curDevice = Volume::getDeviceForVolume(desc->device()); if ((device == AUDIO_DEVICE_OUT_DEFAULT) || ((curDevice & strategyDevice) != 0)) { status_t volStatus = checkAndSetVolume(stream, index, desc, curDevice); if (volStatus != NO_ERROR) { status = volStatus; } } if ((device == AUDIO_DEVICE_OUT_DEFAULT) || ((curDevice & accessibilityDevice) != 0)) { status_t volStatus = checkAndSetVolume(AUDIO_STREAM_ACCESSIBILITY, index, desc, curDevice); } } return status;}
Z:\workspace\MT3561\frameworks\av\services\audiopolicy\managerdefault\automanager\AutoPolicy.h
// policy auto type audioclass AutoPolicy {public: AutoPolicy(const SwAudioOutputCollection &output, AudioPolicyClientInterface *client); virtual ~AutoPolicy(); status_t intercept_setStreamVolumeIndex(audio_stream_type_t stream, int index, audio_devices_t device); status_t intercept_checkAndSetVolume(audio_stream_type_t stream, int index, const sp& outputDesc, audio_devices_t device); bool intercept_startOutput(audio_io_handle_t output, audio_stream_type_t stream, audio_session_t session); bool intercept_stopOutput(audio_io_handle_t output, audio_stream_type_t stream, audio_session_t session); void setAutoAudioAction(auto_audio_action_t cmd, int value); ......private: SwAudioOutputCollection mOutputs; AudioPolicyClientInterface *mpClientInterface; // audio policy client interface float mMediaMixVolume;float mNaviMixVolume; int mNaviStartOutputCount;// AUDIO_STREAM_NAVI int mVoiceCallStartOutputCount; // AUDIO_STREAM_VOICE_CALL int mVoiceRecoStartOutputCount; // AUDIO_STREAM_VOICE_RECO bool mIsVoiceRecognizeMode;bool mIsBackcarMode;};
更多相关文章
- 代码中设置drawableleft
- android 3.0 隐藏 系统标题栏
- Android开发中activity切换动画的实现
- Android(安卓)学习 笔记_05. 文件下载
- Android中直播视频技术探究之—摄像头Camera视频源数据采集解析
- 技术博客汇总
- android 2.3 wifi (一)
- AndRoid Notification的清空和修改
- Android中的Chronometer