Skip to content

Commit

Permalink
Mute audio when volume is zero on Android.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wjia@chromium.org committed Dec 5, 2013
1 parent e5b0c82 commit da6ed34
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 5 deletions.
2 changes: 2 additions & 0 deletions build/android/findbugs_filter/findbugs_known_bugs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 27 additions & 1 deletion media/audio/android/audio_manager_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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(),
Expand Down
16 changes: 16 additions & 0 deletions media/audio/android/audio_manager_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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();

Expand All @@ -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);
};

Expand Down
9 changes: 8 additions & 1 deletion media/audio/android/opensles_output.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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_]);
Expand Down
10 changes: 10 additions & 0 deletions media/audio/android/opensles_output.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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_;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
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;
Expand Down Expand Up @@ -106,6 +113,7 @@ private AudioDeviceName(int id, String name) {

private final AudioManager mAudioManager;
private final Context mContext;
private final long mNativeAudioManagerAndroid;

private boolean mHasBluetoothPermission = false;
private boolean mIsInitialized = false;
Expand All @@ -121,18 +129,28 @@ private AudioDeviceName(int id, String name) {
// 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();
}

/**
Expand Down Expand Up @@ -182,6 +200,16 @@ public void init() {
initBluetooth();

mIsInitialized = true;

mSettingsObserverThread = new SettingsObserverThread();
mSettingsObserverThread.start();
synchronized(mSettingsObserverLock) {
try {
mSettingsObserverLock.wait();
} catch (InterruptedException e) {
Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage());
}
}
}

/**
Expand All @@ -193,6 +221,14 @@ public void close() {
if (!mIsInitialized)
return;

if (mSettingsObserverThread != null ) {
mSettingsObserverThread = null;
}
if (mSettingsObserver != null) {
mContentResolver.unregisterContentObserver(mSettingsObserver);
mSettingsObserver = null;
}

unregisterForWiredHeadsetIntentBroadcast();

// Restore previously stored audio states.
Expand Down Expand Up @@ -575,4 +611,40 @@ private void logd(String msg) {
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();
}
}
}

0 comments on commit da6ed34

Please sign in to comment.