Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 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 | */ |
| 16 | |
| 17 | #define LOG_NDEBUG 0 |
| 18 | #define LOG_TAG "BootAnim_AudioPlayer" |
| 19 | |
| 20 | #include "AudioPlayer.h" |
| 21 | |
| 22 | #include <androidfw/ZipFileRO.h> |
| 23 | #include <tinyalsa/asoundlib.h> |
| 24 | #include <utils/Log.h> |
| 25 | #include <utils/String8.h> |
| 26 | |
| 27 | #define ID_RIFF 0x46464952 |
| 28 | #define ID_WAVE 0x45564157 |
| 29 | #define ID_FMT 0x20746d66 |
| 30 | #define ID_DATA 0x61746164 |
| 31 | |
| 32 | // Maximum line length for audio_conf.txt |
| 33 | // We only accept lines less than this length to avoid overflows using sscanf() |
| 34 | #define MAX_LINE_LENGTH 1024 |
| 35 | |
| 36 | struct riff_wave_header { |
| 37 | uint32_t riff_id; |
| 38 | uint32_t riff_sz; |
| 39 | uint32_t wave_id; |
| 40 | }; |
| 41 | |
| 42 | struct chunk_header { |
| 43 | uint32_t id; |
| 44 | uint32_t sz; |
| 45 | }; |
| 46 | |
| 47 | struct chunk_fmt { |
| 48 | uint16_t audio_format; |
| 49 | uint16_t num_channels; |
| 50 | uint32_t sample_rate; |
| 51 | uint32_t byte_rate; |
| 52 | uint16_t block_align; |
| 53 | uint16_t bits_per_sample; |
| 54 | }; |
| 55 | |
| 56 | |
| 57 | namespace android { |
| 58 | |
| 59 | AudioPlayer::AudioPlayer() |
| 60 | : mCard(-1), |
| 61 | mDevice(-1), |
| 62 | mPeriodSize(0), |
| 63 | mPeriodCount(0), |
| 64 | mCurrentFile(NULL) |
| 65 | { |
| 66 | } |
| 67 | |
| 68 | AudioPlayer::~AudioPlayer() { |
| 69 | } |
| 70 | |
| 71 | static bool setMixerValue(struct mixer* mixer, const char* name, const char* values) |
| 72 | { |
| 73 | if (!mixer) { |
| 74 | ALOGE("no mixer in setMixerValue"); |
| 75 | return false; |
| 76 | } |
| 77 | struct mixer_ctl *ctl = mixer_get_ctl_by_name(mixer, name); |
| 78 | if (!ctl) { |
| 79 | ALOGE("mixer_get_ctl_by_name failed for %s", name); |
| 80 | return false; |
| 81 | } |
| 82 | |
| 83 | enum mixer_ctl_type type = mixer_ctl_get_type(ctl); |
| 84 | int numValues = mixer_ctl_get_num_values(ctl); |
| 85 | int intValue; |
| 86 | char stringValue[MAX_LINE_LENGTH]; |
| 87 | |
| 88 | for (int i = 0; i < numValues && values; i++) { |
| 89 | // strip leading space |
| 90 | while (*values == ' ') values++; |
| 91 | if (*values == 0) break; |
| 92 | |
| 93 | switch (type) { |
| 94 | case MIXER_CTL_TYPE_BOOL: |
| 95 | case MIXER_CTL_TYPE_INT: |
| 96 | if (sscanf(values, "%d", &intValue) == 1) { |
| 97 | if (mixer_ctl_set_value(ctl, i, intValue) != 0) { |
| 98 | ALOGE("mixer_ctl_set_value failed for %s %d", name, intValue); |
| 99 | } |
| 100 | } else { |
Bernhard Rosenkränzer | 09993f7 | 2014-11-17 20:25:28 +0100 | [diff] [blame] | 101 | ALOGE("Could not parse %s as int for %s", values, name); |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 102 | } |
| 103 | break; |
| 104 | case MIXER_CTL_TYPE_ENUM: |
| 105 | if (sscanf(values, "%s", stringValue) == 1) { |
| 106 | if (mixer_ctl_set_enum_by_string(ctl, stringValue) != 0) { |
Bernhard Rosenkränzer | 09993f7 | 2014-11-17 20:25:28 +0100 | [diff] [blame] | 107 | ALOGE("mixer_ctl_set_enum_by_string failed for %s %s", name, stringValue); |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 108 | } |
| 109 | } else { |
Bernhard Rosenkränzer | 09993f7 | 2014-11-17 20:25:28 +0100 | [diff] [blame] | 110 | ALOGE("Could not parse %s as enum for %s", values, name); |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 111 | } |
| 112 | break; |
| 113 | default: |
| 114 | ALOGE("unsupported mixer type %d for %s", type, name); |
| 115 | break; |
| 116 | } |
| 117 | |
| 118 | values = strchr(values, ' '); |
| 119 | } |
| 120 | |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | |
| 125 | /* |
| 126 | * Parse the audio configuration file. |
| 127 | * The file is named audio_conf.txt and must begin with the following header: |
| 128 | * |
| 129 | * card=<ALSA card number> |
| 130 | * device=<ALSA device number> |
| 131 | * period_size=<period size> |
| 132 | * period_count=<period count> |
| 133 | * |
| 134 | * This header is followed by zero or more mixer settings, each with the format: |
| 135 | * mixer "<name>" = <value list> |
| 136 | * Since mixer names can contain spaces, the name must be enclosed in double quotes. |
| 137 | * The values in the value list can be integers, booleans (represented by 0 or 1) |
| 138 | * or strings for enum values. |
| 139 | */ |
| 140 | bool AudioPlayer::init(const char* config) |
| 141 | { |
| 142 | int tempInt; |
| 143 | struct mixer* mixer = NULL; |
| 144 | char name[MAX_LINE_LENGTH]; |
| 145 | |
| 146 | for (;;) { |
| 147 | const char* endl = strstr(config, "\n"); |
| 148 | if (!endl) break; |
| 149 | String8 line(config, endl - config); |
| 150 | if (line.length() >= MAX_LINE_LENGTH) { |
| 151 | ALOGE("Line too long in audio_conf.txt"); |
| 152 | return false; |
| 153 | } |
| 154 | const char* l = line.string(); |
| 155 | |
| 156 | if (sscanf(l, "card=%d", &tempInt) == 1) { |
| 157 | ALOGD("card=%d", tempInt); |
| 158 | mCard = tempInt; |
| 159 | |
| 160 | mixer = mixer_open(mCard); |
| 161 | if (!mixer) { |
| 162 | ALOGE("could not open mixer for card %d", mCard); |
| 163 | return false; |
| 164 | } |
| 165 | } else if (sscanf(l, "device=%d", &tempInt) == 1) { |
| 166 | ALOGD("device=%d", tempInt); |
| 167 | mDevice = tempInt; |
| 168 | } else if (sscanf(l, "period_size=%d", &tempInt) == 1) { |
| 169 | ALOGD("period_size=%d", tempInt); |
| 170 | mPeriodSize = tempInt; |
| 171 | } else if (sscanf(l, "period_count=%d", &tempInt) == 1) { |
| 172 | ALOGD("period_count=%d", tempInt); |
| 173 | mPeriodCount = tempInt; |
| 174 | } else if (sscanf(l, "mixer \"%[0-9a-zA-Z _]s\"", name) == 1) { |
| 175 | const char* values = strchr(l, '='); |
| 176 | if (values) { |
| 177 | values++; // skip '=' |
| 178 | ALOGD("name: \"%s\" = %s", name, values); |
| 179 | setMixerValue(mixer, name, values); |
| 180 | } else { |
| 181 | ALOGE("values missing for name: \"%s\"", name); |
| 182 | } |
| 183 | } |
| 184 | config = ++endl; |
| 185 | } |
| 186 | |
| 187 | mixer_close(mixer); |
| 188 | |
| 189 | if (mCard >= 0 && mDevice >= 0) { |
| 190 | return true; |
| 191 | } |
| 192 | |
| 193 | return false; |
| 194 | } |
| 195 | |
Bernhard Rosenkränzer | 99d61ed | 2014-11-17 21:15:30 +0100 | [diff] [blame] | 196 | void AudioPlayer::playFile(FileMap* fileMap) { |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 197 | // stop any currently playing sound |
| 198 | requestExitAndWait(); |
| 199 | |
| 200 | mCurrentFile = fileMap; |
| 201 | run("bootanim audio", PRIORITY_URGENT_AUDIO); |
| 202 | } |
| 203 | |
| 204 | bool AudioPlayer::threadLoop() |
| 205 | { |
| 206 | struct pcm_config config; |
| 207 | struct pcm *pcm = NULL; |
| 208 | bool moreChunks = true; |
| 209 | const struct chunk_fmt* chunkFmt = NULL; |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 210 | int bufferSize; |
| 211 | const uint8_t* wavData; |
| 212 | size_t wavLength; |
| 213 | const struct riff_wave_header* wavHeader; |
| 214 | |
| 215 | if (mCurrentFile == NULL) { |
| 216 | ALOGE("mCurrentFile is NULL"); |
| 217 | return false; |
| 218 | } |
| 219 | |
| 220 | wavData = (const uint8_t *)mCurrentFile->getDataPtr(); |
| 221 | if (!wavData) { |
| 222 | ALOGE("Could not access WAV file data"); |
| 223 | goto exit; |
| 224 | } |
| 225 | wavLength = mCurrentFile->getDataLength(); |
| 226 | |
| 227 | wavHeader = (const struct riff_wave_header *)wavData; |
| 228 | if (wavLength < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || |
| 229 | (wavHeader->wave_id != ID_WAVE)) { |
| 230 | ALOGE("Error: audio file is not a riff/wave file\n"); |
| 231 | goto exit; |
| 232 | } |
| 233 | wavData += sizeof(*wavHeader); |
| 234 | wavLength -= sizeof(*wavHeader); |
| 235 | |
| 236 | do { |
| 237 | const struct chunk_header* chunkHeader = (const struct chunk_header*)wavData; |
| 238 | if (wavLength < sizeof(*chunkHeader)) { |
| 239 | ALOGE("EOF reading chunk headers"); |
| 240 | goto exit; |
| 241 | } |
| 242 | |
| 243 | wavData += sizeof(*chunkHeader); |
| 244 | wavLength -= sizeof(*chunkHeader); |
| 245 | |
| 246 | switch (chunkHeader->id) { |
| 247 | case ID_FMT: |
| 248 | chunkFmt = (const struct chunk_fmt *)wavData; |
| 249 | wavData += chunkHeader->sz; |
| 250 | wavLength -= chunkHeader->sz; |
| 251 | break; |
| 252 | case ID_DATA: |
| 253 | /* Stop looking for chunks */ |
| 254 | moreChunks = 0; |
| 255 | break; |
| 256 | default: |
| 257 | /* Unknown chunk, skip bytes */ |
| 258 | wavData += chunkHeader->sz; |
| 259 | wavLength -= chunkHeader->sz; |
| 260 | } |
| 261 | } while (moreChunks); |
| 262 | |
| 263 | if (!chunkFmt) { |
| 264 | ALOGE("format not found in WAV file"); |
| 265 | goto exit; |
| 266 | } |
| 267 | |
| 268 | |
| 269 | memset(&config, 0, sizeof(config)); |
| 270 | config.channels = chunkFmt->num_channels; |
| 271 | config.rate = chunkFmt->sample_rate; |
| 272 | config.period_size = mPeriodSize; |
| 273 | config.period_count = mPeriodCount; |
Mike Lockwood | 2813858 | 2014-10-07 14:47:26 -0700 | [diff] [blame] | 274 | config.start_threshold = mPeriodSize / 4; |
| 275 | config.stop_threshold = INT_MAX; |
| 276 | config.avail_min = config.start_threshold; |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 277 | if (chunkFmt->bits_per_sample != 16) { |
| 278 | ALOGE("only 16 bit WAV files are supported"); |
| 279 | goto exit; |
| 280 | } |
| 281 | config.format = PCM_FORMAT_S16_LE; |
| 282 | |
| 283 | pcm = pcm_open(mCard, mDevice, PCM_OUT, &config); |
| 284 | if (!pcm || !pcm_is_ready(pcm)) { |
| 285 | ALOGE("Unable to open PCM device (%s)\n", pcm_get_error(pcm)); |
| 286 | goto exit; |
| 287 | } |
| 288 | |
| 289 | bufferSize = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); |
| 290 | |
| 291 | while (wavLength > 0) { |
| 292 | if (exitPending()) goto exit; |
| 293 | size_t count = bufferSize; |
| 294 | if (count > wavLength) |
| 295 | count = wavLength; |
| 296 | |
| 297 | if (pcm_write(pcm, wavData, count)) { |
| 298 | ALOGE("pcm_write failed (%s)", pcm_get_error(pcm)); |
| 299 | goto exit; |
| 300 | } |
| 301 | wavData += count; |
| 302 | wavLength -= count; |
| 303 | } |
| 304 | |
| 305 | exit: |
| 306 | if (pcm) |
| 307 | pcm_close(pcm); |
Narayan Kamath | 688ff4c | 2015-02-23 15:47:54 +0000 | [diff] [blame] | 308 | delete mCurrentFile; |
Mike Lockwood | ebf9a0d | 2014-10-02 16:08:47 -0700 | [diff] [blame] | 309 | mCurrentFile = NULL; |
| 310 | return false; |
| 311 | } |
| 312 | |
| 313 | } // namespace android |