diff options
-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(); + } + } } |