| Index: content/browser/speech/speech_recognizer_impl.cc
|
| diff --git a/content/browser/speech/speech_recognizer_impl.cc b/content/browser/speech/speech_recognizer_impl.cc
|
| index 8824bf0dd92c52061aa2762457ad89dc22e15c67..76b8b305584e3aa53247d5d11a694d711a0806f0 100644
|
| --- a/content/browser/speech/speech_recognizer_impl.cc
|
| +++ b/content/browser/speech/speech_recognizer_impl.cc
|
| @@ -6,11 +6,15 @@
|
|
|
| #include "base/basictypes.h"
|
| #include "base/bind.h"
|
| +#include "base/memory/shared_memory.h"
|
| +#include "base/process/process_handle.h"
|
| +#include "base/threading/thread.h"
|
| #include "base/time/time.h"
|
| #include "content/browser/browser_main_loop.h"
|
| #include "content/browser/media/media_internals.h"
|
| #include "content/browser/speech/audio_buffer.h"
|
| #include "content/browser/speech/google_one_shot_remote_engine.h"
|
| +#include "content/public/browser/browser_message_filter.h"
|
| #include "content/public/browser/speech_recognition_event_listener.h"
|
| #include "media/base/audio_converter.h"
|
| #include "net/url_request/url_request_context_getter.h"
|
| @@ -21,6 +25,7 @@
|
|
|
| using media::AudioBus;
|
| using media::AudioConverter;
|
| +using media::AudioInputBufferParameters;
|
| using media::AudioInputController;
|
| using media::AudioManager;
|
| using media::AudioParameters;
|
| @@ -41,6 +46,7 @@ class SpeechRecognizerImpl::OnDataConverter
|
| // Converts input audio |data| bus into an AudioChunk where the input format
|
| // is given by |input_parameters_| and the output format by
|
| // |output_parameters_|.
|
| + // Skips conversion on |audio_converter_| when input and output formats match.
|
| scoped_refptr<AudioChunk> Convert(const AudioBus* data);
|
|
|
| private:
|
| @@ -57,6 +63,7 @@ class SpeechRecognizerImpl::OnDataConverter
|
| const AudioParameters input_parameters_;
|
| const AudioParameters output_parameters_;
|
| bool waiting_for_input_;
|
| + bool need_conversion_;
|
| scoped_ptr<uint8[]> converted_data_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(OnDataConverter);
|
| @@ -124,6 +131,7 @@ SpeechRecognizerImpl::OnDataConverter::OnDataConverter(
|
| waiting_for_input_(false),
|
| converted_data_(new uint8[output_parameters_.GetBytesPerBuffer()]) {
|
| audio_converter_.AddInput(this);
|
| + need_conversion_ = !(input_parameters_ == output_parameters_);
|
| }
|
|
|
| SpeechRecognizerImpl::OnDataConverter::~OnDataConverter() {
|
| @@ -136,10 +144,15 @@ scoped_refptr<AudioChunk> SpeechRecognizerImpl::OnDataConverter::Convert(
|
| const AudioBus* data) {
|
| CHECK_EQ(data->frames(), input_parameters_.frames_per_buffer());
|
|
|
| - data->CopyTo(input_bus_.get());
|
| -
|
| - waiting_for_input_ = true;
|
| - audio_converter_.Convert(output_bus_.get());
|
| + // Do conversion if needed - only when |input_parameters_| differ from
|
| + // |output_parameters_|.
|
| + if (need_conversion_) {
|
| + data->CopyTo(input_bus_.get());
|
| + waiting_for_input_ = true;
|
| + audio_converter_.Convert(output_bus_.get());
|
| + } else {
|
| + data->CopyTo(output_bus_.get());
|
| + }
|
|
|
| output_bus_->ToInterleaved(
|
| output_bus_->frames(), output_parameters_.bits_per_sample() / 8,
|
| @@ -175,6 +188,8 @@ SpeechRecognizerImpl::SpeechRecognizerImpl(
|
| int session_id,
|
| bool continuous,
|
| bool provisional_results,
|
| + bool using_audio_track,
|
| + base::ProcessHandle peer_process_handle,
|
| SpeechRecognitionEngine* engine)
|
| : SpeechRecognizer(listener, session_id),
|
| recognition_engine_(engine),
|
| @@ -183,6 +198,8 @@ SpeechRecognizerImpl::SpeechRecognizerImpl(
|
| media::AudioLogFactory::AUDIO_INPUT_CONTROLLER)),
|
| is_dispatching_event_(false),
|
| provisional_results_(provisional_results),
|
| + using_audio_track_(using_audio_track),
|
| + peer_process_handle_(peer_process_handle),
|
| state_(STATE_IDLE) {
|
| DCHECK(recognition_engine_ != NULL);
|
| if (!continuous) {
|
| @@ -214,7 +231,7 @@ SpeechRecognizerImpl::SpeechRecognizerImpl(
|
| // synchronous callbacks.
|
|
|
| void SpeechRecognizerImpl::StartRecognition(const std::string& device_id) {
|
| - DCHECK(!device_id.empty());
|
| + DCHECK(!device_id.empty() ^ using_audio_track_);
|
| device_id_ = device_id;
|
|
|
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
|
| @@ -245,7 +262,8 @@ bool SpeechRecognizerImpl::IsCapturingAudio() const {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // See IsActive().
|
| const bool is_capturing_audio = state_ >= STATE_STARTING &&
|
| state_ <= STATE_RECOGNIZING;
|
| - DCHECK((is_capturing_audio && (audio_controller_.get() != NULL)) ||
|
| + DCHECK((is_capturing_audio &&
|
| + (using_audio_track_ || (audio_controller_.get() != NULL))) ||
|
| (!is_capturing_audio && audio_controller_.get() == NULL));
|
| return is_capturing_audio;
|
| }
|
| @@ -263,6 +281,12 @@ SpeechRecognizerImpl::~SpeechRecognizerImpl() {
|
| base::Bind(&KeepAudioControllerRefcountedForDtor, audio_controller_));
|
| audio_log_->OnClosed(0);
|
| }
|
| +
|
| + if (audio_sync_thread_.get()) {
|
| + base::ThreadRestrictions::SetIOAllowed(true);
|
| + audio_sync_socket_->Shutdown();
|
| + audio_sync_thread_->Stop();
|
| + }
|
| }
|
|
|
| // Invoked in the audio thread.
|
| @@ -482,7 +506,7 @@ void SpeechRecognizerImpl::ProcessAudioPipeline(const AudioChunk& raw_audio) {
|
| endpointer_.ProcessAudio(raw_audio, &rms);
|
|
|
| if (route_to_vumeter) {
|
| - DCHECK(route_to_endpointer); // Depends on endpointer due to |rms|.
|
| + DCHECK(route_to_endpointer); // Depends on endpointer due to |rms|.
|
| UpdateSignalAndNoiseLevels(rms, clip_detected);
|
| }
|
| if (route_to_sr_engine) {
|
| @@ -529,6 +553,15 @@ SpeechRecognizerImpl::StartRecording(const FSMEventArgs&) {
|
| AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout, kAudioSampleRate,
|
| kNumBitsPerAudioSample, frames_per_buffer);
|
|
|
| + // Check if using audio track as source (audio data processed on renderer).
|
| + // If |StartRecordingUsingAudioTrack()| fails, we abort the recognition.
|
| + if (using_audio_track_)
|
| + return StartRecordingUsingAudioTrack(output_parameters)
|
| + ? STATE_STARTING
|
| + : Abort(SpeechRecognitionError(SPEECH_RECOGNITION_ERROR_AUDIO));
|
| +
|
| + // Get audio data from microphone on the browser.
|
| +
|
| // Audio converter will receive audio based on these parameters as input.
|
| // On Windows we start by verifying that Core Audio is supported. If not,
|
| // the WaveIn API is used and we might as well avoid all audio conversations
|
| @@ -585,6 +618,108 @@ SpeechRecognizerImpl::StartRecording(const FSMEventArgs&) {
|
| return STATE_STARTING;
|
| }
|
|
|
| +bool SpeechRecognizerImpl::StartRecordingUsingAudioTrack(
|
| + const media::AudioParameters& params) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + // Create the shared memory and share with the renderer process.
|
| + uint32 shared_memory_size = sizeof(uint32) +
|
| + AudioBus::CalculateMemorySize(params);
|
| + shared_memory_.reset(new base::SharedMemory());
|
| + if (!shared_memory_->CreateAndMapAnonymous(shared_memory_size)) {
|
| + DLOG(ERROR) << "Failed to create shared memory for the speech recognizer";
|
| + return false;
|
| + }
|
| + base::SharedMemoryHandle foreign_memory_handle;
|
| + if (!shared_memory_->ShareToProcess(base::GetCurrentProcessHandle(),
|
| + &foreign_memory_handle)) {
|
| + DLOG(ERROR) << "Failed to map and share the shared memory";
|
| + return false;
|
| + }
|
| + media::AudioSharedBuffer* buffer =
|
| + static_cast<media::AudioSharedBuffer*>(shared_memory_->memory());
|
| + audio_track_bus_ = AudioBus::WrapMemory(params, buffer->audio);
|
| + // Use |buffer->params.size| from shared memory to synchronize by counting
|
| + // audio buffers when receiving audio data from the renderer.
|
| + buffer_index_ = &(buffer->buffer_index);
|
| + *buffer_index_ = 0U;
|
| + // Reusing the audio converter only to create chunks from the bus.
|
| + // Same input and output format indicate no additional conversion here.
|
| + audio_converter_.reset(new OnDataConverter(params, params));
|
| +
|
| + // The endpointer needs to estimate the environment/background noise before
|
| + // starting to treat the audio as user input. We wait in the state
|
| + // ESTIMATING_ENVIRONMENT until such interval has elapsed before switching
|
| + // to user input mode.
|
| + endpointer_.SetEnvironmentEstimationMode();
|
| +
|
| + foreign_sync_socket_.reset(new base::CancelableSyncSocket());
|
| + audio_sync_socket_.reset(new base::CancelableSyncSocket());
|
| + if (!base::SyncSocket::CreatePair(audio_sync_socket_.get(),
|
| + foreign_sync_socket_.get())) {
|
| + DLOG(ERROR) << "Cannot create SyncSocket pair";
|
| + return false;
|
| + }
|
| +
|
| + base::SyncSocket::TransitDescriptor foreign_socket_descriptor;
|
| + if (!foreign_sync_socket_->PrepareTransitDescriptor(
|
| + peer_process_handle_, &foreign_socket_descriptor)) {
|
| + DLOG(ERROR) << "Cannot prepare syncsocket transit descriptor";
|
| + return false;
|
| + }
|
| +
|
| + // The thread check will be first invoked in |OnAudioTrackData()|.
|
| + audio_sync_thread_checker_.DetachFromThread();
|
| + audio_sync_thread_.reset(
|
| + new base::Thread("SpeechRecognitionAudioTrackThread"));
|
| + audio_sync_thread_->Start();
|
| +
|
| + DCHECK_EQ(0U, *buffer_index_);
|
| + if (!audio_sync_thread_->message_loop_proxy()
|
| + ->PostTask(FROM_HERE,
|
| + base::Bind(&SpeechRecognizerImpl::OnAudioTrackData,
|
| + base::Unretained(this)))) {
|
| + DLOG(ERROR) << "Cannot post audio sync thread task";
|
| + return false;
|
| + }
|
| +
|
| + listener()->OnAudioTrackReady(session_id(), params, foreign_memory_handle,
|
| + foreign_socket_descriptor);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void SpeechRecognizerImpl::OnAudioTrackData() {
|
| + DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
|
| + DCHECK(audio_sync_thread_checker_.CalledOnValidThread());
|
| + base::ThreadRestrictions::SetIOAllowed(true);
|
| +
|
| + // TODO(burnik): We should receive with a timeout here. And when it occurs,
|
| + // we should stop the recognition assuming there is no more audio data.
|
| + size_t bytes_received =
|
| + audio_sync_socket_->Receive(buffer_index_, sizeof(*buffer_index_));
|
| +
|
| + if (bytes_received != sizeof(*buffer_index_)) {
|
| + DCHECK_EQ(bytes_received, 0U);
|
| + return;
|
| + }
|
| +
|
| + FSMEventArgs event_args(EVENT_AUDIO_DATA);
|
| + // Convert audio from native format to fixed format used by WebSpeech.
|
| + event_args.audio_data = audio_converter_->Convert(audio_track_bus_.get());
|
| + // Process on browser thread.
|
| + BrowserThread::PostTask(
|
| + BrowserThread::IO, FROM_HERE,
|
| + base::Bind(&SpeechRecognizerImpl::DispatchEvent, this, event_args));
|
| +
|
| + // Notify host that the buffer was consumed via shared memory.
|
| + (*buffer_index_)++;
|
| +
|
| + // Post this task for next round of data.
|
| + audio_sync_thread_->message_loop_proxy()->PostTask(
|
| + FROM_HERE, base::Bind(&SpeechRecognizerImpl::OnAudioTrackData,
|
| + base::Unretained(this)));
|
| +}
|
| +
|
| SpeechRecognizerImpl::FSMState
|
| SpeechRecognizerImpl::StartRecognitionEngine(const FSMEventArgs& event_args) {
|
| // This is the first audio packet captured, so the recognition engine is
|
| @@ -635,7 +770,7 @@ SpeechRecognizerImpl::StopCaptureAndWaitForResult(const FSMEventArgs&) {
|
| DCHECK(state_ >= STATE_ESTIMATING_ENVIRONMENT && state_ <= STATE_RECOGNIZING);
|
|
|
| DVLOG(1) << "Concluding recognition";
|
| - CloseAudioControllerAsynchronously();
|
| + if (!using_audio_track_) CloseAudioControllerAsynchronously();
|
| recognition_engine_->AudioChunksEnded();
|
|
|
| if (state_ > STATE_WAITING_FOR_SPEECH)
|
| @@ -664,7 +799,7 @@ SpeechRecognizerImpl::AbortWithError(const FSMEventArgs& event_args) {
|
|
|
| SpeechRecognizerImpl::FSMState SpeechRecognizerImpl::Abort(
|
| const SpeechRecognitionError& error) {
|
| - if (IsCapturingAudio())
|
| + if (IsCapturingAudio() && !using_audio_track_)
|
| CloseAudioControllerAsynchronously();
|
|
|
| DVLOG(1) << "SpeechRecognizerImpl canceling recognition. ";
|
| @@ -770,11 +905,13 @@ SpeechRecognizerImpl::NotFeasible(const FSMEventArgs& event_args) {
|
|
|
| void SpeechRecognizerImpl::CloseAudioControllerAsynchronously() {
|
| DCHECK(IsCapturingAudio());
|
| + DCHECK(!using_audio_track_);
|
| DVLOG(1) << "SpeechRecognizerImpl closing audio controller.";
|
| // Issues a Close on the audio controller, passing an empty callback. The only
|
| // purpose of such callback is to keep the audio controller refcounted until
|
| // Close has completed (in the audio thread) and automatically destroy it
|
| // afterwards (upon return from OnAudioClosed).
|
| +
|
| audio_controller_->Close(base::Bind(&SpeechRecognizerImpl::OnAudioClosed,
|
| this, audio_controller_));
|
| audio_controller_ = NULL; // The controller is still refcounted by Bind.
|
|
|