diff options
author | Hongwei Wang <hwwang@google.com> | 2018-02-26 17:23:23 -0800 |
---|---|---|
committer | Hongwei Wang <hwwang@google.com> | 2018-03-01 14:09:41 -0800 |
commit | e5962f17ab577d1bd7ccbd1bcfbcfefd069a6621 (patch) | |
tree | 32f7441aa4f7301235117c2ece8e877fec50db3a /emulator | |
parent | 7a44f9e397f2103ed71ada434735e853a5bd23d6 (diff) | |
download | device_generic_car-e5962f17ab577d1bd7ccbd1bcfbcfefd069a6621.tar.gz device_generic_car-e5962f17ab577d1bd7ccbd1bcfbcfefd069a6621.tar.bz2 device_generic_car-e5962f17ab577d1bd7ccbd1bcfbcfefd069a6621.zip |
Makes car emulator audio driver AOSP
- Grouped all emulator related work in emulator folder
- Moved also the default hal implementation here
- Deprecated the Android.mk in favor of Android.bp
Bug: 68940567
Test: lunch gcar_emu_x86-userdebug && m -j
Change-Id: I284d8b60301fcb9c7c65aed3c8a08e7e6c0ac0f2
Diffstat (limited to 'emulator')
-rw-r--r-- | emulator/audio/audio_policy_configuration.xml | 234 | ||||
-rw-r--r-- | emulator/audio/car_emulator_audio.mk | 29 | ||||
-rw-r--r-- | emulator/audio/driver/Android.bp | 41 | ||||
-rw-r--r-- | emulator/audio/driver/audio_hw.c | 1520 | ||||
-rw-r--r-- | emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml | 8 | ||||
-rw-r--r-- | emulator/audio/overlay/packages/services/Car/service/res/values/config.xml | 26 | ||||
-rw-r--r-- | emulator/hal/car_emulator_hal.mk | 24 | ||||
-rw-r--r-- | emulator/hal/car_emulator_manifest.xml | 205 |
8 files changed, 2087 insertions, 0 deletions
diff --git a/emulator/audio/audio_policy_configuration.xml b/emulator/audio/audio_policy_configuration.xml new file mode 100644 index 0000000..2bc3172 --- /dev/null +++ b/emulator/audio/audio_policy_configuration.xml @@ -0,0 +1,234 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude"> + <!-- version section contains a “version” tag in the form “major.minor” e.g version=”1.0” --> + + <!-- Global configuration Decalaration --> + <globalConfiguration speaker_drc_enabled="true"/> + + <!-- Modules section: + There is one section per audio HW module present on the platform. + Each module section will contains two mandatory tags for audio HAL “halVersion” and “name”. + The module names are the same as in current .conf file: + “primary”, “A2DP”, “remote_submix”, “USB” + Each module will contain the following sections: + “devicePorts”: a list of device descriptors for all input and output devices accessible via this + module. + This contains both permanently attached devices and removable devices. + "gain": constraints applied to the millibel values: + - maxValueMB >= minValueMB + - defaultValueMB >= minValueMB && defaultValueMB <= maxValueMB + - (maxValueMB - minValueMB) % stepValueMB == 0 + - (defaultValueMB - minValueMB) % stepValueMB == 0 + “mixPorts”: listing all output and input streams exposed by the audio HAL + “routes”: list of possible connections between input and output devices or between stream and + devices. + "route": is defined by an attribute: + -"type": <mux|mix> means all sources are mutual exclusive (mux) or can be mixed (mix) + -"sink": the sink involved in this route + -"sources": all the sources than can be connected to the sink via vis route + “attachedDevices”: permanently attached devices. + The attachedDevices section is a list of devices names. The names correspond to device names + defined in <devicePorts> section. + “defaultOutputDevice”: device to be used by default when no policy rule applies + --> + <modules> + <!-- Primary Audio HAL --> + <module name="primary" halVersion="3.0"> + <attachedDevices> + <!-- One bus per context --> + <item>bus0_media_out</item> + <item>bus1_navigation_out</item> + <item>bus2_voice_command_out</item> + <item>bus3_call_ring_out</item> + <item>bus4_call_out</item> + <item>bus5_alarm_out</item> + <item>bus6_notification_out</item> + <item>bus7_system_sound_out</item> + <item>bus0_mic1_in</item> + </attachedDevices> + <defaultOutputDevice>bus0_media_out</defaultOutputDevice> + <mixPorts> + <mixPort name="mixport_bus0_media_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus1_navigation_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus2_voice_command_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus3_call_ring_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus4_call_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus5_alarm_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus6_notification_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus7_system_sound_out" role="source" + flags="AUDIO_OUTPUT_FLAG_PRIMARY"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + </mixPort> + <mixPort name="mixport_bus0_mic1_in" role="sink"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" + channelMasks="AUDIO_CHANNEL_IN_STEREO"/> + </mixPort> + </mixPorts> + <devicePorts> + <devicePort tagName="bus0_media_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus0_media_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus1_navigation_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus1_navigation_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus2_voice_command_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus2_voice_command_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus3_call_ring_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus3_call_ring_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus4_call_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus4_call_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus5_alarm_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus5_alarm_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus6_notification_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus6_notification_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus7_system_sound_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" + address="bus7_system_sound_out"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> + <gains> + <gain name="" mode="AUDIO_GAIN_MODE_JOINT" + minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/> + </gains> + </devicePort> + <devicePort tagName="bus0_mic1_in" type="AUDIO_DEVICE_IN_BUS" role="source" + address="bus0_mic1_in"> + <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" + samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/> + </devicePort> + </devicePorts> + <!-- route declaration, i.e. list all available sources for a given sink --> + <routes> + <route type="mix" sink="bus0_media_out" sources="mixport_bus0_media_out"/> + <route type="mix" sink="bus1_navigation_out" sources="mixport_bus1_navigation_out"/> + <route type="mix" sink="bus2_voice_command_out" sources="mixport_bus2_voice_command_out"/> + <route type="mix" sink="bus3_call_ring_out" sources="mixport_bus3_call_ring_out"/> + <route type="mix" sink="bus4_call_out" sources="mixport_bus4_call_out"/> + <route type="mix" sink="bus5_alarm_out" sources="mixport_bus5_alarm_out"/> + <route type="mix" sink="bus6_notification_out" sources="mixport_bus6_notification_out"/> + <route type="mix" sink="bus7_system_sound_out" sources="mixport_bus7_system_sound_out"/> + <route type="mix" sink="mixport_bus0_mic1_in" sources="bus0_mic1_in"/> + </routes> + + </module> + + <!-- A2dp Audio HAL --> + <xi:include href="a2dp_audio_policy_configuration.xml"/> + + <!-- Usb Audio HAL --> + <xi:include href="usb_audio_policy_configuration.xml"/> + + <!-- Remote Submix Audio HAL --> + <xi:include href="r_submix_audio_policy_configuration.xml"/> + + </modules> + <!-- End of Modules section --> + + <!-- Volume section --> + + <xi:include href="audio_policy_volumes.xml"/> + <xi:include href="default_volume_tables.xml"/> + + <!-- End of Volume section --> + <!-- End of Modules section --> + +</audioPolicyConfiguration> diff --git a/emulator/audio/car_emulator_audio.mk b/emulator/audio/car_emulator_audio.mk new file mode 100644 index 0000000..a716538 --- /dev/null +++ b/emulator/audio/car_emulator_audio.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +USE_XML_AUDIO_POLICY_CONF := 1 + +PRODUCT_PACKAGES += audio.primary.caremu +PRODUCT_PROPERTY_OVERRIDES += ro.hardware.audio.primary=caremu + +PRODUCT_COPY_FILES += \ + frameworks/av/services/audiopolicy/config/a2dp_audio_policy_configuration.xml:system/etc/a2dp_audio_policy_configuration.xml \ + frameworks/av/services/audiopolicy/config/usb_audio_policy_configuration.xml:system/etc/usb_audio_policy_configuration.xml \ + frameworks/av/services/audiopolicy/config/r_submix_audio_policy_configuration.xml:system/etc/r_submix_audio_policy_configuration.xml \ + frameworks/av/services/audiopolicy/config/audio_policy_volumes.xml:system/etc/audio_policy_volumes.xml \ + frameworks/av/services/audiopolicy/config/default_volume_tables.xml:system/etc/default_volume_tables.xml \ + device/generic/car/emulator/audio/audio_policy_configuration.xml:system/etc/audio_policy_configuration.xml + +DEVICE_PACKAGE_OVERLAYS += device/generic/car/emulator/audio/overlay diff --git a/emulator/audio/driver/Android.bp b/emulator/audio/driver/Android.bp new file mode 100644 index 0000000..24aa96d --- /dev/null +++ b/emulator/audio/driver/Android.bp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Derived from device/generic/goldfish/audio/Android.mk + +cc_library_shared { + + vendor: true, + name: "audio.primary.caremu", + relative_install_path: "hw", + + srcs: ["audio_hw.c"], + + include_dirs: ["external/tinyalsa/include"], + + shared_libs: [ + "libcutils", + "liblog", + "libdl", + "libtinyalsa", + ], + + cflags: ["-Wno-unused-parameter"], + header_libs: [ + "libhardware_headers", + "libcutils_headers", + ], + +} diff --git a/emulator/audio/driver/audio_hw.c b/emulator/audio/driver/audio_hw.c new file mode 100644 index 0000000..24294ca --- /dev/null +++ b/emulator/audio/driver/audio_hw.c @@ -0,0 +1,1520 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Derived from goldfish/audio/audio_hw.c + * Changes made to adding support of AUDIO_DEVICE_OUT_BUS + */ + +#define LOG_TAG "audio_hw_generic" + +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/time.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#include <log/log.h> +#include <cutils/hashmap.h> +#include <cutils/str_parms.h> + +#include <hardware/hardware.h> +#include <system/audio.h> +#include <hardware/audio.h> +#include <tinyalsa/asoundlib.h> + +#define PCM_CARD 0 +#define PCM_DEVICE 0 + +#define OUT_PERIOD_MS 15 +#define OUT_PERIOD_COUNT 4 + +#define IN_PERIOD_MS 15 +#define IN_PERIOD_COUNT 4 + +struct generic_audio_device { + struct audio_hw_device device; // Constant after init + pthread_mutex_t lock; + bool mic_mute; // Proteced by this->lock + struct mixer *mixer; // Proteced by this->lock + Hashmap *out_bus_stream_map; // Extended field. Constant after init +}; + +static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state); + +typedef struct audio_vbuffer { + pthread_mutex_t lock; + uint8_t *data; + size_t frame_size; + size_t frame_count; + size_t head; + size_t tail; + size_t live; +} audio_vbuffer_t; + +static int audio_vbuffer_init(audio_vbuffer_t *audio_vbuffer, size_t frame_count, + size_t frame_size) { + if (!audio_vbuffer) { + return -EINVAL; + } + audio_vbuffer->frame_size = frame_size; + audio_vbuffer->frame_count = frame_count; + size_t bytes = frame_count * frame_size; + audio_vbuffer->data = calloc(bytes, 1); + if (!audio_vbuffer->data) { + return -ENOMEM; + } + audio_vbuffer->head = 0; + audio_vbuffer->tail = 0; + audio_vbuffer->live = 0; + pthread_mutex_init(&audio_vbuffer->lock, (const pthread_mutexattr_t *) NULL); + return 0; +} + +static int audio_vbuffer_destroy(audio_vbuffer_t *audio_vbuffer) { + if (!audio_vbuffer) { + return -EINVAL; + } + free(audio_vbuffer->data); + pthread_mutex_destroy(&audio_vbuffer->lock); + return 0; +} + +static int audio_vbuffer_live(audio_vbuffer_t *audio_vbuffer) { + if (!audio_vbuffer) { + return -EINVAL; + } + pthread_mutex_lock(&audio_vbuffer->lock); + int live = audio_vbuffer->live; + pthread_mutex_unlock(&audio_vbuffer->lock); + return live; +} + +static int audio_vbuffer_dead(audio_vbuffer_t *audio_vbuffer) { + if (!audio_vbuffer) { + return -EINVAL; + } + pthread_mutex_lock(&audio_vbuffer->lock); + int dead = audio_vbuffer->frame_count - audio_vbuffer->live; + pthread_mutex_unlock(&audio_vbuffer->lock); + return dead; +} + +#define MIN(a,b) (((a)<(b))?(a):(b)) +static size_t audio_vbuffer_write(audio_vbuffer_t *audio_vbuffer, const void *buffer, + size_t frame_count) { + size_t frames_written = 0; + pthread_mutex_lock(&audio_vbuffer->lock); + + while (frame_count != 0) { + int frames = 0; + if (audio_vbuffer->live == 0 || audio_vbuffer->head > audio_vbuffer->tail) { + frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->head); + } else if (audio_vbuffer->head < audio_vbuffer->tail) { + frames = MIN(frame_count, audio_vbuffer->tail - (audio_vbuffer->head)); + } else { + ALOGD("%s audio_vbuffer is full", __func__); + break; + } + memcpy(&audio_vbuffer->data[audio_vbuffer->head*audio_vbuffer->frame_size], + &((uint8_t*)buffer)[frames_written*audio_vbuffer->frame_size], + frames*audio_vbuffer->frame_size); + audio_vbuffer->live += frames; + frames_written += frames; + frame_count -= frames; + audio_vbuffer->head = (audio_vbuffer->head + frames) % audio_vbuffer->frame_count; + } + + pthread_mutex_unlock(&audio_vbuffer->lock); + return frames_written; +} + +static size_t audio_vbuffer_read(audio_vbuffer_t *audio_vbuffer, void *buffer, + size_t frame_count) { + size_t frames_read = 0; + pthread_mutex_lock(&audio_vbuffer->lock); + + while (frame_count != 0) { + int frames = 0; + if (audio_vbuffer->live == audio_vbuffer->frame_count || + audio_vbuffer->tail > audio_vbuffer->head) { + frames = MIN(frame_count, audio_vbuffer->frame_count - audio_vbuffer->tail); + } else if (audio_vbuffer->tail < audio_vbuffer->head) { + frames = MIN(frame_count, audio_vbuffer->head - audio_vbuffer->tail); + } else { + break; + } + memcpy(&((uint8_t*)buffer)[frames_read*audio_vbuffer->frame_size], + &audio_vbuffer->data[audio_vbuffer->tail*audio_vbuffer->frame_size], + frames*audio_vbuffer->frame_size); + audio_vbuffer->live -= frames; + frames_read += frames; + frame_count -= frames; + audio_vbuffer->tail = (audio_vbuffer->tail + frames) % audio_vbuffer->frame_count; + } + + pthread_mutex_unlock(&audio_vbuffer->lock); + return frames_read; +} + +struct generic_stream_out { + struct audio_stream_out stream; // Constant after init + pthread_mutex_t lock; + struct generic_audio_device *dev; // Constant after init + audio_devices_t device; // Protected by this->lock + struct audio_config req_config; // Constant after init + struct pcm_config pcm_config; // Constant after init + audio_vbuffer_t buffer; // Constant after init + const char *bus_address; // Extended field. Constant after init + + // Time & Position Keeping + bool standby; // Protected by this->lock + uint64_t underrun_position; // Protected by this->lock + struct timespec underrun_time; // Protected by this->lock + uint64_t last_write_time_us; // Protected by this->lock + uint64_t frames_total_buffered; // Protected by this->lock + uint64_t frames_written; // Protected by this->lock + uint64_t frames_rendered; // Protected by this->lock + + // Worker + pthread_t worker_thread; // Constant after init + pthread_cond_t worker_wake; // Protected by this->lock + bool worker_standby; // Protected by this->lock + bool worker_exit; // Protected by this->lock +}; + +struct generic_stream_in { + struct audio_stream_in stream; // Constant after init + pthread_mutex_t lock; + struct generic_audio_device *dev; // Constant after init + audio_devices_t device; // Protected by this->lock + struct audio_config req_config; // Constant after init + struct pcm *pcm; // Protected by this->lock + struct pcm_config pcm_config; // Constant after init + int16_t *stereo_to_mono_buf; // Protected by this->lock + size_t stereo_to_mono_buf_size; // Protected by this->lock + audio_vbuffer_t buffer; // Protected by this->lock + const char *bus_address; // Extended field. Constant after init + + // Time & Position Keeping + bool standby; // Protected by this->lock + int64_t standby_position; // Protected by this->lock + struct timespec standby_exit_time;// Protected by this->lock + int64_t standby_frames_read; // Protected by this->lock + + // Worker + pthread_t worker_thread; // Constant after init + pthread_cond_t worker_wake; // Protected by this->lock + bool worker_standby; // Protected by this->lock + bool worker_exit; // Protected by this->lock +}; + +static struct pcm_config pcm_config_out = { + .channels = 2, + .rate = 0, + .period_size = 0, + .period_count = OUT_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, +}; + +static struct pcm_config pcm_config_in = { + .channels = 2, + .rate = 0, + .period_size = 0, + .period_count = IN_PERIOD_COUNT, + .format = PCM_FORMAT_S16_LE, + .start_threshold = 0, + .stop_threshold = INT_MAX, +}; + +static pthread_mutex_t adev_init_lock = PTHREAD_MUTEX_INITIALIZER; +static unsigned int audio_device_ref_count = 0; + +static uint32_t out_get_sample_rate(const struct audio_stream *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + return out->req_config.sample_rate; +} + +static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) { + return -ENOSYS; +} + +static size_t out_get_buffer_size(const struct audio_stream *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + int size = out->pcm_config.period_size * + audio_stream_out_frame_size(&out->stream); + + return size; +} + +static audio_channel_mask_t out_get_channels(const struct audio_stream *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + return out->req_config.channel_mask; +} + +static audio_format_t out_get_format(const struct audio_stream *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + return out->req_config.format; +} + +static int out_set_format(struct audio_stream *stream, audio_format_t format) { + return -ENOSYS; +} + +static int out_dump(const struct audio_stream *stream, int fd) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + pthread_mutex_lock(&out->lock); + dprintf(fd, "\tout_dump:\n" + "\t\taddress: %s\n" + "\t\tsample rate: %u\n" + "\t\tbuffer size: %zu\n" + "\t\tchannel mask: %08x\n" + "\t\tformat: %d\n" + "\t\tdevice: %08x\n" + "\t\taudio dev: %p\n\n", + out->bus_address, + out_get_sample_rate(stream), + out_get_buffer_size(stream), + out_get_channels(stream), + out_get_format(stream), + out->device, + out->dev); + pthread_mutex_unlock(&out->lock); + return 0; +} + +static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + struct str_parms *parms; + char value[32]; + int ret; + long val; + char *end; + + pthread_mutex_lock(&out->lock); + if (!out->standby) { + //Do not support changing params while stream running + ret = -ENOSYS; + } else { + parms = str_parms_create_str(kvpairs); + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, + value, sizeof(value)); + if (ret >= 0) { + errno = 0; + val = strtol(value, &end, 10); + if (errno == 0 && (end != NULL) && (*end == '\0') && ((int)val == val)) { + out->device = (int)val; + ret = 0; + } else { + ret = -EINVAL; + } + } + str_parms_destroy(parms); + } + pthread_mutex_unlock(&out->lock); + return ret; +} + +static char *out_get_parameters(const struct audio_stream *stream, const char *keys) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + struct str_parms *query = str_parms_create_str(keys); + char *str; + char value[256]; + struct str_parms *reply = str_parms_create(); + int ret; + + ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); + if (ret >= 0) { + pthread_mutex_lock(&out->lock); + str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, out->device); + pthread_mutex_unlock(&out->lock); + str = strdup(str_parms_to_str(reply)); + } else { + str = strdup(keys); + } + + str_parms_destroy(query); + str_parms_destroy(reply); + return str; +} + +static uint32_t out_get_latency(const struct audio_stream_out *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + return (out->pcm_config.period_size * 1000) / out->pcm_config.rate; +} + +static int out_set_volume(struct audio_stream_out *stream, + float left, float right) { + return -ENOSYS; +} + +static void *out_write_worker(void *args) { + struct generic_stream_out *out = (struct generic_stream_out *)args; + struct pcm *pcm = NULL; + uint8_t *buffer = NULL; + int buffer_frames; + int buffer_size; + bool restart = false; + bool shutdown = false; + while (true) { + pthread_mutex_lock(&out->lock); + while (out->worker_standby || restart) { + restart = false; + if (pcm) { + pcm_close(pcm); // Frees pcm + pcm = NULL; + free(buffer); + buffer=NULL; + } + if (out->worker_exit) { + break; + } + pthread_cond_wait(&out->worker_wake, &out->lock); + } + + if (out->worker_exit) { + if (!out->worker_standby) { + ALOGE("Out worker:%s not in standby before exiting", out->bus_address); + } + shutdown = true; + } + + while (!shutdown && audio_vbuffer_live(&out->buffer) == 0) { + pthread_cond_wait(&out->worker_wake, &out->lock); + } + + if (shutdown) { + pthread_mutex_unlock(&out->lock); + break; + } + + if (!pcm) { + pcm = pcm_open(PCM_CARD, PCM_DEVICE, + PCM_OUT | PCM_MONOTONIC, &out->pcm_config); + if (!pcm_is_ready(pcm)) { + ALOGE("pcm_open(out) failed: %s: address %s channels %d format %d rate %d", + pcm_get_error(pcm), + out->bus_address, + out->pcm_config.channels, + out->pcm_config.format, + out->pcm_config.rate); + pthread_mutex_unlock(&out->lock); + break; + } + buffer_frames = out->pcm_config.period_size; + buffer_size = pcm_frames_to_bytes(pcm, buffer_frames); + buffer = malloc(buffer_size); + if (!buffer) { + ALOGE("could not allocate write buffer"); + pthread_mutex_unlock(&out->lock); + break; + } + } + int frames = audio_vbuffer_read(&out->buffer, buffer, buffer_frames); + pthread_mutex_unlock(&out->lock); + int write_error = pcm_write(pcm, buffer, pcm_frames_to_bytes(pcm, frames)); + if (write_error) { + ALOGE("pcm_write failed %s address %s", pcm_get_error(pcm), out->bus_address); + restart = true; + } else { + ALOGD("pcm_write succeed address %s", out->bus_address); + } + } + if (buffer) { + free(buffer); + } + + return NULL; +} + +// Call with in->lock held +static void get_current_output_position(struct generic_stream_out *out, + uint64_t *position, struct timespec * timestamp) { + struct timespec curtime = { .tv_sec = 0, .tv_nsec = 0 }; + clock_gettime(CLOCK_MONOTONIC, &curtime); + const int64_t now_us = (curtime.tv_sec * 1000000000LL + curtime.tv_nsec) / 1000; + if (timestamp) { + *timestamp = curtime; + } + int64_t position_since_underrun; + if (out->standby) { + position_since_underrun = 0; + } else { + const int64_t first_us = (out->underrun_time.tv_sec * 1000000000LL + + out->underrun_time.tv_nsec) / 1000; + position_since_underrun = (now_us - first_us) * + out_get_sample_rate(&out->stream.common) / + 1000000; + if (position_since_underrun < 0) { + position_since_underrun = 0; + } + } + *position = out->underrun_position + position_since_underrun; + + // The device will reuse the same output stream leading to periods of + // underrun. + if (*position > out->frames_written) { + ALOGW("Not supplying enough data to HAL, expected position %" PRIu64 " , only wrote " + "%" PRIu64, + *position, out->frames_written); + + *position = out->frames_written; + out->underrun_position = *position; + out->underrun_time = curtime; + out->frames_total_buffered = 0; + } +} + +static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, size_t bytes) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + const size_t frames = bytes / audio_stream_out_frame_size(stream); + + pthread_mutex_lock(&out->lock); + + if (out->worker_standby) { + out->worker_standby = false; + } + + uint64_t current_position; + struct timespec current_time; + + get_current_output_position(out, ¤t_position, ¤t_time); + const uint64_t now_us = (current_time.tv_sec * 1000000000LL + + current_time.tv_nsec) / 1000; + if (out->standby) { + out->standby = false; + out->underrun_time = current_time; + out->frames_rendered = 0; + out->frames_total_buffered = 0; + } + + size_t frames_written = audio_vbuffer_write(&out->buffer, buffer, frames); + pthread_cond_signal(&out->worker_wake); + + /* Implementation just consumes bytes if we start getting backed up */ + out->frames_written += frames; + out->frames_rendered += frames; + out->frames_total_buffered += frames; + + // We simulate the audio device blocking when it's write buffers become + // full. + + // At the beginning or after an underrun, try to fill up the vbuffer. + // This will be throttled by the PlaybackThread + int frames_sleep = out->frames_total_buffered < out->buffer.frame_count ? 0 : frames; + + uint64_t sleep_time_us = frames_sleep * 1000000LL / + out_get_sample_rate(&stream->common); + + // If the write calls are delayed, subtract time off of the sleep to + // compensate + uint64_t time_since_last_write_us = now_us - out->last_write_time_us; + if (time_since_last_write_us < sleep_time_us) { + sleep_time_us -= time_since_last_write_us; + } else { + sleep_time_us = 0; + } + out->last_write_time_us = now_us + sleep_time_us; + + pthread_mutex_unlock(&out->lock); + + if (sleep_time_us > 0) { + usleep(sleep_time_us); + } + + if (frames_written < frames) { + ALOGW("Hardware backing HAL too slow, could only write %zu of %zu frames", + frames_written, frames); + } + + /* Always consume all bytes */ + return bytes; +} + +static int out_get_presentation_position(const struct audio_stream_out *stream, + uint64_t *frames, struct timespec *timestamp) { + int ret = -EINVAL; + if (stream == NULL || frames == NULL || timestamp == NULL) { + return -EINVAL; + } + struct generic_stream_out *out = (struct generic_stream_out *)stream; + + pthread_mutex_lock(&out->lock); + get_current_output_position(out, frames, timestamp); + pthread_mutex_unlock(&out->lock); + + return 0; +} + +static int out_get_render_position(const struct audio_stream_out *stream, uint32_t *dsp_frames) { + if (stream == NULL || dsp_frames == NULL) { + return -EINVAL; + } + struct generic_stream_out *out = (struct generic_stream_out *)stream; + pthread_mutex_lock(&out->lock); + *dsp_frames = out->frames_rendered; + pthread_mutex_unlock(&out->lock); + return 0; +} + +// Must be called with out->lock held +static void do_out_standby(struct generic_stream_out *out) { + int frames_sleep = 0; + uint64_t sleep_time_us = 0; + if (out->standby) { + return; + } + while (true) { + get_current_output_position(out, &out->underrun_position, NULL); + frames_sleep = out->frames_written - out->underrun_position; + + if (frames_sleep == 0) { + break; + } + + sleep_time_us = frames_sleep * 1000000LL / + out_get_sample_rate(&out->stream.common); + + pthread_mutex_unlock(&out->lock); + usleep(sleep_time_us); + pthread_mutex_lock(&out->lock); + } + out->worker_standby = true; + out->standby = true; +} + +static int out_standby(struct audio_stream *stream) { + struct generic_stream_out *out = (struct generic_stream_out *)stream; + pthread_mutex_lock(&out->lock); + do_out_standby(out); + pthread_mutex_unlock(&out->lock); + return 0; +} + +static int out_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { + // out_add_audio_effect is a no op + return 0; +} + +static int out_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { + // out_remove_audio_effect is a no op + return 0; +} + +static int out_get_next_write_timestamp(const struct audio_stream_out *stream, + int64_t *timestamp) { + return -ENOSYS; +} + +static uint32_t in_get_sample_rate(const struct audio_stream *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + return in->req_config.sample_rate; +} + +static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) { + return -ENOSYS; +} + +static int refine_output_parameters(uint32_t *sample_rate, audio_format_t *format, + audio_channel_mask_t *channel_mask) { + static const uint32_t sample_rates [] = { + 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000 + }; + static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t); + bool inval = false; + if (*format != AUDIO_FORMAT_PCM_16_BIT) { + *format = AUDIO_FORMAT_PCM_16_BIT; + inval = true; + } + + int channel_count = popcount(*channel_mask); + if (channel_count != 1 && channel_count != 2) { + *channel_mask = AUDIO_CHANNEL_IN_STEREO; + inval = true; + } + + int i; + for (i = 0; i < sample_rates_count; i++) { + if (*sample_rate < sample_rates[i]) { + *sample_rate = sample_rates[i]; + inval=true; + break; + } + else if (*sample_rate == sample_rates[i]) { + break; + } + else if (i == sample_rates_count-1) { + // Cap it to the highest rate we support + *sample_rate = sample_rates[i]; + inval=true; + } + } + + if (inval) { + return -EINVAL; + } + return 0; +} + +static int refine_input_parameters(uint32_t *sample_rate, audio_format_t *format, + audio_channel_mask_t *channel_mask) { + static const uint32_t sample_rates [] = {8000, 11025, 16000, 22050, 44100, 48000}; + static const int sample_rates_count = sizeof(sample_rates)/sizeof(uint32_t); + bool inval = false; + // Only PCM_16_bit is supported. If this is changed, stereo to mono drop + // must be fixed in in_read + if (*format != AUDIO_FORMAT_PCM_16_BIT) { + *format = AUDIO_FORMAT_PCM_16_BIT; + inval = true; + } + + int channel_count = popcount(*channel_mask); + if (channel_count != 1 && channel_count != 2) { + *channel_mask = AUDIO_CHANNEL_IN_STEREO; + inval = true; + } + + int i; + for (i = 0; i < sample_rates_count; i++) { + if (*sample_rate < sample_rates[i]) { + *sample_rate = sample_rates[i]; + inval=true; + break; + } + else if (*sample_rate == sample_rates[i]) { + break; + } + else if (i == sample_rates_count-1) { + // Cap it to the highest rate we support + *sample_rate = sample_rates[i]; + inval=true; + } + } + + if (inval) { + return -EINVAL; + } + return 0; +} + +static size_t get_input_buffer_size(uint32_t sample_rate, audio_format_t format, + audio_channel_mask_t channel_mask) { + size_t size; + size_t device_rate; + int channel_count = popcount(channel_mask); + if (refine_input_parameters(&sample_rate, &format, &channel_mask) != 0) + return 0; + + size = sample_rate*IN_PERIOD_MS/1000; + // Audioflinger expects audio buffers to be multiple of 16 frames + size = ((size + 15) / 16) * 16; + size *= sizeof(short) * channel_count; + + return size; +} + +static size_t in_get_buffer_size(const struct audio_stream *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + int size = get_input_buffer_size(in->req_config.sample_rate, + in->req_config.format, + in->req_config.channel_mask); + + return size; +} + +static audio_channel_mask_t in_get_channels(const struct audio_stream *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + return in->req_config.channel_mask; +} + +static audio_format_t in_get_format(const struct audio_stream *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + return in->req_config.format; +} + +static int in_set_format(struct audio_stream *stream, audio_format_t format) { + return -ENOSYS; +} + +static int in_dump(const struct audio_stream *stream, int fd) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + + pthread_mutex_lock(&in->lock); + dprintf(fd, "\tin_dump:\n" + "\t\tsample rate: %u\n" + "\t\tbuffer size: %zu\n" + "\t\tchannel mask: %08x\n" + "\t\tformat: %d\n" + "\t\tdevice: %08x\n" + "\t\taudio dev: %p\n\n", + in_get_sample_rate(stream), + in_get_buffer_size(stream), + in_get_channels(stream), + in_get_format(stream), + in->device, + in->dev); + pthread_mutex_unlock(&in->lock); + return 0; +} + +static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + struct str_parms *parms; + char value[32]; + int ret; + long val; + char *end; + + pthread_mutex_lock(&in->lock); + if (!in->standby) { + ret = -ENOSYS; + } else { + parms = str_parms_create_str(kvpairs); + + ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING, + value, sizeof(value)); + if (ret >= 0) { + errno = 0; + val = strtol(value, &end, 10); + if ((errno == 0) && (end != NULL) && (*end == '\0') && ((int)val == val)) { + in->device = (int)val; + ret = 0; + } else { + ret = -EINVAL; + } + } + + str_parms_destroy(parms); + } + pthread_mutex_unlock(&in->lock); + return ret; +} + +static char *in_get_parameters(const struct audio_stream *stream, const char *keys) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + struct str_parms *query = str_parms_create_str(keys); + char *str; + char value[256]; + struct str_parms *reply = str_parms_create(); + int ret; + + ret = str_parms_get_str(query, AUDIO_PARAMETER_STREAM_ROUTING, value, sizeof(value)); + if (ret >= 0) { + str_parms_add_int(reply, AUDIO_PARAMETER_STREAM_ROUTING, in->device); + str = strdup(str_parms_to_str(reply)); + } else { + str = strdup(keys); + } + + str_parms_destroy(query); + str_parms_destroy(reply); + return str; +} + +static int in_set_gain(struct audio_stream_in *stream, float gain) { + // TODO(hwwang): support adjusting input gain + return 0; +} + +// Call with in->lock held +static void get_current_input_position(struct generic_stream_in *in, + int64_t * position, struct timespec * timestamp) { + struct timespec t = { .tv_sec = 0, .tv_nsec = 0 }; + clock_gettime(CLOCK_MONOTONIC, &t); + const int64_t now_us = (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000; + if (timestamp) { + *timestamp = t; + } + int64_t position_since_standby; + if (in->standby) { + position_since_standby = 0; + } else { + const int64_t first_us = (in->standby_exit_time.tv_sec * 1000000000LL + + in->standby_exit_time.tv_nsec) / 1000; + position_since_standby = (now_us - first_us) * + in_get_sample_rate(&in->stream.common) / + 1000000; + if (position_since_standby < 0) { + position_since_standby = 0; + } + } + *position = in->standby_position + position_since_standby; +} + +// Must be called with in->lock held +static void do_in_standby(struct generic_stream_in *in) { + if (in->standby) { + return; + } + in->worker_standby = true; + get_current_input_position(in, &in->standby_position, NULL); + in->standby = true; +} + +static int in_standby(struct audio_stream *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + pthread_mutex_lock(&in->lock); + do_in_standby(in); + pthread_mutex_unlock(&in->lock); + return 0; +} + +static void *in_read_worker(void *args) { + struct generic_stream_in *in = (struct generic_stream_in *)args; + struct pcm *pcm = NULL; + uint8_t *buffer = NULL; + size_t buffer_frames; + int buffer_size; + + bool restart = false; + bool shutdown = false; + while (true) { + pthread_mutex_lock(&in->lock); + while (in->worker_standby || restart) { + restart = false; + if (pcm) { + pcm_close(pcm); // Frees pcm + pcm = NULL; + free(buffer); + buffer=NULL; + } + if (in->worker_exit) { + break; + } + pthread_cond_wait(&in->worker_wake, &in->lock); + } + + if (in->worker_exit) { + if (!in->worker_standby) { + ALOGE("In worker not in standby before exiting"); + } + shutdown = true; + } + if (shutdown) { + pthread_mutex_unlock(&in->lock); + break; + } + if (!pcm) { + pcm = pcm_open(PCM_CARD, PCM_DEVICE, + PCM_IN | PCM_MONOTONIC, &in->pcm_config); + if (!pcm_is_ready(pcm)) { + ALOGE("pcm_open(in) failed: %s: channels %d format %d rate %d", + pcm_get_error(pcm), + in->pcm_config.channels, + in->pcm_config.format, + in->pcm_config.rate); + pthread_mutex_unlock(&in->lock); + break; + } + buffer_frames = in->pcm_config.period_size; + buffer_size = pcm_frames_to_bytes(pcm, buffer_frames); + buffer = malloc(buffer_size); + if (!buffer) { + ALOGE("could not allocate worker read buffer"); + pthread_mutex_unlock(&in->lock); + break; + } + } + pthread_mutex_unlock(&in->lock); + int ret = pcm_read(pcm, buffer, pcm_frames_to_bytes(pcm, buffer_frames)); + if (ret != 0) { + ALOGW("pcm_read failed %s", pcm_get_error(pcm)); + restart = true; + } + + pthread_mutex_lock(&in->lock); + size_t frames_written = audio_vbuffer_write(&in->buffer, buffer, buffer_frames); + pthread_mutex_unlock(&in->lock); + + if (frames_written != buffer_frames) { + ALOGW("in_read_worker only could write %zu / %zu frames", + frames_written, buffer_frames); + } + } + if (buffer) { + free(buffer); + } + return NULL; +} + +static ssize_t in_read(struct audio_stream_in *stream, void *buffer, size_t bytes) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + struct generic_audio_device *adev = in->dev; + const size_t frames = bytes / audio_stream_in_frame_size(stream); + int ret = 0; + bool mic_mute = false; + size_t read_bytes = 0; + + adev_get_mic_mute(&adev->device, &mic_mute); + pthread_mutex_lock(&in->lock); + + if (in->worker_standby) { + in->worker_standby = false; + } + pthread_cond_signal(&in->worker_wake); + + int64_t current_position; + struct timespec current_time; + + get_current_input_position(in, ¤t_position, ¤t_time); + if (in->standby) { + in->standby = false; + in->standby_exit_time = current_time; + in->standby_frames_read = 0; + } + + const int64_t frames_available = + current_position - in->standby_position - in->standby_frames_read; + assert(frames_available >= 0); + + const size_t frames_wait = + ((uint64_t)frames_available > frames) ? 0 : frames - frames_available; + + int64_t sleep_time_us = frames_wait * 1000000LL / in_get_sample_rate(&stream->common); + + pthread_mutex_unlock(&in->lock); + + if (sleep_time_us > 0) { + usleep(sleep_time_us); + } + + pthread_mutex_lock(&in->lock); + int read_frames = 0; + if (in->standby) { + ALOGW("Input put to sleep while read in progress"); + goto exit; + } + in->standby_frames_read += frames; + + if (popcount(in->req_config.channel_mask) == 1 && + in->pcm_config.channels == 2) { + // Need to resample to mono + if (in->stereo_to_mono_buf_size < bytes*2) { + in->stereo_to_mono_buf = realloc(in->stereo_to_mono_buf, bytes*2); + if (!in->stereo_to_mono_buf) { + ALOGE("Failed to allocate stereo_to_mono_buff"); + goto exit; + } + } + + read_frames = audio_vbuffer_read(&in->buffer, in->stereo_to_mono_buf, frames); + + // Currently only pcm 16 is supported. + uint16_t *src = (uint16_t *)in->stereo_to_mono_buf; + uint16_t *dst = (uint16_t *)buffer; + size_t i; + // Resample stereo 16 to mono 16 by dropping one channel. + // The stereo stream is interleaved L-R-L-R + for (i = 0; i < frames; i++) { + *dst = *src; + src += 2; + dst += 1; + } + } else { + read_frames = audio_vbuffer_read(&in->buffer, buffer, frames); + } + +exit: + read_bytes = read_frames*audio_stream_in_frame_size(stream); + + if (mic_mute) { + read_bytes = 0; + } + + if (read_bytes < bytes) { + memset (&((uint8_t *)buffer)[read_bytes], 0, bytes-read_bytes); + } + + pthread_mutex_unlock(&in->lock); + + return bytes; +} + +static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) { + return 0; +} + +static int in_get_capture_position(const struct audio_stream_in *stream, + int64_t *frames, int64_t *time) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + pthread_mutex_lock(&in->lock); + struct timespec current_time; + get_current_input_position(in, frames, ¤t_time); + *time = (current_time.tv_sec * 1000000000LL + current_time.tv_nsec); + pthread_mutex_unlock(&in->lock); + return 0; +} + +static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { + // in_add_audio_effect is a no op + return 0; +} + +static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect) { + // in_add_audio_effect is a no op + return 0; +} + +static int adev_open_output_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, audio_devices_t devices, audio_output_flags_t flags, + struct audio_config *config, struct audio_stream_out **stream_out, const char *address) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + struct generic_stream_out *out; + int ret = 0; + + if (refine_output_parameters(&config->sample_rate, &config->format, &config->channel_mask)) { + ALOGE("Error opening output stream format %d, channel_mask %04x, sample_rate %u", + config->format, config->channel_mask, config->sample_rate); + ret = -EINVAL; + goto error; + } + + out = (struct generic_stream_out *)calloc(1, sizeof(struct generic_stream_out)); + + if (!out) + return -ENOMEM; + + out->stream.common.get_sample_rate = out_get_sample_rate; + out->stream.common.set_sample_rate = out_set_sample_rate; + out->stream.common.get_buffer_size = out_get_buffer_size; + out->stream.common.get_channels = out_get_channels; + out->stream.common.get_format = out_get_format; + out->stream.common.set_format = out_set_format; + out->stream.common.standby = out_standby; + out->stream.common.dump = out_dump; + out->stream.common.set_parameters = out_set_parameters; + out->stream.common.get_parameters = out_get_parameters; + out->stream.common.add_audio_effect = out_add_audio_effect; + out->stream.common.remove_audio_effect = out_remove_audio_effect; + out->stream.get_latency = out_get_latency; + out->stream.set_volume = out_set_volume; + out->stream.write = out_write; + out->stream.get_render_position = out_get_render_position; + out->stream.get_presentation_position = out_get_presentation_position; + out->stream.get_next_write_timestamp = out_get_next_write_timestamp; + + pthread_mutex_init(&out->lock, (const pthread_mutexattr_t *) NULL); + out->dev = adev; + out->device = devices; + memcpy(&out->req_config, config, sizeof(struct audio_config)); + memcpy(&out->pcm_config, &pcm_config_out, sizeof(struct pcm_config)); + out->pcm_config.rate = config->sample_rate; + out->pcm_config.period_size = out->pcm_config.rate*OUT_PERIOD_MS/1000; + + out->standby = true; + out->underrun_position = 0; + out->underrun_time.tv_sec = 0; + out->underrun_time.tv_nsec = 0; + out->last_write_time_us = 0; + out->frames_total_buffered = 0; + out->frames_written = 0; + out->frames_rendered = 0; + + ret = audio_vbuffer_init(&out->buffer, + out->pcm_config.period_size*out->pcm_config.period_count, + out->pcm_config.channels * + pcm_format_to_bits(out->pcm_config.format) >> 3); + if (ret == 0) { + pthread_cond_init(&out->worker_wake, NULL); + out->worker_standby = true; + out->worker_exit = false; + pthread_create(&out->worker_thread, NULL, out_write_worker, out); + } + + if (address) { + out->bus_address = calloc(strlen(address) + 1, sizeof(char)); + strncpy(out->bus_address, address, strlen(address)); + hashmapPut(adev->out_bus_stream_map, out->bus_address, out); + } + *stream_out = &out->stream; + ALOGD("%s bus:%s", __func__, out->bus_address); + +error: + return ret; +} + +static void adev_close_output_stream(struct audio_hw_device *dev, + struct audio_stream_out *stream) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + struct generic_stream_out *out = (struct generic_stream_out *)stream; + ALOGD("%s bus:%s", __func__, out->bus_address); + pthread_mutex_lock(&out->lock); + do_out_standby(out); + + out->worker_exit = true; + pthread_cond_signal(&out->worker_wake); + pthread_mutex_unlock(&out->lock); + + pthread_join(out->worker_thread, NULL); + pthread_mutex_destroy(&out->lock); + audio_vbuffer_destroy(&out->buffer); + + if (out->bus_address) { + hashmapRemove(adev->out_bus_stream_map, out->bus_address); + free(out->bus_address); + } + free(stream); +} + +static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs) { + return 0; +} + +static char *adev_get_parameters(const struct audio_hw_device *dev, const char *keys) { + return NULL; +} + +static int adev_init_check(const struct audio_hw_device *dev) { + return 0; +} + +static int adev_set_voice_volume(struct audio_hw_device *dev, float volume) { + // adev_set_voice_volume is a no op (simulates phones) + return 0; +} + +static int adev_set_master_volume(struct audio_hw_device *dev, float volume) { + return -ENOSYS; +} + +static int adev_get_master_volume(struct audio_hw_device *dev, float *volume) { + return -ENOSYS; +} + +static int adev_set_master_mute(struct audio_hw_device *dev, bool muted) { + return -ENOSYS; +} + +static int adev_get_master_mute(struct audio_hw_device *dev, bool *muted) { + return -ENOSYS; +} + +static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode) { + // adev_set_mode is a no op (simulates phones) + return 0; +} + +static int adev_set_mic_mute(struct audio_hw_device *dev, bool state) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + pthread_mutex_lock(&adev->lock); + adev->mic_mute = state; + pthread_mutex_unlock(&adev->lock); + return 0; +} + +static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + pthread_mutex_lock(&adev->lock); + *state = adev->mic_mute; + pthread_mutex_unlock(&adev->lock); + return 0; +} + +static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev, + const struct audio_config *config) { + return get_input_buffer_size(config->sample_rate, config->format, config->channel_mask); +} + +static void adev_close_input_stream(struct audio_hw_device *dev, + struct audio_stream_in *stream) { + struct generic_stream_in *in = (struct generic_stream_in *)stream; + pthread_mutex_lock(&in->lock); + do_in_standby(in); + + in->worker_exit = true; + pthread_cond_signal(&in->worker_wake); + pthread_mutex_unlock(&in->lock); + pthread_join(in->worker_thread, NULL); + + if (in->stereo_to_mono_buf != NULL) { + free(in->stereo_to_mono_buf); + in->stereo_to_mono_buf_size = 0; + } + + if (in->bus_address) { + free(in->bus_address); + } + + pthread_mutex_destroy(&in->lock); + audio_vbuffer_destroy(&in->buffer); + free(stream); +} + +static int adev_open_input_stream(struct audio_hw_device *dev, + audio_io_handle_t handle, audio_devices_t devices, struct audio_config *config, + struct audio_stream_in **stream_in, audio_input_flags_t flags __unused, const char *address, + audio_source_t source __unused) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + struct generic_stream_in *in; + int ret = 0; + if (refine_input_parameters(&config->sample_rate, &config->format, &config->channel_mask)) { + ALOGE("Error opening input stream format %d, channel_mask %04x, sample_rate %u", + config->format, config->channel_mask, config->sample_rate); + ret = -EINVAL; + goto error; + } + + in = (struct generic_stream_in *)calloc(1, sizeof(struct generic_stream_in)); + if (!in) { + ret = -ENOMEM; + goto error; + } + + in->stream.common.get_sample_rate = in_get_sample_rate; + in->stream.common.set_sample_rate = in_set_sample_rate; // no op + in->stream.common.get_buffer_size = in_get_buffer_size; + in->stream.common.get_channels = in_get_channels; + in->stream.common.get_format = in_get_format; + in->stream.common.set_format = in_set_format; // no op + in->stream.common.standby = in_standby; + in->stream.common.dump = in_dump; + in->stream.common.set_parameters = in_set_parameters; + in->stream.common.get_parameters = in_get_parameters; + in->stream.common.add_audio_effect = in_add_audio_effect; // no op + in->stream.common.remove_audio_effect = in_remove_audio_effect; // no op + in->stream.set_gain = in_set_gain; // no op + in->stream.read = in_read; + in->stream.get_input_frames_lost = in_get_input_frames_lost; // no op + in->stream.get_capture_position = in_get_capture_position; + + pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *) NULL); + in->dev = adev; + in->device = devices; + memcpy(&in->req_config, config, sizeof(struct audio_config)); + memcpy(&in->pcm_config, &pcm_config_in, sizeof(struct pcm_config)); + in->pcm_config.rate = config->sample_rate; + in->pcm_config.period_size = in->pcm_config.rate*IN_PERIOD_MS/1000; + + in->stereo_to_mono_buf = NULL; + in->stereo_to_mono_buf_size = 0; + + in->standby = true; + in->standby_position = 0; + in->standby_exit_time.tv_sec = 0; + in->standby_exit_time.tv_nsec = 0; + in->standby_frames_read = 0; + + ret = audio_vbuffer_init(&in->buffer, + in->pcm_config.period_size*in->pcm_config.period_count, + in->pcm_config.channels * + pcm_format_to_bits(in->pcm_config.format) >> 3); + if (ret == 0) { + pthread_cond_init(&in->worker_wake, NULL); + in->worker_standby = true; + in->worker_exit = false; + pthread_create(&in->worker_thread, NULL, in_read_worker, in); + } + + if (address) { + in->bus_address = calloc(strlen(address) + 1, sizeof(char)); + strncpy(in->bus_address, address, strlen(address)); + } + + *stream_in = &in->stream; + +error: + return ret; +} + +static int adev_dump(const audio_hw_device_t *dev, int fd) { + return 0; +} + +static int adev_set_audio_port_config(struct audio_hw_device *dev, + const struct audio_port_config *config) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + const char *bus_address = config->ext.device.address; + struct generic_stream_out *out = hashmapGet(adev->out_bus_stream_map, bus_address); + if (out) { + // TODO set the actual audio port gain on physical bus + ALOGD("%s: set audio gain: %d on bus_address:%s", + __func__, config->gain.values[0], bus_address); + } else { + ALOGE("%s: can not find output stream by bus_address:%s", __func__, bus_address); + } + return 0; +} + +static int adev_create_audio_patch(struct audio_hw_device *dev, + unsigned int num_sources, + const struct audio_port_config *sources, + unsigned int num_sinks, + const struct audio_port_config *sinks, + audio_patch_handle_t *handle) { + return 0; +} + +static int adev_release_audio_patch(struct audio_hw_device *dev, + audio_patch_handle_t handle) { + return 0; +} + +static int adev_close(hw_device_t *dev) { + struct generic_audio_device *adev = (struct generic_audio_device *)dev; + int ret = 0; + if (!adev) + return 0; + + pthread_mutex_lock(&adev_init_lock); + + if (audio_device_ref_count == 0) { + ALOGE("adev_close called when ref_count 0"); + ret = -EINVAL; + goto error; + } + + if ((--audio_device_ref_count) == 0) { + if (adev->mixer) { + mixer_close(adev->mixer); + } + if (adev->out_bus_stream_map) { + hashmapFree(adev->out_bus_stream_map); + } + free(adev); + } + +error: + pthread_mutex_unlock(&adev_init_lock); + return ret; +} + +/* copied from libcutils/str_parms.c */ +static bool str_eq(void *key_a, void *key_b) { + return !strcmp((const char *)key_a, (const char *)key_b); +} + +/** + * use djb hash unless we find it inadequate. + * copied from libcutils/str_parms.c + */ +#ifdef __clang__ +__attribute__((no_sanitize("integer"))) +#endif +static int str_hash_fn(void *str) { + uint32_t hash = 5381; + char *p; + for (p = str; p && *p; p++) { + hash = ((hash << 5) + hash) + *p; + } + return (int)hash; +} + +static int adev_open(const hw_module_t *module, + const char *name, hw_device_t **device) { + static struct generic_audio_device *adev; + + if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) + return -EINVAL; + + pthread_mutex_lock(&adev_init_lock); + if (audio_device_ref_count != 0) { + *device = &adev->device.common; + audio_device_ref_count++; + ALOGV("%s: returning existing instance of adev", __func__); + ALOGV("%s: exit", __func__); + goto unlock; + } + adev = calloc(1, sizeof(struct generic_audio_device)); + + pthread_mutex_init(&adev->lock, (const pthread_mutexattr_t *) NULL); + + adev->device.common.tag = HARDWARE_DEVICE_TAG; + adev->device.common.version = AUDIO_DEVICE_API_VERSION_3_0; + adev->device.common.module = (struct hw_module_t *) module; + adev->device.common.close = adev_close; + + adev->device.init_check = adev_init_check; // no op + adev->device.set_voice_volume = adev_set_voice_volume; // no op + adev->device.set_master_volume = adev_set_master_volume; // no op + adev->device.get_master_volume = adev_get_master_volume; // no op + adev->device.set_master_mute = adev_set_master_mute; // no op + adev->device.get_master_mute = adev_get_master_mute; // no op + adev->device.set_mode = adev_set_mode; // no op + adev->device.set_mic_mute = adev_set_mic_mute; + adev->device.get_mic_mute = adev_get_mic_mute; + adev->device.set_parameters = adev_set_parameters; // no op + adev->device.get_parameters = adev_get_parameters; // no op + adev->device.get_input_buffer_size = adev_get_input_buffer_size; + adev->device.open_output_stream = adev_open_output_stream; + adev->device.close_output_stream = adev_close_output_stream; + adev->device.open_input_stream = adev_open_input_stream; + adev->device.close_input_stream = adev_close_input_stream; + adev->device.dump = adev_dump; + + // New in AUDIO_DEVICE_API_VERSION_3_0 + adev->device.set_audio_port_config = adev_set_audio_port_config; + adev->device.create_audio_patch = adev_create_audio_patch; + adev->device.release_audio_patch = adev_release_audio_patch; + + *device = &adev->device.common; + + adev->mixer = mixer_open(PCM_CARD); + struct mixer_ctl *ctl; + + // Set default mixer ctls + // Enable channels and set volume + for (int i = 0; i < (int)mixer_get_num_ctls(adev->mixer); i++) { + ctl = mixer_get_ctl(adev->mixer, i); + ALOGD("mixer %d name %s", i, mixer_ctl_get_name(ctl)); + if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Volume") || + !strcmp(mixer_ctl_get_name(ctl), "Capture Volume")) { + for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) { + ALOGD("set ctl %d to %d", z, 100); + mixer_ctl_set_percent(ctl, z, 100); + } + continue; + } + if (!strcmp(mixer_ctl_get_name(ctl), "Master Playback Switch") || + !strcmp(mixer_ctl_get_name(ctl), "Capture Switch")) { + for (int z = 0; z < (int)mixer_ctl_get_num_values(ctl); z++) { + ALOGD("set ctl %d to %d", z, 1); + mixer_ctl_set_value(ctl, z, 1); + } + continue; + } + } + + // Initialize the bus address to output stream map + adev->out_bus_stream_map = hashmapCreate(5, str_hash_fn, str_eq); + + audio_device_ref_count++; + +unlock: + pthread_mutex_unlock(&adev_init_lock); + return 0; +} + +static struct hw_module_methods_t hal_module_methods = { + .open = adev_open, +}; + +struct audio_module HAL_MODULE_INFO_SYM = { + .common = { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = AUDIO_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = AUDIO_HARDWARE_MODULE_ID, + .name = "Generic car audio HW HAL", + .author = "The Android Open Source Project", + .methods = &hal_module_methods, + }, +}; diff --git a/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml b/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml new file mode 100644 index 0000000..b256c8b --- /dev/null +++ b/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml @@ -0,0 +1,8 @@ +<resources> + <!-- Car uses hardware amplifier for volume. --> + <bool name="config_useFixedVolume">true</bool> + <!-- + Handle volume keys directly in CarAudioService without passing them to the foreground app + --> + <bool name="config_handleVolumeKeysInWindowManager">true</bool> +</resources> diff --git a/emulator/audio/overlay/packages/services/Car/service/res/values/config.xml b/emulator/audio/overlay/packages/services/Car/service/res/values/config.xml new file mode 100644 index 0000000..28857b3 --- /dev/null +++ b/emulator/audio/overlay/packages/services/Car/service/res/values/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- + Overlay resources to configure car service based on each OEM's preference. + See also packages/services/Car/service/res/values/config.xml +--> +<resources> + <bool name="audioUseDynamicRouting">true</bool> +</resources> diff --git a/emulator/hal/car_emulator_hal.mk b/emulator/hal/car_emulator_hal.mk new file mode 100644 index 0000000..86139a7 --- /dev/null +++ b/emulator/hal/car_emulator_hal.mk @@ -0,0 +1,24 @@ +# +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Default HAL implementations for automotive +PRODUCT_PACKAGES += \ + android.hardware.automotive.audiocontrol@1.0-service \ + android.hardware.automotive.vehicle@2.0-service + +# Vendor Interface Manifest +PRODUCT_COPY_FILES += \ + device/generic/car/emulator/hal/car_emulator_manifest.xml:$(TARGET_COPY_OUT_VENDOR)/manifest.xml diff --git a/emulator/hal/car_emulator_manifest.xml b/emulator/hal/car_emulator_manifest.xml new file mode 100644 index 0000000..0e09a8f --- /dev/null +++ b/emulator/hal/car_emulator_manifest.xml @@ -0,0 +1,205 @@ +<!-- A copy of the goldfish manifest with the addition of car default hals. --> +<manifest version="1.0" type="device"> + <hal format="hidl"> + <name>android.hardware.drm</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>ICryptoFactory</name> + <instance>default</instance> + <instance>widevine</instance> + </interface> + <interface> + <name>IDrmFactory</name> + <instance>default</instance> + <instance>widevine</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.audio.effect</name> + <transport>hwbinder</transport> + <version>2.0</version> + <interface> + <name>IEffectsFactory</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.biometrics.fingerprint</name> + <transport>hwbinder</transport> + <version>2.1</version> + <interface> + <name>IBiometricsFingerprint</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.configstore</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>ISurfaceFlingerConfigs</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.audio</name> + <transport>hwbinder</transport> + <version>2.0</version> + <interface> + <name>IDevicesFactory</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.keymaster</name> + <transport>hwbinder</transport> + <version>3.0</version> + <interface> + <name>IKeymasterDevice</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.keymaster</name> + <transport>hwbinder</transport> + <version>4.0</version> + <interface> + <name>IKeymasterDevice</name> + <instance>strongbox</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.graphics.allocator</name> + <transport>hwbinder</transport> + <version>2.0</version> + <interface> + <name>IAllocator</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.graphics.mapper</name> + <transport arch="32+64">passthrough</transport> + <version>2.0</version> + <interface> + <name>IMapper</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.graphics.composer</name> + <transport>hwbinder</transport> + <version>2.1</version> + <interface> + <name>IComposer</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.power</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IPower</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.broadcastradio</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IBroadcastRadioFactory</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.camera.provider</name> + <transport>hwbinder</transport> + <version>2.4</version> + <interface> + <name>ICameraProvider</name> + <instance>legacy/0</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.sensors</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>ISensors</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.gatekeeper</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IGatekeeper</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.gnss</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IGnss</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.media.omx</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IOmx</name> + <instance>default</instance> + </interface> + <interface> + <name>IOmxStore</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.radio.deprecated</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IOemHook</name> + <instance>slot1</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.radio</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IRadio</name> + <instance>slot1</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.automotive.audiocontrol</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IAudioControl</name> + <instance>default</instance> + </interface> + </hal> + <hal format="hidl"> + <name>android.hardware.automotive.vehicle</name> + <transport>hwbinder</transport> + <version>2.0</version> + <interface> + <name>IVehicle</name> + <instance>default</instance> + </interface> + </hal> + <sepolicy> + <version>10000.0</version> + </sepolicy> +</manifest> |