summaryrefslogtreecommitdiffstats
path: root/chrome/browser/notifications
diff options
context:
space:
mode:
authormukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-31 21:36:46 +0000
committermukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-31 21:36:46 +0000
commit87f7ab4c5f2f608b1abbab78fd9d524c51e79d83 (patch)
treef2deaab641d079e96e1c752ad98eb0c72f6a59ce /chrome/browser/notifications
parent914816dfb279c4b9f07235f26ff511a3598ffe8f (diff)
downloadchromium_src-87f7ab4c5f2f608b1abbab78fd9d524c51e79d83.zip
chromium_src-87f7ab4c5f2f608b1abbab78fd9d524c51e79d83.tar.gz
chromium_src-87f7ab4c5f2f608b1abbab78fd9d524c51e79d83.tar.bz2
Introduces NotificationAudioManager.
This CL does the following things: - allow to access media::AudioManager from chrome/browser - .wav format parser - NotificationAudioManager class, which manages playing sound by a resource id (representing wav file format data) BUG=164248 TEST=with crrev.com/12663010, a notification will cause a sound Review URL: https://chromiumcodereview.appspot.com/12287003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@203482 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/notifications')
-rw-r--r--chrome/browser/notifications/notification_audio_controller.cc383
-rw-r--r--chrome/browser/notifications/notification_audio_controller.h73
-rw-r--r--chrome/browser/notifications/notification_audio_controller_unittest.cc122
3 files changed, 578 insertions, 0 deletions
diff --git a/chrome/browser/notifications/notification_audio_controller.cc b/chrome/browser/notifications/notification_audio_controller.cc
new file mode 100644
index 0000000..213f9c4
--- /dev/null
+++ b/chrome/browser/notifications/notification_audio_controller.cc
@@ -0,0 +1,383 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/notifications/notification_audio_controller.h"
+
+#include <cstring>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/cancelable_callback.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sys_byteorder.h"
+#include "base/task_runner_util.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/audio_bus.h"
+#include "media/base/channel_layout.h"
+
+namespace {
+
+// The size of the header of a wav file. The header consists of 'RIFF', 4 bytes
+// of total data length, and 'WAVE'.
+const size_t kWavFileHeaderSize = 12;
+
+// The size of a chunk header in wav file format. A chunk header consists of a
+// tag ('fmt ' or 'data') and 4 bytes of chunk length.
+const size_t kChunkHeaderSize = 8;
+
+// The minimum size of 'fmt' chunk.
+const size_t kFmtChunkMinimumSize = 16;
+
+// The offsets of 'fmt' fields.
+const size_t kAudioFormatOffset = 0;
+const size_t kChunnelOffset = 2;
+const size_t kSampleRateOffset = 4;
+const size_t kBitsPerSampleOffset = 14;
+
+// Some constants for audio format.
+const int kAudioFormatPCM = 1;
+
+// The frame-per-buffer parameter for wav data format reader. Since wav format
+// doesn't have this parameter in its header, just choose some value.
+const int kFramesPerBuffer = 4096;
+
+///////////////////////////////////////////////////////////////////////////
+// WavAudioHandler
+//
+// This class provides the input from wav file format.
+// See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+class WavAudioHandler {
+ public:
+ explicit WavAudioHandler(const base::StringPiece& data);
+ virtual ~WavAudioHandler();
+
+ // Returns true when it plays to the end of the track.
+ bool AtEnd();
+
+ // Copies the audio data to |dest|.
+ void CopyTo(media::AudioBus* dest);
+
+ uint16 num_channels() const { return num_channels_; }
+ uint32 sample_rate() const { return sample_rate_; }
+ uint16 bits_per_sample() const { return bits_per_sample_; }
+
+ private:
+ // Parses a chunk of wav format data. Returns the length of the chunk.
+ int ParseSubChunk(const base::StringPiece& data);
+
+ // Parses the 'fmt' section chunk and stores |params_|.
+ void ParseFmtChunk(const base::StringPiece& data);
+
+ // Parses the 'data' section chunk and stores |data_|.
+ void ParseDataChunk(const base::StringPiece& data);
+
+ // Reads an integer from |data| with |offset|.
+ template<typename T> T ReadInt(const base::StringPiece& data, size_t offset);
+
+ base::StringPiece data_;
+ size_t cursor_;
+ uint16 num_channels_;
+ uint32 sample_rate_;
+ uint16 bits_per_sample_;
+
+ DISALLOW_COPY_AND_ASSIGN(WavAudioHandler);
+};
+
+WavAudioHandler::WavAudioHandler(const base::StringPiece& data)
+ : cursor_(0) {
+ if (data.size() < kWavFileHeaderSize)
+ return;
+
+ if (!data.starts_with("RIFF") || ::memcmp(data.data() + 8, "WAVE", 4) != 0)
+ return;
+
+ uint32 total_length =
+ std::min(ReadInt<uint32>(data, 4), static_cast<uint32>(data.size()));
+
+ uint32 offset = kWavFileHeaderSize;
+ while (offset < total_length)
+ offset += ParseSubChunk(data.substr(offset));
+}
+
+WavAudioHandler::~WavAudioHandler() {
+}
+
+bool WavAudioHandler::AtEnd() {
+ return data_.size() <= cursor_;
+}
+
+void WavAudioHandler::CopyTo(media::AudioBus* dest) {
+ DCHECK_EQ(dest->channels(), num_channels_);
+ if (cursor_ >= data_.size()) {
+ dest->Zero();
+ return;
+ }
+
+ const int bytes_per_sample = bits_per_sample_ / 8;
+ const int bytes_per_frame = num_channels_ * bytes_per_sample;
+ const int remaining_frames = (data_.size() - cursor_) / bytes_per_frame;
+ const int frames = std::min(dest->frames(), remaining_frames);
+ dest->FromInterleaved(data_.data() + cursor_, frames, bytes_per_sample);
+ cursor_ += frames * bytes_per_frame;
+ dest->ZeroFramesPartial(frames, dest->frames() - frames);
+}
+
+int WavAudioHandler::ParseSubChunk(const base::StringPiece& data) {
+ if (data.size() < kChunkHeaderSize)
+ return data.size();
+ uint32 chunk_length = ReadInt<uint32>(data, 4);
+
+ if (data.starts_with("fmt "))
+ ParseFmtChunk(data.substr(kChunkHeaderSize, chunk_length));
+ else if (data.starts_with("data"))
+ ParseDataChunk(data.substr(kChunkHeaderSize, chunk_length));
+ else
+ DLOG(WARNING) << "Unknown data chunk: " << data.substr(0, 4);
+ return chunk_length + kChunkHeaderSize;
+}
+
+void WavAudioHandler::ParseFmtChunk(const base::StringPiece& data) {
+ if (data.size() < kFmtChunkMinimumSize) {
+ DLOG(ERROR) << "data size " << data.size() << " is too short.";
+ return;
+ }
+
+ DCHECK_EQ(ReadInt<uint16>(data, kAudioFormatOffset), kAudioFormatPCM);
+
+ num_channels_ = ReadInt<uint16>(data, kChunnelOffset);
+ sample_rate_ = ReadInt<uint32>(data, kSampleRateOffset);
+ bits_per_sample_ = ReadInt<uint16>(data, kBitsPerSampleOffset);
+}
+
+void WavAudioHandler::ParseDataChunk(const base::StringPiece& data) {
+ data_ = data;
+}
+
+template<typename T> T WavAudioHandler::ReadInt(const base::StringPiece& data,
+ size_t offset) {
+ DCHECK_LE(offset + sizeof(T), data.size());
+ T result;
+ ::memcpy(&result, data.data() + offset, sizeof(T));
+#if !defined(ARCH_CPU_LITTLE_ENDIAN)
+ result = base::ByteSwap(result);
+#endif
+
+ return result;
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////
+// NotificationAudioController::AudioHandler
+//
+// This class connects a sound for a notification to the audio manager. It
+// will be released by its owner when the sound ends.
+class NotificationAudioController::AudioHandler
+ : public media::AudioOutputStream::AudioSourceCallback {
+ public:
+ typedef base::Callback<void(AudioHandler*)> OnFinishedCB;
+
+ AudioHandler(const OnFinishedCB& on_finished_callback,
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data);
+ virtual ~AudioHandler();
+
+ const std::string& notification_id() const { return notification_id_; }
+ const Profile* profile() const { return profile_; }
+
+ // Start playing the sound. Returns true when it's successfully scheduled.
+ bool StartPlayingSound();
+
+ // Stops the playing sound request.
+ void StopPlayingSound();
+
+ private:
+ // media::AudioOutputStream::AudioSourceCallback overrides:
+ virtual int OnMoreData(media::AudioBus* dest,
+ media::AudioBuffersState state) OVERRIDE;
+ virtual int OnMoreIOData(media::AudioBus* source,
+ media::AudioBus* dest,
+ media::AudioBuffersState state) OVERRIDE;
+ virtual void OnError(media::AudioOutputStream* stream) OVERRIDE;
+
+ OnFinishedCB on_finished_callback_;
+ base::CancelableClosure stop_playing_sound_;
+ std::string notification_id_;
+ const Profile* profile_;
+
+ media::AudioManager* audio_manager_;
+ WavAudioHandler wav_audio_;
+ media::AudioOutputStream* stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioHandler);
+};
+
+NotificationAudioController::AudioHandler::AudioHandler(
+ const OnFinishedCB& on_finished_callback,
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data)
+ : on_finished_callback_(on_finished_callback),
+ notification_id_(notification_id),
+ profile_(profile),
+ audio_manager_(media::AudioManager::Get()),
+ wav_audio_(data) {
+}
+
+NotificationAudioController::AudioHandler::~AudioHandler() {
+}
+
+bool NotificationAudioController::AudioHandler::StartPlayingSound() {
+ DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
+
+ media::AudioParameters params = media::AudioParameters(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::GuessChannelLayout(wav_audio_.num_channels()),
+ wav_audio_.num_channels(),
+ wav_audio_.sample_rate(),
+ wav_audio_.bits_per_sample(),
+ kFramesPerBuffer);
+
+ if (!params.IsValid())
+ return false;
+
+ stream_ = audio_manager_->MakeAudioOutputStreamProxy(params);
+ if (!stream_->Open()) {
+ DLOG(ERROR) << "Failed to open the output stream";
+ stream_->Close();
+ return false;
+ }
+
+ stop_playing_sound_.Reset(base::Bind(
+ &AudioHandler::StopPlayingSound, base::Unretained(this)));
+ stream_->Start(this);
+ return true;
+}
+
+// Stops actually the audio output stream.
+void NotificationAudioController::AudioHandler::StopPlayingSound() {
+ DCHECK(audio_manager_->GetMessageLoop()->BelongsToCurrentThread());
+ stop_playing_sound_.Cancel();
+
+ // Close deletes |stream| too.
+ stream_->Stop();
+ stream_->Close();
+ on_finished_callback_.Run(this);
+}
+
+int NotificationAudioController::AudioHandler::OnMoreData(
+ media::AudioBus* dest,
+ media::AudioBuffersState state) {
+ wav_audio_.CopyTo(dest);
+ if (wav_audio_.AtEnd()) {
+ audio_manager_->GetMessageLoop()->PostTask(
+ FROM_HERE, stop_playing_sound_.callback());
+ }
+ return dest->frames();
+}
+
+int NotificationAudioController::AudioHandler::OnMoreIOData(
+ media::AudioBus* source,
+ media::AudioBus* dest,
+ media::AudioBuffersState state) {
+ return OnMoreData(dest, state);
+}
+
+void NotificationAudioController::AudioHandler::OnError(
+ media::AudioOutputStream* stream) {
+}
+
+///////////////////////////////////////////////////////////////////////////
+// NotificationAudioController
+//
+
+NotificationAudioController::NotificationAudioController()
+ : message_loop_(media::AudioManager::Get()->GetMessageLoop()) {
+}
+
+NotificationAudioController::~NotificationAudioController() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+}
+
+void NotificationAudioController::RequestPlayNotificationSound(
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &NotificationAudioController::PlayNotificationSound,
+ base::Unretained(this),
+ notification_id,
+ base::Unretained(profile),
+ data));
+}
+
+void NotificationAudioController::RequestShutdown() {
+ message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &NotificationAudioController::Shutdown, base::Unretained(this)));
+}
+
+void NotificationAudioController::OnNotificationSoundFinished(
+ AudioHandler* audio_handler) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ std::vector<AudioHandler*>::iterator it =
+ std::find(audio_handlers_.begin(), audio_handlers_.end(), audio_handler);
+ DCHECK(it != audio_handlers_.end());
+ audio_handlers_.erase(it);
+}
+
+void NotificationAudioController::GetAudioHandlersSizeForTest(
+ const base::Callback<void(size_t)>& on_get) {
+ base::PostTaskAndReplyWithResult(
+ message_loop_.get(),
+ FROM_HERE,
+ base::Bind(&NotificationAudioController::GetAudioHandlersSizeCallback,
+ base::Unretained(this)),
+ on_get);
+}
+
+void NotificationAudioController::PlayNotificationSound(
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+
+ for (size_t i = 0; i < audio_handlers_.size(); ++i) {
+ if (audio_handlers_[i]->notification_id() == notification_id &&
+ audio_handlers_[i]->profile() == profile) {
+ audio_handlers_[i]->StopPlayingSound();
+ break;
+ }
+ }
+
+ scoped_ptr<AudioHandler> new_handler(new AudioHandler(
+ base::Bind(&NotificationAudioController::OnNotificationSoundFinished,
+ base::Unretained(this)),
+ notification_id,
+ profile,
+ data));
+ if (new_handler->StartPlayingSound())
+ audio_handlers_.push_back(new_handler.release());
+}
+
+void NotificationAudioController::Shutdown() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+
+ while (!audio_handlers_.empty())
+ audio_handlers_[0]->StopPlayingSound();
+
+ delete this;
+}
+
+size_t NotificationAudioController::GetAudioHandlersSizeCallback() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ return audio_handlers_.size();
+}
diff --git a/chrome/browser/notifications/notification_audio_controller.h b/chrome/browser/notifications/notification_audio_controller.h
new file mode 100644
index 0000000..fb370b3
--- /dev/null
+++ b/chrome/browser/notifications/notification_audio_controller.h
@@ -0,0 +1,73 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_AUDIO_CONTROLLER_H_
+#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_AUDIO_CONTROLLER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/string_piece.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+class Profile;
+
+// This class controls the sound playing for notifications. Note that this class
+// belongs to the audio thread and self-owned.
+class NotificationAudioController {
+ public:
+ NotificationAudioController();
+
+ // Requests the audio thread to play a sound for a notification. |data|
+ // specifies the raw audio data in wav file format. |notification_id|
+ // and |profile| represents the id of the notification for the sound.
+ // When this method is called during playing sound for the specified
+ // |notification_id|, it will stop playing the sound and start the sound for
+ // the newly specified |data|.
+ void RequestPlayNotificationSound(
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data);
+
+ // Request shutdown process in the audio thread. Delete itself when all
+ // processes have finished.
+ void RequestShutdown();
+
+ private:
+ class AudioHandler;
+ friend class NotificationAudioControllerTest;
+
+ ~NotificationAudioController();
+
+ // Request the current number of audio handlers to the audio thread. |on_get|
+ // will be called with the result when finished.
+ void GetAudioHandlersSizeForTest(const base::Callback<void(size_t)>& on_get);
+
+ // Actually start playing the notification sound in the audio thread.
+ void PlayNotificationSound(
+ const std::string& notification_id,
+ const Profile* profile,
+ const base::StringPiece& data);
+
+ // Removes all instances in |audio_handlers_|.
+ void Shutdown();
+
+ // Gets the current size of |audio_handlers_| in the audio thread.
+ size_t GetAudioHandlersSizeCallback();
+
+ // Called when the sound for |audio_handler| has finished.
+ void OnNotificationSoundFinished(AudioHandler* audio_handler);
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+ ScopedVector<AudioHandler> audio_handlers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationAudioController);
+};
+
+#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_AUDIO_CONTROLLER_H_
diff --git a/chrome/browser/notifications/notification_audio_controller_unittest.cc b/chrome/browser/notifications/notification_audio_controller_unittest.cc
new file mode 100644
index 0000000..2126722
--- /dev/null
+++ b/chrome/browser/notifications/notification_audio_controller_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/notifications/notification_audio_controller.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "chrome/test/base/testing_profile.h"
+#include "media/audio/audio_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Sample audio data
+const char kTestAudioData[] = "RIFF\x26\x00\x00\x00WAVEfmt \x10\x00\x00\x00"
+ "\x01\x00\x02\x00\x80\xbb\x00\x00\x00\x77\x01\x00\x02\x00\x10\x00"
+ "data\x04\x00\x00\x00\x01\x00\x01\x00";
+const char kInvalidAudioData[] = "invalid audio data";
+const char kDummyNotificationId[] = "dummy_id";
+const char kDummyNotificationId2[] = "dummy_id2";
+
+void CopyResultAndQuit(
+ base::RunLoop* run_loop, size_t *result_ptr, size_t result) {
+ *result_ptr = result;
+ run_loop->Quit();
+}
+
+} // namespace
+
+class NotificationAudioControllerTest : public testing::Test {
+ public:
+ NotificationAudioControllerTest() {
+ audio_manager_.reset(media::AudioManager::Create());
+ notification_audio_controller_ = new NotificationAudioController();
+ }
+
+ virtual ~NotificationAudioControllerTest() {
+ notification_audio_controller_->RequestShutdown();
+ audio_manager_.reset();
+ }
+
+ protected:
+ NotificationAudioController* notification_audio_controller() {
+ return notification_audio_controller_;
+ }
+ Profile* profile() { return &profile_; }
+
+ size_t GetHandlersSize() {
+ base::RunLoop run_loop;
+ size_t result = 0;
+ notification_audio_controller_->GetAudioHandlersSizeForTest(
+ base::Bind(&CopyResultAndQuit, &run_loop, &result));
+ run_loop.Run();
+ return result;
+ }
+
+ private:
+ base::MessageLoopForUI message_loop_;
+ TestingProfile profile_;
+ NotificationAudioController* notification_audio_controller_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationAudioControllerTest);
+};
+
+TEST_F(NotificationAudioControllerTest, PlaySound) {
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ // Still playing the sound, not empty yet.
+ EXPECT_EQ(1u, GetHandlersSize());
+}
+
+TEST_F(NotificationAudioControllerTest, PlayInvalidSound) {
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kInvalidAudioData, arraysize(kInvalidAudioData)));
+ EXPECT_EQ(0u, GetHandlersSize());
+}
+
+TEST_F(NotificationAudioControllerTest, PlayTwoSounds) {
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId2,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ EXPECT_EQ(2u, GetHandlersSize());
+}
+
+TEST_F(NotificationAudioControllerTest, PlaySoundTwice) {
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ EXPECT_EQ(1u, GetHandlersSize());
+}
+
+TEST_F(NotificationAudioControllerTest, MultiProfiles) {
+ TestingProfile profile2;
+
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ profile(),
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ notification_audio_controller()->RequestPlayNotificationSound(
+ kDummyNotificationId,
+ &profile2,
+ base::StringPiece(kTestAudioData, arraysize(kTestAudioData)));
+ EXPECT_EQ(2u, GetHandlersSize());
+}