diff options
author | wjia@chromium.org <wjia@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-05 19:09:18 +0000 |
---|---|---|
committer | wjia@chromium.org <wjia@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-05 19:09:18 +0000 |
commit | da6ed3413eaeeea934f6e34c455cf9364c8c3a01 (patch) | |
tree | 6a935abd3c06c0ce27a60033311b5838be53276d | |
parent | e5b0c823cc168fef0c39a0f70647f81fa8e2686f (diff) | |
download | chromium_src-da6ed3413eaeeea934f6e34c455cf9364c8c3a01.zip chromium_src-da6ed3413eaeeea934f6e34c455cf9364c8c3a01.tar.gz chromium_src-da6ed3413eaeeea934f6e34c455cf9364c8c3a01.tar.bz2 |
Mute audio when volume is zero on Android.
We are using Communication mode for audio backend on Android. By design, the platform voice
volume never goes to zero. This patch uses ContentObserver to listen to volume change. When
volume is set to zero, AudioManagerAndroid will mute all its output streams.
BUG=263399
R=henrika@chromium.org, qinmin@chromium.org, tommi@chromium.org
Review URL: https://codereview.chromium.org/93233003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239007 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | build/android/findbugs_filter/findbugs_known_bugs.txt | 2 | ||||
-rw-r--r-- | media/audio/android/audio_manager_android.cc | 28 | ||||
-rw-r--r-- | media/audio/android/audio_manager_android.h | 16 | ||||
-rw-r--r-- | media/audio/android/opensles_output.cc | 9 | ||||
-rw-r--r-- | media/audio/android/opensles_output.h | 10 | ||||
-rw-r--r-- | media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java | 78 |
6 files changed, 138 insertions, 5 deletions
diff --git a/build/android/findbugs_filter/findbugs_known_bugs.txt b/build/android/findbugs_filter/findbugs_known_bugs.txt index 09e403f..5ac2ae0 100644 --- a/build/android/findbugs_filter/findbugs_known_bugs.txt +++ b/build/android/findbugs_filter/findbugs_known_bugs.txt @@ -26,6 +26,8 @@ M D SF: Switch statement found in org.chromium.chrome.browser.database.SQLiteCur M D SF: Switch statement found in org.chromium.content.browser.third_party.GestureDetector.onTouchEvent(MotionEvent) where default case is missing At GestureDetector.java M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getBooleanValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setBooleanValue(boolean) is synchronized At JavaBridgeReturnValuesTest.java M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getStringValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setStringValue(String) is synchronized At JavaBridgeReturnValuesTest.java +M M UW: Unconditional wait in org.chromium.media.AudioManagerAndroid.init() At AudioManagerAndroid.java +M M Wa: Wait not in loop in org.chromium.media.AudioManagerAndroid.init() At AudioManagerAndroid.java M V EI2: new org.chromium.chrome.browser.FindMatchRectsDetails(int, RectF[], RectF) may expose internal representation by storing an externally mutable object into FindMatchRectsDetails.rects At FindMatchRectsDetails.java M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setFavicon(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mFavicon At ChromeBrowserProvider.java M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setThumbnail(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mThumbnail At ChromeBrowserProvider.java diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc index 2e16a1f..b588768 100644 --- a/media/audio/android/audio_manager_android.cc +++ b/media/audio/android/audio_manager_android.cc @@ -53,7 +53,8 @@ AudioManagerAndroid::AudioManagerAndroid(AudioLogFactory* audio_log_factory) j_audio_manager_.Reset( Java_AudioManagerAndroid_createAudioManagerAndroid( base::android::AttachCurrentThread(), - base::android::GetApplicationContext())); + base::android::GetApplicationContext(), + reinterpret_cast<intptr_t>(this))); Init(); } @@ -126,6 +127,12 @@ AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream( if (stream && output_stream_count() == 1) { SetAudioMode(kAudioModeInCommunication); } + + { + base::AutoLock lock(streams_lock_); + streams_.insert(static_cast<OpenSLESOutputStream*>(stream)); + } + return stream; } @@ -141,6 +148,8 @@ void AudioManagerAndroid::ReleaseOutputStream(AudioOutputStream* stream) { if (!output_stream_count()) { SetAudioMode(kAudioModeNormal); } + base::AutoLock lock(streams_lock_); + streams_.erase(static_cast<OpenSLESOutputStream*>(stream)); } void AudioManagerAndroid::ReleaseInputStream(AudioInputStream* stream) { @@ -241,6 +250,23 @@ void AudioManagerAndroid::Close() { j_audio_manager_.obj()); } +void AudioManagerAndroid::SetMute(JNIEnv* env, jobject obj, jboolean muted) { + GetMessageLoop()->PostTask( + FROM_HERE, + base::Bind( + &AudioManagerAndroid::DoSetMuteOnAudioThread, + base::Unretained(this), + muted)); +} + +void AudioManagerAndroid::DoSetMuteOnAudioThread(bool muted) { + base::AutoLock lock(streams_lock_); + for (OutputStreams::iterator it = streams_.begin(); + it != streams_.end(); ++it) { + (*it)->SetMute(muted); + } +} + void AudioManagerAndroid::SetAudioMode(int mode) { Java_AudioManagerAndroid_setMode( base::android::AttachCurrentThread(), diff --git a/media/audio/android/audio_manager_android.h b/media/audio/android/audio_manager_android.h index 59c830d..d9744ff 100644 --- a/media/audio/android/audio_manager_android.h +++ b/media/audio/android/audio_manager_android.h @@ -5,12 +5,17 @@ #ifndef MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_ #define MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_ +#include <set> + #include "base/android/jni_android.h" #include "base/gtest_prod_util.h" +#include "base/synchronization/lock.h" #include "media/audio/audio_manager_base.h" namespace media { +class OpenSLESOutputStream; + // Android implemention of AudioManager. class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { public: @@ -52,6 +57,8 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { static bool RegisterAudioManager(JNIEnv* env); + void SetMute(JNIEnv* env, jobject obj, jboolean muted); + protected: virtual ~AudioManagerAndroid(); @@ -69,12 +76,21 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { int GetAudioLowLatencyOutputFrameSize(); int GetOptimalOutputFrameSize(int sample_rate, int channels); + void DoSetMuteOnAudioThread(bool muted); + // Allow the AudioAndroidTest to access private methods. FRIEND_TEST_ALL_PREFIXES(AudioAndroidTest, IsAudioLowLatencySupported); // Java AudioManager instance. base::android::ScopedJavaGlobalRef<jobject> j_audio_manager_; + typedef std::set<OpenSLESOutputStream*> OutputStreams; + OutputStreams streams_; + // TODO(wjia): remove this lock once unit test modules are fixed to call + // AudioManager::MakeAudioOutputStream on the audio thread. For now, this + // lock is used to guard access to |streams_|. + base::Lock streams_lock_; + DISALLOW_COPY_AND_ASSIGN(AudioManagerAndroid); }; diff --git a/media/audio/android/opensles_output.cc b/media/audio/android/opensles_output.cc index 5643f83..77e6778 100644 --- a/media/audio/android/opensles_output.cc +++ b/media/audio/android/opensles_output.cc @@ -28,6 +28,7 @@ OpenSLESOutputStream::OpenSLESOutputStream(AudioManagerAndroid* manager, active_buffer_index_(0), buffer_size_bytes_(0), started_(false), + muted_(false), volume_(1.0) { DVLOG(2) << "OpenSLESOutputStream::OpenSLESOutputStream()"; format_.formatType = SL_DATAFORMAT_PCM; @@ -172,6 +173,12 @@ void OpenSLESOutputStream::GetVolume(double* volume) { *volume = static_cast<double>(volume_); } +void OpenSLESOutputStream::SetMute(bool muted) { + DVLOG(2) << "OpenSLESOutputStream::SetMute(" << muted << ")"; + DCHECK(thread_checker_.CalledOnValidThread()); + muted_ = muted; +} + bool OpenSLESOutputStream::CreatePlayer() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!engine_object_.Get()); @@ -324,7 +331,7 @@ void OpenSLESOutputStream::FillBufferQueueNoLock() { // Note: If the internal representation ever changes from 16-bit PCM to // raw float, the data must be clipped and sanitized since it may come // from an untrusted source such as NaCl. - audio_bus_->Scale(volume_); + audio_bus_->Scale(muted_ ? 0.0f : volume_); audio_bus_->ToInterleaved(frames_filled, format_.bitsPerSample / 8, audio_data_[active_buffer_index_]); diff --git a/media/audio/android/opensles_output.h b/media/audio/android/opensles_output.h index 7232d5d..623b019 100644 --- a/media/audio/android/opensles_output.h +++ b/media/audio/android/opensles_output.h @@ -40,6 +40,10 @@ class OpenSLESOutputStream : public AudioOutputStream { virtual void SetVolume(double volume) OVERRIDE; virtual void GetVolume(double* volume) OVERRIDE; + // Set the value of |muted_|. It does not affect |volume_| which can be + // got by calling GetVolume(). See comments for |muted_| below. + void SetMute(bool muted); + private: bool CreatePlayer(); @@ -96,6 +100,12 @@ class OpenSLESOutputStream : public AudioOutputStream { bool started_; + // Volume control coming from hardware. It overrides |volume_| when it's + // true. Otherwise, use |volume_| for scaling. + // This is needed because platform voice volume never goes to zero in + // COMMUNICATION mode on Android. + bool muted_; + // Volume level from 0 to 1. float volume_; diff --git a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java index 1a9cb0a..0c37c0a 100644 --- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java +++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java @@ -7,16 +7,23 @@ package org.chromium.media; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.AudioTrack; +import android.net.Uri; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.os.Process; +import android.provider.Settings; +import android.provider.Settings.System; import android.util.Log; import java.util.ArrayList; @@ -106,6 +113,7 @@ class AudioManagerAndroid { private final AudioManager mAudioManager; private final Context mContext; + private final long mNativeAudioManagerAndroid; private boolean mHasBluetoothPermission = false; private boolean mIsInitialized = false; @@ -121,18 +129,28 @@ class AudioManagerAndroid { // Contains a list of currently available audio devices. private boolean[] mAudioDevices = new boolean[DEVICE_COUNT]; + private final ContentResolver mContentResolver; + private SettingsObserver mSettingsObserver = null; + private SettingsObserverThread mSettingsObserverThread = null; + private int mCurrentVolume; + private final Object mSettingsObserverLock = new Object(); + // Broadcast receiver for wired headset intent broadcasts. private BroadcastReceiver mWiredHeadsetReceiver; /** Construction */ @CalledByNative - private static AudioManagerAndroid createAudioManagerAndroid(Context context) { - return new AudioManagerAndroid(context); + private static AudioManagerAndroid createAudioManagerAndroid( + Context context, + long nativeAudioManagerAndroid) { + return new AudioManagerAndroid(context, nativeAudioManagerAndroid); } - private AudioManagerAndroid(Context context) { + private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) { mContext = context; + mNativeAudioManagerAndroid = nativeAudioManagerAndroid; mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); + mContentResolver = mContext.getContentResolver(); } /** @@ -182,6 +200,16 @@ class AudioManagerAndroid { initBluetooth(); mIsInitialized = true; + + mSettingsObserverThread = new SettingsObserverThread(); + mSettingsObserverThread.start(); + synchronized(mSettingsObserverLock) { + try { + mSettingsObserverLock.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage()); + } + } } /** @@ -193,6 +221,14 @@ class AudioManagerAndroid { if (!mIsInitialized) return; + if (mSettingsObserverThread != null ) { + mSettingsObserverThread = null; + } + if (mSettingsObserver != null) { + mContentResolver.unregisterContentObserver(mSettingsObserver); + mSettingsObserver = null; + } + unregisterForWiredHeadsetIntentBroadcast(); // Restore previously stored audio states. @@ -575,4 +611,40 @@ class AudioManagerAndroid { private void loge(String msg) { Log.e(TAG, msg); } + + private class SettingsObserver extends ContentObserver { + SettingsObserver() { + super(new Handler()); + mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL); + nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); + } + } + + private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); + + private class SettingsObserverThread extends Thread { + SettingsObserverThread() { + super("SettinsObserver"); + } + + @Override + public void run() { + // Set this thread up so the handler will work on it. + Looper.prepare(); + + synchronized(mSettingsObserverLock) { + mSettingsObserver = new SettingsObserver(); + mSettingsObserverLock.notify(); + } + + // Listen for volume change. + Looper.loop(); + } + } } |