Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/browser/speech/speech_recognition_audio_receiver.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/process/process_handle.h" | |
| 9 #include "base/threading/thread_restrictions.h" | |
| 10 #include "content/public/browser/browser_thread.h" | |
| 11 #include "media/audio/audio_parameters.h" | |
| 12 #include "media/base/audio_bus.h" | |
| 13 | |
| 14 namespace content { | |
| 15 | |
| 16 // static | |
| 17 SpeechRecognitionAudioReceiver* SpeechRecognitionAudioReceiver::Create( | |
|
no longer working on chromium
2014/10/15 12:14:28
use scoped_ptr<SpeechRecognitionAudioReceiver>
burnik
2014/10/15 14:52:27
Done.
| |
| 18 const media::AudioParameters& audio_params, | |
| 19 const base::ProcessHandle peer_process_handle, | |
| 20 media::AudioInputController::EventHandler* event_handler, | |
| 21 base::SingleThreadTaskRunner* task_runner, | |
| 22 base::SharedMemoryHandle* foreign_memory_handle, | |
| 23 base::SyncSocket::TransitDescriptor* foreign_socket_descriptor) { | |
| 24 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 25 DCHECK(event_handler); | |
| 26 | |
| 27 scoped_ptr<SpeechRecognitionAudioReceiver> receiver( | |
| 28 new SpeechRecognitionAudioReceiver(audio_params, | |
| 29 event_handler, | |
| 30 task_runner)); | |
| 31 | |
| 32 if (receiver->Initialize(peer_process_handle, | |
| 33 foreign_memory_handle, | |
| 34 foreign_socket_descriptor)) { | |
| 35 return receiver.release(); | |
| 36 } else { | |
| 37 return NULL; | |
| 38 } | |
| 39 } | |
| 40 | |
| 41 SpeechRecognitionAudioReceiver::SpeechRecognitionAudioReceiver( | |
| 42 const media::AudioParameters& audio_params, | |
| 43 media::AudioInputController::EventHandler* event_handler, | |
| 44 base::SingleThreadTaskRunner* task_runner) : | |
| 45 audio_params_(audio_params), | |
| 46 task_runner_(task_runner), | |
| 47 event_handler_(event_handler), | |
| 48 dropped_packets_(0), | |
| 49 weak_ptr_factory_(this), | |
| 50 listening_(false) { | |
| 51 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 52 } | |
| 53 | |
| 54 SpeechRecognitionAudioReceiver::~SpeechRecognitionAudioReceiver() { | |
| 55 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 56 | |
| 57 // Make sure we don't issue an OnError event since the client is | |
| 58 // deleting the object. | |
| 59 set_listening(false); | |
| 60 | |
| 61 // We close the socket so the task runner thread doesn't block on it. | |
| 62 receiving_socket_->Shutdown(); | |
| 63 | |
| 64 // No audio thread indicates the client provided a TaskRunner. | |
| 65 if (!audio_thread_.get()) | |
| 66 return; | |
| 67 | |
| 68 // We need to stop the |audio_thread_| to properly clean up. | |
| 69 // We must allow the IO until the task in the thread's message loop completes. | |
| 70 // This should execute almost immediately in most of cases since we expect | |
| 71 // the sockets were closed and |Receive()| returned without reposting a task. | |
| 72 const bool previous_io_allowed = base::ThreadRestrictions::SetIOAllowed(true); | |
| 73 audio_thread_->Stop(); | |
| 74 base::ThreadRestrictions::SetIOAllowed(previous_io_allowed); | |
| 75 } | |
| 76 | |
| 77 bool SpeechRecognitionAudioReceiver::Initialize( | |
| 78 const base::ProcessHandle peer_process_handle, | |
| 79 base::SharedMemoryHandle* foreign_memory_handle, | |
| 80 base::SyncSocket::TransitDescriptor* foreign_socket_descriptor) { | |
| 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 82 DCHECK(foreign_memory_handle); | |
| 83 DCHECK(foreign_socket_descriptor); | |
| 84 DCHECK(!listening()); | |
| 85 | |
| 86 // All clean up on failure will be done by the destructor. | |
| 87 const size_t kSharedMemorySize = sizeof(media::AudioInputBufferParameters) + | |
| 88 media::AudioBus::CalculateMemorySize(audio_params_); | |
| 89 shared_memory_.reset(new base::SharedMemory()); | |
| 90 if (!shared_memory_->CreateAndMapAnonymous(kSharedMemorySize)) { | |
| 91 DLOG(ERROR) << "Failed to create shared memory for the speech recognizer"; | |
| 92 return false; | |
| 93 } | |
| 94 memset(shared_memory_->memory(), 0, kSharedMemorySize); | |
| 95 | |
| 96 if (!shared_memory_->ShareToProcess(base::GetCurrentProcessHandle(), | |
| 97 foreign_memory_handle)) { | |
| 98 DLOG(ERROR) << "Failed to map and share the shared memory"; | |
| 99 return false; | |
| 100 } | |
| 101 | |
| 102 foreign_socket_.reset(new base::CancelableSyncSocket()); | |
| 103 receiving_socket_.reset(new base::CancelableSyncSocket()); | |
| 104 if (!base::SyncSocket::CreatePair(receiving_socket_.get(), | |
| 105 foreign_socket_.get())) { | |
| 106 DLOG(ERROR) << "Cannot create SyncSocket pair"; | |
| 107 return false; | |
| 108 } | |
| 109 | |
| 110 if (!foreign_socket_->PrepareTransitDescriptor(peer_process_handle, | |
| 111 foreign_socket_descriptor)) { | |
| 112 DLOG(ERROR) << "Cannot prepare syncsocket transit descriptor"; | |
| 113 return false; | |
| 114 } | |
| 115 | |
| 116 media::AudioInputBuffer* audio_buffer = | |
| 117 static_cast<media::AudioInputBuffer*>(shared_memory_->memory()); | |
| 118 audio_bus_ = media::AudioBus::WrapMemory(audio_params_, audio_buffer->audio); | |
| 119 audio_bus_->Zero(); | |
| 120 | |
| 121 // Use |buffer->params.size| from shared memory to synchronize by counting | |
| 122 // audio buffers when receiving from the renderer. | |
| 123 audio_buffer->params.size = 0; | |
| 124 | |
| 125 audio_thread_checker_.DetachFromThread(); | |
| 126 | |
| 127 // Check if we need a new thread for the task runner. | |
| 128 if (!task_runner_) { | |
| 129 audio_thread_.reset(new base::Thread("AudioReceiverThread")); | |
| 130 audio_thread_->Start(); | |
| 131 task_runner_ = audio_thread_->task_runner().get(); | |
| 132 } | |
| 133 | |
| 134 return true; | |
| 135 } | |
| 136 | |
| 137 void SpeechRecognitionAudioReceiver::Listen() { | |
| 138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 139 if (listening()) | |
| 140 return; | |
| 141 | |
| 142 task_runner_->PostTask(FROM_HERE, | |
| 143 base::Bind(&SpeechRecognitionAudioReceiver::Receive, | |
| 144 weak_ptr_factory_.GetWeakPtr())); | |
| 145 set_listening(true); | |
| 146 } | |
| 147 | |
| 148 void SpeechRecognitionAudioReceiver::Receive() { | |
| 149 DCHECK(audio_thread_checker_.CalledOnValidThread()); | |
| 150 static const int kAudioChunkLengthMs = | |
| 151 (audio_params_.frames_per_buffer() * 1000) / audio_params_.sample_rate(); | |
| 152 | |
| 153 // Allow near 100% overhead on receive timeout. | |
| 154 // This is well adjusted to the allowed latency on the renderer. | |
| 155 // See |kNumberOfBuffersInFifo| in SpeechRecognitionAudioSink implementation. | |
| 156 static const base::TimeDelta kReceiveTimeout = | |
| 157 base::TimeDelta::FromMilliseconds(kAudioChunkLengthMs * 2); | |
| 158 DCHECK_LT(kReceiveTimeout.InMilliseconds(), 1000); | |
| 159 | |
| 160 // Maximum of consecutive dropped packets. When reached we trigger an error | |
| 161 // and stop reposting the task for |Receive()|. | |
| 162 static const uint32 kMaxDroppedPackets = 2; | |
| 163 static const size_t kSize = sizeof(media::AudioInputBufferParameters().size); | |
| 164 | |
| 165 // Receive with timeout not to block indefinitely. Ideally we block here for | |
| 166 // |kAudioChunkLengthMs| as long as the socket is open. | |
| 167 size_t bytes_received = receiving_socket_->ReceiveWithTimeout( | |
|
no longer working on chromium
2014/10/15 12:14:28
I think the best solution here is to use a short t
burnik
2014/10/15 14:52:27
Yeah, I've tried that approach. The delay seems no
| |
| 168 &(GetAudioBuffer()->params.size), kSize, kReceiveTimeout); | |
| 169 DCHECK_EQ(0U, bytes_received % kSize); | |
| 170 | |
| 171 if (bytes_received == kSize) { | |
| 172 // Any previous issues with the socket are ignored. | |
| 173 dropped_packets_ = 0; | |
| 174 | |
| 175 // Execute the client callback on this |task_runner_|'s thread. | |
| 176 event_handler_->OnData(NULL, audio_bus_.get()); | |
| 177 | |
| 178 // Notify host via shared memory that the buffer was consumed. | |
| 179 GetAudioBuffer()->params.size++; | |
| 180 } else { | |
| 181 // If destructor was called we stop posting tasks to the thread. | |
| 182 if (!listening()) | |
| 183 return; | |
| 184 | |
| 185 // Socket issue: either a timeout occurred or socket is closed. | |
| 186 if (++dropped_packets_ >= kMaxDroppedPackets) { | |
| 187 DLOG(ERROR) << "Dropped max packets"; | |
| 188 receiving_socket_->Shutdown(); | |
| 189 | |
| 190 // We have passed the limit of consecutive dropped packets | |
| 191 // so we trigger the error event. | |
| 192 event_handler_->OnError( | |
| 193 NULL, media::AudioInputController::UNKNOWN_ERROR); | |
| 194 | |
| 195 return; | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 base::MessageLoopProxy::current()->PostTask( | |
| 200 FROM_HERE, | |
| 201 base::Bind(&SpeechRecognitionAudioReceiver::Receive, | |
| 202 weak_ptr_factory_.GetWeakPtr())); | |
| 203 } | |
| 204 | |
| 205 media::AudioInputBuffer* SpeechRecognitionAudioReceiver::GetAudioBuffer() { | |
| 206 DCHECK(audio_thread_checker_.CalledOnValidThread()); | |
| 207 DCHECK_GE(shared_memory_->mapped_size(), shared_memory_->requested_size()); | |
| 208 return static_cast<media::AudioInputBuffer*>(shared_memory_->memory()); | |
| 209 } | |
| 210 | |
| 211 bool SpeechRecognitionAudioReceiver::listening() { | |
| 212 base::AutoLock lock(listening_lock_); | |
| 213 return listening_; | |
| 214 } | |
| 215 | |
| 216 void SpeechRecognitionAudioReceiver::set_listening(const bool listening) { | |
| 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 218 base::AutoLock lock(listening_lock_); | |
| 219 listening_ = listening; | |
| 220 } | |
| 221 | |
| 222 } // namespace content | |
| OLD | NEW |