Tomasz Wiszkowski | b34828c | 2017-09-28 14:03:41 -0700 | [diff] [blame] | 1 | /* |
Tomasz Wiszkowski | bbf8c0b | 2017-09-28 14:23:06 -0700 | [diff] [blame] | 2 | * Copyright (C) 2017 The Android Open Source Project |
Tomasz Wiszkowski | b34828c | 2017-09-28 14:03:41 -0700 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
Tomasz Wiszkowski | bbf8c0b | 2017-09-28 14:23:06 -0700 | [diff] [blame] | 16 | #include "guest/commands/audio/audio_hal.h" |
Tomasz Wiszkowski | b34828c | 2017-09-28 14:03:41 -0700 | [diff] [blame] | 17 | |
| 18 | #include <stdio.h> |
| 19 | #include <stdlib.h> |
| 20 | #include <sys/time.h> |
| 21 | #include <fcntl.h> |
| 22 | |
| 23 | extern "C" { |
| 24 | #include <cutils/str_parms.h> |
| 25 | } |
| 26 | |
Tomasz Wiszkowski | bbf8c0b | 2017-09-28 14:23:06 -0700 | [diff] [blame] | 27 | #include "common/libs/auto_resources/auto_resources.h" |
| 28 | #include "common/libs/fs/shared_select.h" |
Greg Hartman | a4ff248 | 2017-10-03 16:35:00 -0700 | [diff] [blame^] | 29 | #include "common/libs/threads/cuttlefish_thread.h" |
Tomasz Wiszkowski | bbf8c0b | 2017-09-28 14:23:06 -0700 | [diff] [blame] | 30 | #include "common/libs/threads/thunkers.h" |
| 31 | #include "guest/commands/audio/vsoc_audio.h" |
| 32 | #include "guest/commands/audio/vsoc_audio_input_stream.h" |
| 33 | #include "guest/commands/audio/vsoc_audio_output_stream.h" |
| 34 | #include "guest/libs/platform_support/api_level_fixes.h" |
| 35 | #include "guest/libs/remoter/remoter_framework_pkt.h" |
Tomasz Wiszkowski | b34828c | 2017-09-28 14:03:41 -0700 | [diff] [blame] | 36 | |
| 37 | using avd::LockGuard; |
| 38 | using avd::Mutex; |
| 39 | |
| 40 | namespace avd { |
| 41 | |
| 42 | namespace { |
| 43 | template <typename F> struct HWDeviceThunker : |
| 44 | ThunkerBase<hw_device_t, GceAudio, F>{}; |
| 45 | template <typename F> struct AudioThunker : |
| 46 | ThunkerBase<audio_hw_device, GceAudio, F>{}; |
| 47 | template <typename F> struct AudioThreadThunker : |
| 48 | ThunkerBase<void, GceAudio, F>{}; |
| 49 | } |
| 50 | |
| 51 | GceAudio::~GceAudio() { } |
| 52 | |
| 53 | int GceAudio::Close() { |
| 54 | D("GceAudio::%s", __FUNCTION__); |
| 55 | { |
| 56 | LockGuard<Mutex> guard(lock_); |
| 57 | for (std::list<GceAudioOutputStream*>::iterator it = output_list_.begin(); |
| 58 | it != output_list_.end(); ++it) { |
| 59 | delete *it; |
| 60 | } |
| 61 | for (input_map_t::iterator it = input_map_.begin(); |
| 62 | it != input_map_.end(); ++it) { |
| 63 | delete it->second; |
| 64 | } |
| 65 | } |
| 66 | // Make certain that the listener thread wakes up |
| 67 | avd::SharedFD temp_client = |
| 68 | avd::SharedFD::SocketSeqPacketClient( |
| 69 | gce_audio_message::kAudioHALSocketName); |
| 70 | uint64_t dummy_val = 1; |
| 71 | terminate_listener_thread_event_->Write(&dummy_val, sizeof dummy_val); |
| 72 | pthread_join(listener_thread_, NULL); |
| 73 | delete this; |
| 74 | return 0; |
| 75 | } |
| 76 | |
| 77 | avd::SharedFD GceAudio::GetAudioFd() { |
| 78 | LockGuard<Mutex> guard(lock_); |
| 79 | return audio_data_socket_; |
| 80 | } |
| 81 | |
| 82 | size_t GceAudio::GetInputBufferSize(const audio_config*) const { |
| 83 | return IN_BUFFER_BYTES; |
| 84 | } |
| 85 | |
| 86 | uint32_t GceAudio::GetSupportedDevices() const { |
| 87 | return AUDIO_DEVICE_OUT_EARPIECE | |
| 88 | AUDIO_DEVICE_OUT_SPEAKER | |
| 89 | AUDIO_DEVICE_OUT_DEFAULT | |
| 90 | AUDIO_DEVICE_IN_COMMUNICATION | |
| 91 | AUDIO_DEVICE_IN_BUILTIN_MIC | |
| 92 | AUDIO_DEVICE_IN_WIRED_HEADSET | |
| 93 | AUDIO_DEVICE_IN_VOICE_CALL | |
| 94 | AUDIO_DEVICE_IN_DEFAULT; |
| 95 | } |
| 96 | |
| 97 | int GceAudio::InitCheck() const { |
| 98 | D("GceAudio::%s", __FUNCTION__); |
| 99 | return 0; |
| 100 | } |
| 101 | |
| 102 | int GceAudio::SetMicMute(bool state) { |
| 103 | D("GceAudio::%s", __FUNCTION__); |
| 104 | LockGuard<Mutex> guard(lock_); |
| 105 | mic_muted_ = state; |
| 106 | return 0; |
| 107 | } |
| 108 | |
| 109 | int GceAudio::GetMicMute(bool *state) const { |
| 110 | D("GceAudio::%s", __FUNCTION__); |
| 111 | LockGuard<Mutex> guard(lock_); |
| 112 | *state = mic_muted_; |
| 113 | return 0; |
| 114 | } |
| 115 | |
| 116 | int GceAudio::OpenInputStream(audio_io_handle_t handle, |
| 117 | audio_devices_t devices, |
| 118 | audio_config *config, |
| 119 | audio_stream_in **stream_in, |
| 120 | audio_input_flags_t /*flags*/, |
| 121 | const char * /*address*/, |
| 122 | audio_source_t /*source*/) { |
| 123 | GceAudioInputStream* new_stream; |
| 124 | int rval = GceAudioInputStream::Open( |
| 125 | this, handle, devices, *config, &new_stream); |
| 126 | uint32_t stream_number; |
| 127 | if (new_stream) { |
| 128 | LockGuard<Mutex> guard(lock_); |
| 129 | stream_number = next_stream_number_++; |
| 130 | input_map_[stream_number] = new_stream; |
| 131 | } |
| 132 | // This should happen after the lock is released, hence the double check |
| 133 | if (new_stream) { |
| 134 | SendStreamUpdate(new_stream->GetStreamDescriptor( |
| 135 | stream_number, gce_audio_message::OPEN_INPUT_STREAM), MSG_DONTWAIT); |
| 136 | } |
| 137 | *stream_in = new_stream; |
| 138 | return rval; |
| 139 | } |
| 140 | |
| 141 | |
| 142 | void GceAudio::CloseInputStream(audio_stream_in *stream) { |
| 143 | GceAudioInputStream* astream = static_cast<GceAudioInputStream*>(stream); |
| 144 | gce_audio_message descriptor; |
| 145 | { |
| 146 | LockGuard<Mutex> guard(lock_); |
| 147 | // TODO(ghartman): This could be optimized if stream knew it's number. |
| 148 | for (input_map_t::iterator it = input_map_.begin(); |
| 149 | it != input_map_.end(); ++it) { |
| 150 | if (it->second == stream) { |
| 151 | descriptor = it->second->GetStreamDescriptor( |
| 152 | it->first, gce_audio_message::CLOSE_INPUT_STREAM); |
| 153 | input_map_.erase(it); |
| 154 | break; |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | SendStreamUpdate(descriptor, MSG_DONTWAIT); |
| 159 | delete astream; |
| 160 | } |
| 161 | |
| 162 | |
| 163 | int GceAudio::OpenOutputStream(audio_io_handle_t handle, |
| 164 | audio_devices_t devices, |
| 165 | audio_output_flags_t flags, |
| 166 | audio_config *config, |
| 167 | audio_stream_out **stream_out, |
| 168 | const char * /*address*/) { |
| 169 | GceAudioOutputStream* new_stream; |
| 170 | int rval; |
| 171 | { |
| 172 | LockGuard<Mutex> guard(lock_); |
| 173 | rval = GceAudioOutputStream::Open( |
| 174 | this, handle, devices, flags, config, next_stream_number_++, |
| 175 | &new_stream); |
| 176 | if (new_stream) { |
| 177 | output_list_.push_back(new_stream); |
| 178 | } |
| 179 | } |
| 180 | if (new_stream) { |
| 181 | SendStreamUpdate(new_stream->GetStreamDescriptor( |
| 182 | gce_audio_message::OPEN_OUTPUT_STREAM), MSG_DONTWAIT); |
| 183 | } |
| 184 | *stream_out = new_stream; |
| 185 | return rval; |
| 186 | } |
| 187 | |
| 188 | void GceAudio::CloseOutputStream(audio_stream_out *stream) { |
| 189 | GceAudioOutputStream* astream = static_cast<GceAudioOutputStream*>(stream); |
| 190 | gce_audio_message close; |
| 191 | { |
| 192 | LockGuard<Mutex> guard(lock_); |
| 193 | output_list_.remove(astream); |
| 194 | close = astream->GetStreamDescriptor( |
| 195 | gce_audio_message::CLOSE_OUTPUT_STREAM); |
| 196 | } |
| 197 | SendStreamUpdate(close, MSG_DONTWAIT); |
| 198 | delete astream; |
| 199 | } |
| 200 | |
| 201 | int GceAudio::Dump(int fd) const { |
| 202 | LockGuard<Mutex> guard(lock_); |
Tomasz Wiszkowski | bbf8c0b | 2017-09-28 14:23:06 -0700 | [diff] [blame] | 203 | VSOC_FDPRINTF( |
Tomasz Wiszkowski | b34828c | 2017-09-28 14:03:41 -0700 | [diff] [blame] | 204 | fd, |
| 205 | "\nadev_dump:\n" |
| 206 | "\tmic_mute: %s\n" |
| 207 | "\tnum_outputs: %zu\n" |
| 208 | "\tnum_inputs: %zu\n\n", |
| 209 | mic_muted_ ? "true": "false", |
| 210 | output_list_.size(), input_map_.size()); |
| 211 | |
| 212 | for (std::list<GceAudioOutputStream*>::const_iterator it = |
| 213 | output_list_.begin(); |
| 214 | it != output_list_.end(); ++it) { |
| 215 | (*it)->common.dump(&(*it)->common, fd); |
| 216 | } |
| 217 | |
| 218 | for (input_map_t::const_iterator it = input_map_.begin(); |
| 219 | it != input_map_.end(); ++it) { |
| 220 | (*it).second->common.dump(&(*it).second->common, fd); |
| 221 | } |
| 222 | |
| 223 | return 0; |
| 224 | } |
| 225 | |
| 226 | ssize_t GceAudio::SendMsg(const msghdr& msg, int flags) { |
| 227 | avd::SharedFD fd = GetAudioFd(); |
| 228 | if (!fd->IsOpen()) { |
| 229 | return 0; |
| 230 | } |
| 231 | return fd->SendMsg(&msg, flags); |
| 232 | } |
| 233 | |
| 234 | ssize_t GceAudio::SendStreamUpdate( |
| 235 | const gce_audio_message& stream_info, int flags) { |
| 236 | msghdr msg; |
| 237 | iovec msg_iov[1]; |
| 238 | msg_iov[0].iov_base = const_cast<gce_audio_message*>(&stream_info); |
| 239 | msg_iov[0].iov_len = sizeof(gce_audio_message); |
| 240 | msg.msg_name = NULL; |
| 241 | msg.msg_namelen = 0; |
| 242 | msg.msg_iov = msg_iov; |
| 243 | msg.msg_iovlen = arraysize(msg_iov); |
| 244 | msg.msg_control = NULL; |
| 245 | msg.msg_controllen = 0; |
| 246 | msg.msg_flags = 0; |
| 247 | return SendMsg(msg, flags); |
| 248 | } |
| 249 | |
| 250 | int GceAudio::SetVoiceVolume(float volume) { |
| 251 | D("GceAudio::%s: set voice volume %f", __FUNCTION__, volume); |
| 252 | voice_volume_ = volume; |
| 253 | return 0; |
| 254 | } |
| 255 | |
| 256 | int GceAudio::SetMasterVolume(float volume) { |
| 257 | D("GceAudio::%s: set master volume %f", __FUNCTION__, volume); |
| 258 | master_volume_ = volume; |
| 259 | return 0; |
| 260 | } |
| 261 | |
| 262 | int GceAudio::GetMasterVolume(float* volume) { |
| 263 | D("GceAudio::%s: get master volume %f", __FUNCTION__, master_volume_); |
| 264 | *volume = master_volume_; |
| 265 | return 0; |
| 266 | } |
| 267 | |
| 268 | int GceAudio::SetMasterMute(bool muted) { |
| 269 | D("GceAudio::%s: set master muted %d", __FUNCTION__, muted); |
| 270 | master_muted_ = muted; |
| 271 | return 0; |
| 272 | } |
| 273 | |
| 274 | int GceAudio::GetMasterMute(bool* muted) { |
| 275 | D("GceAudio::%s: get master muted %d", __FUNCTION__, master_muted_); |
| 276 | *muted = master_muted_; |
| 277 | return 0; |
| 278 | } |
| 279 | |
| 280 | int GceAudio::SetMode(audio_mode_t mode) { |
| 281 | D("GceAudio::%s: new mode %d", __FUNCTION__, mode); |
| 282 | mode_ = mode; |
| 283 | return 0; |
| 284 | } |
| 285 | |
| 286 | void* GceAudio::Listener() { |
| 287 | // TODO(ghartman): Consider tightening the mode on this later. |
| 288 | audio_listener_socket_ = avd::SharedFD::SocketSeqPacketServer( |
| 289 | gce_audio_message::kAudioHALSocketName, 0777); |
| 290 | if (!audio_listener_socket_->IsOpen()) { |
| 291 | ALOGE("GceAudio::%s: Could not listen for audio connections. (%s).", |
| 292 | __FUNCTION__, audio_listener_socket_->StrError()); |
| 293 | return NULL; |
| 294 | } |
| 295 | ALOGI("GceAudio::%s: Listening for audio connections at %s", |
| 296 | __FUNCTION__, gce_audio_message::kAudioHALSocketName); |
| 297 | remoter_request_packet announce; |
| 298 | remoter_request_packet_init(&announce, kRemoterHALReady, 0); |
| 299 | announce.send_response = 0; |
| 300 | strncpy(announce.params.hal_ready_params.unix_socket, |
| 301 | gce_audio_message::kAudioHALSocketName, |
| 302 | sizeof(announce.params.hal_ready_params.unix_socket)); |
| 303 | AutoCloseFileDescriptor remoter_socket(remoter_connect()); |
| 304 | if (remoter_socket.IsError()) { |
| 305 | ALOGI("GceAudio::%s: Couldn't connect to remoter to register HAL (%s).", |
| 306 | __FUNCTION__, strerror(errno)); |
| 307 | } else { |
| 308 | int err = remoter_do_single_request_with_socket( |
| 309 | remoter_socket, &announce, NULL); |
| 310 | if (err == -1) { |
| 311 | ALOGI("GceAudio::%s: HAL registration failed after connect (%s).", |
| 312 | __FUNCTION__, strerror(errno)); |
| 313 | } else { |
| 314 | ALOGI("GceAudio::%s: HAL registered with the remoter", __FUNCTION__); |
| 315 | } |
| 316 | } |
| 317 | while (true) { |
| 318 | // Poll for new connections or the terminatation event. |
| 319 | // The listener is non-blocking. We send to at most one client. If a new |
| 320 | // client comes in disconnect the old one. |
| 321 | avd::SharedFDSet fd_set; |
| 322 | fd_set.Set(audio_listener_socket_); |
| 323 | fd_set.Set(terminate_listener_thread_event_); |
| 324 | if (avd::Select(&fd_set, NULL, NULL, NULL) <= 0) { |
| 325 | // There's no timeout, so 0 shouldn't happen. |
| 326 | ALOGE("GceAudio::%s: Error using shared Select", __FUNCTION__); |
| 327 | break; |
| 328 | } |
| 329 | if (fd_set.IsSet(terminate_listener_thread_event_)) { |
| 330 | break; |
| 331 | } |
| 332 | LOG_FATAL_IF(fd_set.IsSet(audio_listener_socket_), |
| 333 | "No error in Select() but nothing ready to read"); |
| 334 | avd::SharedFD fd = avd::SharedFD::Accept( |
| 335 | *audio_listener_socket_); |
| 336 | if (!fd->IsOpen()) { |
| 337 | continue; |
| 338 | } |
| 339 | std::list<gce_audio_message> streams; |
| 340 | { |
| 341 | LockGuard<Mutex> guard(lock_); |
| 342 | // Do not do I/O while holding the lock. It could block the HAL |
| 343 | // implementation. |
| 344 | // Register the fd before dropping the lock to ensure that every |
| 345 | // active stream will appear when we first connect. |
| 346 | // Some output streams may appear twice if an open is active |
| 347 | // during the connect. |
| 348 | audio_data_socket_ = fd; |
| 349 | for (std::list<GceAudioOutputStream*>::iterator it = output_list_.begin(); |
| 350 | it != output_list_.end(); ++it) { |
| 351 | streams.push_back((*it)->GetStreamDescriptor( |
| 352 | gce_audio_message::OPEN_OUTPUT_STREAM)); |
| 353 | } |
| 354 | for (input_map_t::iterator it = input_map_.begin(); |
| 355 | it != input_map_.end(); ++it) { |
| 356 | streams.push_back(it->second->GetStreamDescriptor( |
| 357 | it->first, gce_audio_message::OPEN_INPUT_STREAM)); |
| 358 | } |
| 359 | } |
| 360 | for (std::list<gce_audio_message>::iterator it = streams.begin(); |
| 361 | it != streams.end(); ++it) { |
| 362 | // We're willing to block because this is independent of the HAL |
| 363 | // implementation. We also don't want to forget to mention the input |
| 364 | // streams. |
| 365 | if (SendStreamUpdate(*it, 0) < 0) { |
| 366 | ALOGE("GceAudio::%s: Failed to announce open stream (%s)", |
| 367 | __FUNCTION__, fd->StrError()); |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | return NULL; |
| 372 | } |
| 373 | |
| 374 | int GceAudio::Open(const hw_module_t* module, const char* name, |
| 375 | hw_device_t** device) { |
| 376 | D("GceAudio::%s", __FUNCTION__); |
| 377 | |
| 378 | if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) { |
| 379 | ALOGE("GceAudio::%s: invalid module name %s (expected %s)", |
| 380 | __FUNCTION__, name, AUDIO_HARDWARE_INTERFACE); |
| 381 | return -EINVAL; |
| 382 | } |
| 383 | |
| 384 | GceAudio* rval = new GceAudio; |
| 385 | int err = pthread_create( |
| 386 | &rval->listener_thread_, NULL, |
| 387 | AudioThreadThunker<void*()>::call<&GceAudio::Listener>, rval); |
| 388 | if (err) { |
| 389 | ALOGE("GceAudio::%s: Unable to start listener thread (%s)", __FUNCTION__, |
| 390 | strerror(err)); |
| 391 | } |
| 392 | rval->common.tag = HARDWARE_DEVICE_TAG; |
| 393 | rval->common.version = version_; |
| 394 | rval->common.module = const_cast<hw_module_t *>(module); |
| 395 | rval->common.close = HWDeviceThunker<int()>::call<&GceAudio::Close>; |
| 396 | |
| 397 | #if !defined(AUDIO_DEVICE_API_VERSION_2_0) |
| 398 | // This HAL entry is supported only on AUDIO_DEVICE_API_VERSION_1_0. |
| 399 | // In fact, with version 2.0 the device numbers were orgainized in a |
| 400 | // way that makes the return value nonsense. |
| 401 | // Skipping the assignment is ok: the memset in the constructor already |
| 402 | // put a NULL here. |
| 403 | rval->get_supported_devices = |
| 404 | AudioThunker<uint32_t()>::call<&GceAudio::GetSupportedDevices>; |
| 405 | #endif |
| 406 | rval->init_check = AudioThunker<int()>::call<&GceAudio::InitCheck>; |
| 407 | |
| 408 | rval->set_voice_volume = |
| 409 | AudioThunker<int(float)>::call<&GceAudio::SetVoiceVolume>; |
| 410 | rval->set_master_volume = |
| 411 | AudioThunker<int(float)>::call<&GceAudio::SetMasterVolume>; |
| 412 | rval->get_master_volume = |
| 413 | AudioThunker<int(float*)>::call<&GceAudio::GetMasterVolume>; |
| 414 | |
| 415 | #if defined(AUDIO_DEVICE_API_VERSION_2_0) |
| 416 | rval->set_master_mute = |
| 417 | AudioThunker<int(bool)>::call<&GceAudio::SetMasterMute>; |
| 418 | rval->get_master_mute = |
| 419 | AudioThunker<int(bool*)>::call<&GceAudio::GetMasterMute>; |
| 420 | #endif |
| 421 | |
| 422 | rval->set_mode = AudioThunker<int(audio_mode_t)>::call<&GceAudio::SetMode>; |
| 423 | rval->set_mic_mute = AudioThunker<int(bool)>::call<&GceAudio::SetMicMute>; |
| 424 | rval->get_mic_mute = |
| 425 | AudioThunker<int(bool*)>::call<&GceAudio::GetMicMute>; |
| 426 | |
| 427 | rval->set_parameters = |
| 428 | AudioThunker<int(const char*)>::call<&GceAudio::SetParameters>; |
| 429 | rval->get_parameters = |
| 430 | AudioThunker<char*(const char*)>::call<&GceAudio::GetParameters>; |
| 431 | |
| 432 | rval->get_input_buffer_size = |
| 433 | AudioThunker<size_t(const audio_config*)>::call< |
| 434 | &GceAudio::GetInputBufferSize>; |
| 435 | |
| 436 | rval->open_input_stream = |
| 437 | AudioThunker<GceAudio::OpenInputStreamHAL_t>::call< |
| 438 | &GceAudio::OpenInputStreamCurrentHAL>; |
| 439 | rval->close_input_stream = |
| 440 | AudioThunker<void(audio_stream_in*)>::call<&GceAudio::CloseInputStream>; |
| 441 | |
| 442 | rval->open_output_stream = |
| 443 | AudioThunker<GceAudio::OpenOutputStreamHAL_t>::call< |
| 444 | &GceAudio::OpenOutputStreamCurrentHAL>; |
| 445 | rval->close_output_stream = |
| 446 | AudioThunker<void(audio_stream_out*)>::call<&GceAudio::CloseOutputStream>; |
| 447 | |
| 448 | rval->dump = AudioThunker<int(int)>::call<&GceAudio::Dump>; |
| 449 | |
| 450 | *device = &rval->common; |
| 451 | return 0; |
| 452 | } |
| 453 | |
| 454 | int GceAudio::SetParameters(const char *kvpairs) { |
| 455 | ALOGE("GceAudio::%s: not implemented", __FUNCTION__); |
| 456 | if (kvpairs) D("GceAudio::%s: kvpairs %s", __FUNCTION__, kvpairs); |
| 457 | return 0; |
| 458 | } |
| 459 | |
| 460 | |
| 461 | char* GceAudio::GetParameters(const char *keys) const { |
| 462 | ALOGE("GceAudio::%s: not implemented", __FUNCTION__); |
| 463 | if (keys) D("GceAudio::%s: kvpairs %s", __FUNCTION__, keys); |
| 464 | return strdup(""); |
| 465 | } |
| 466 | |
| 467 | int GceAudio::SetStreamParameters( |
| 468 | struct audio_stream *stream, const char *kv_pairs) { |
| 469 | struct str_parms *parms = str_parms_create_str(kv_pairs); |
| 470 | if (!parms) { |
| 471 | return 0; |
| 472 | } |
| 473 | int sample_rate; |
| 474 | if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_SAMPLING_RATE, |
| 475 | &sample_rate) >= 0) { |
| 476 | stream->set_sample_rate(stream, sample_rate); |
| 477 | } |
| 478 | int format; |
| 479 | if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_FORMAT, |
| 480 | &format) >= 0) { |
| 481 | stream->set_format(stream, static_cast<audio_format_t>(format)); |
| 482 | } |
| 483 | int routing; |
| 484 | if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_ROUTING, |
| 485 | &routing) >= 0) { |
| 486 | stream->set_device(stream, static_cast<audio_devices_t>(routing)); |
| 487 | } |
| 488 | if (str_parms_get_int(parms, AUDIO_PARAMETER_STREAM_INPUT_SOURCE, |
| 489 | &routing) >= 0) { |
| 490 | stream->set_device(stream, static_cast<audio_devices_t>(routing)); |
| 491 | } |
| 492 | str_parms_destroy(parms); |
| 493 | return 0; |
| 494 | } |
| 495 | |
| 496 | } |