Merge "Add support for playing audio during bootanimation" into lmp-dev
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index c4fe6cf..d6ecbe3 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -3,10 +3,13 @@
 
 LOCAL_SRC_FILES:= \
 	bootanimation_main.cpp \
+	AudioPlayer.cpp \
 	BootAnimation.cpp
 
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
+LOCAL_C_INCLUDES += external/tinyalsa/include
+
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
 	liblog \
@@ -17,7 +20,8 @@
 	libskia \
     libEGL \
     libGLESv1_CM \
-    libgui
+    libgui \
+    libtinyalsa
 
 LOCAL_MODULE:= bootanimation
 
diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp
new file mode 100644
index 0000000..a2ee7ea
--- /dev/null
+++ b/cmds/bootanimation/AudioPlayer.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "BootAnim_AudioPlayer"
+
+#include "AudioPlayer.h"
+
+#include <androidfw/ZipFileRO.h>
+#include <tinyalsa/asoundlib.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#define ID_RIFF 0x46464952
+#define ID_WAVE 0x45564157
+#define ID_FMT  0x20746d66
+#define ID_DATA 0x61746164
+
+// Maximum line length for audio_conf.txt
+// We only accept lines less than this length to avoid overflows using sscanf()
+#define MAX_LINE_LENGTH 1024
+
+struct riff_wave_header {
+    uint32_t riff_id;
+    uint32_t riff_sz;
+    uint32_t wave_id;
+};
+
+struct chunk_header {
+    uint32_t id;
+    uint32_t sz;
+};
+
+struct chunk_fmt {
+    uint16_t audio_format;
+    uint16_t num_channels;
+    uint32_t sample_rate;
+    uint32_t byte_rate;
+    uint16_t block_align;
+    uint16_t bits_per_sample;
+};
+
+
+namespace android {
+
+AudioPlayer::AudioPlayer()
+    :   mCard(-1),
+        mDevice(-1),
+        mPeriodSize(0),
+        mPeriodCount(0),
+        mCurrentFile(NULL)
+{
+}
+
+AudioPlayer::~AudioPlayer() {
+}
+
+static bool setMixerValue(struct mixer* mixer, const char* name, const char* values)
+{
+    if (!mixer) {
+        ALOGE("no mixer in setMixerValue");
+        return false;
+    }
+    struct mixer_ctl *ctl = mixer_get_ctl_by_name(mixer, name);
+    if (!ctl) {
+        ALOGE("mixer_get_ctl_by_name failed for %s", name);
+        return false;
+    }
+
+    enum mixer_ctl_type type = mixer_ctl_get_type(ctl);
+    int numValues = mixer_ctl_get_num_values(ctl);
+    int intValue;
+    char stringValue[MAX_LINE_LENGTH];
+
+    for (int i = 0; i < numValues && values; i++) {
+        // strip leading space
+        while (*values == ' ') values++;
+        if (*values == 0) break;
+
+        switch (type) {
+            case MIXER_CTL_TYPE_BOOL:
+            case MIXER_CTL_TYPE_INT:
+                if (sscanf(values, "%d", &intValue) == 1) {
+                    if (mixer_ctl_set_value(ctl, i, intValue) != 0) {
+                        ALOGE("mixer_ctl_set_value failed for %s %d", name, intValue);
+                    }
+                } else {
+                    ALOGE("Could not parse %s as int for %d", intValue, name);
+                }
+                break;
+            case MIXER_CTL_TYPE_ENUM:
+                if (sscanf(values, "%s", stringValue) == 1) {
+                    if (mixer_ctl_set_enum_by_string(ctl, stringValue) != 0) {
+                        ALOGE("mixer_ctl_set_enum_by_string failed for %s %%s", name, stringValue);
+                    }
+                } else {
+                    ALOGE("Could not parse %s as enum for %d", stringValue, name);
+                }
+                break;
+            default:
+                ALOGE("unsupported mixer type %d for %s", type, name);
+                break;
+        }
+
+        values = strchr(values, ' ');
+    }
+
+    return true;
+}
+
+
+/*
+ * Parse the audio configuration file.
+ * The file is named audio_conf.txt and must begin with the following header:
+ *
+ * card=<ALSA card number>
+ * device=<ALSA device number>
+ * period_size=<period size>
+ * period_count=<period count>
+ *
+ * This header is followed by zero or more mixer settings, each with the format:
+ * mixer "<name>" = <value list>
+ * Since mixer names can contain spaces, the name must be enclosed in double quotes.
+ * The values in the value list can be integers, booleans (represented by 0 or 1)
+ * or strings for enum values.
+ */
+bool AudioPlayer::init(const char* config)
+{
+    int tempInt;
+    struct mixer* mixer = NULL;
+    char    name[MAX_LINE_LENGTH];
+
+    for (;;) {
+        const char* endl = strstr(config, "\n");
+        if (!endl) break;
+        String8 line(config, endl - config);
+        if (line.length() >= MAX_LINE_LENGTH) {
+            ALOGE("Line too long in audio_conf.txt");
+            return false;
+        }
+        const char* l = line.string();
+
+        if (sscanf(l, "card=%d", &tempInt) == 1) {
+            ALOGD("card=%d", tempInt);
+            mCard = tempInt;
+
+            mixer = mixer_open(mCard);
+            if (!mixer) {
+                ALOGE("could not open mixer for card %d", mCard);
+                return false;
+            }
+        } else if (sscanf(l, "device=%d", &tempInt) == 1) {
+            ALOGD("device=%d", tempInt);
+            mDevice = tempInt;
+        } else if (sscanf(l, "period_size=%d", &tempInt) == 1) {
+            ALOGD("period_size=%d", tempInt);
+            mPeriodSize = tempInt;
+        } else if (sscanf(l, "period_count=%d", &tempInt) == 1) {
+            ALOGD("period_count=%d", tempInt);
+            mPeriodCount = tempInt;
+        } else if (sscanf(l, "mixer \"%[0-9a-zA-Z _]s\"", name) == 1) {
+            const char* values = strchr(l, '=');
+            if (values) {
+                values++;   // skip '='
+                ALOGD("name: \"%s\" = %s", name, values);
+                setMixerValue(mixer, name, values);
+            } else {
+                ALOGE("values missing for name: \"%s\"", name);
+            }
+        }
+        config = ++endl;
+    }
+
+    mixer_close(mixer);
+
+    if (mCard >= 0 && mDevice >= 0) {
+        return true;
+    }
+
+    return false;
+}
+
+void AudioPlayer::playFile(struct FileMap* fileMap) {
+    // stop any currently playing sound
+    requestExitAndWait();
+
+    mCurrentFile = fileMap;
+    run("bootanim audio", PRIORITY_URGENT_AUDIO);
+}
+
+bool AudioPlayer::threadLoop()
+{
+    struct pcm_config config;
+    struct pcm *pcm = NULL;
+    bool moreChunks = true;
+    const struct chunk_fmt* chunkFmt = NULL;
+    void* buffer = NULL;
+    int bufferSize;
+    const uint8_t* wavData;
+    size_t wavLength;
+    const struct riff_wave_header* wavHeader;
+
+    if (mCurrentFile == NULL) {
+        ALOGE("mCurrentFile is NULL");
+        return false;
+     }
+
+    wavData = (const uint8_t *)mCurrentFile->getDataPtr();
+    if (!wavData) {
+        ALOGE("Could not access WAV file data");
+        goto exit;
+    }
+    wavLength = mCurrentFile->getDataLength();
+
+    wavHeader = (const struct riff_wave_header *)wavData;
+    if (wavLength < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) ||
+        (wavHeader->wave_id != ID_WAVE)) {
+        ALOGE("Error: audio file is not a riff/wave file\n");
+        goto exit;
+    }
+    wavData += sizeof(*wavHeader);
+    wavLength -= sizeof(*wavHeader);
+
+    do {
+        const struct chunk_header* chunkHeader = (const struct chunk_header*)wavData;
+        if (wavLength < sizeof(*chunkHeader)) {
+            ALOGE("EOF reading chunk headers");
+            goto exit;
+        }
+
+        wavData += sizeof(*chunkHeader);
+        wavLength -=  sizeof(*chunkHeader);
+
+        switch (chunkHeader->id) {
+            case ID_FMT:
+                chunkFmt = (const struct chunk_fmt *)wavData;
+                wavData += chunkHeader->sz;
+                wavLength -= chunkHeader->sz;
+                break;
+            case ID_DATA:
+                /* Stop looking for chunks */
+                moreChunks = 0;
+                break;
+            default:
+                /* Unknown chunk, skip bytes */
+                wavData += chunkHeader->sz;
+                wavLength -= chunkHeader->sz;
+        }
+    } while (moreChunks);
+
+    if (!chunkFmt) {
+        ALOGE("format not found in WAV file");
+        goto exit;
+    }
+
+
+    memset(&config, 0, sizeof(config));
+    config.channels = chunkFmt->num_channels;
+    config.rate = chunkFmt->sample_rate;
+    config.period_size = mPeriodSize;
+    config.period_count = mPeriodCount;
+    if (chunkFmt->bits_per_sample != 16) {
+        ALOGE("only 16 bit WAV files are supported");
+        goto exit;
+    }
+    config.format = PCM_FORMAT_S16_LE;
+
+    pcm = pcm_open(mCard, mDevice, PCM_OUT, &config);
+    if (!pcm || !pcm_is_ready(pcm)) {
+        ALOGE("Unable to open PCM device (%s)\n", pcm_get_error(pcm));
+        goto exit;
+    }
+
+    bufferSize = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
+
+    while (wavLength > 0) {
+        if (exitPending()) goto exit;
+        size_t count = bufferSize;
+        if (count > wavLength)
+            count = wavLength;
+
+        if (pcm_write(pcm, wavData, count)) {
+            ALOGE("pcm_write failed (%s)", pcm_get_error(pcm));
+            goto exit;
+        }
+        wavData += count;
+        wavLength -= count;
+    }
+
+exit:
+    if (pcm)
+        pcm_close(pcm);
+    mCurrentFile->release();
+    mCurrentFile = NULL;
+    return false;
+}
+
+} // namespace android
diff --git a/cmds/bootanimation/AudioPlayer.h b/cmds/bootanimation/AudioPlayer.h
new file mode 100644
index 0000000..7e82a07
--- /dev/null
+++ b/cmds/bootanimation/AudioPlayer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef _BOOTANIMATION_AUDIOPLAYER_H
+#define _BOOTANIMATION_AUDIOPLAYER_H
+
+#include <utils/Thread.h>
+
+namespace android {
+
+class AudioPlayer : public Thread
+{
+public:
+                AudioPlayer();
+    virtual     ~AudioPlayer();
+    bool        init(const char* config);
+
+    void        playFile(struct FileMap* fileMap);
+
+private:
+    virtual bool        threadLoop();
+
+private:
+    int                 mCard;      // ALSA card to use
+    int                 mDevice;    // ALSA device to use
+    int                 mPeriodSize;
+    int                 mPeriodCount;
+
+    struct FileMap*     mCurrentFile;
+};
+
+} // namespace android
+
+#endif // _BOOTANIMATION_AUDIOPLAYER_H
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 08923119..b2474f2 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define LOG_NDEBUG 0
 #define LOG_TAG "BootAnimation"
 
 #include <stdint.h>
@@ -30,7 +31,6 @@
 #include <utils/Atomic.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
-#include <utils/threads.h>
 
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
@@ -50,6 +50,7 @@
 #include <EGL/eglext.h>
 
 #include "BootAnimation.h"
+#include "AudioPlayer.h"
 
 #define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
 #define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
@@ -99,6 +100,9 @@
     // might be blocked on a condition variable that will never be updated.
     kill( getpid(), SIGKILL );
     requestExit();
+    if (mAudioPlayer != NULL) {
+        mAudioPlayer->requestExit();
+    }
 }
 
 status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
@@ -394,6 +398,9 @@
     int exitnow = atoi(value);
     if (exitnow) {
         requestExit();
+        if (mAudioPlayer != NULL) {
+            mAudioPlayer->requestExit();
+        }
     }
 }
 
@@ -422,26 +429,45 @@
     return true;
 }
 
+bool BootAnimation::readFile(const char* name, String8& outString)
+{
+    ZipEntryRO entry = mZip->findEntryByName(name);
+    ALOGE_IF(!entry, "couldn't find %s", name);
+    if (!entry) {
+        return false;
+    }
+
+    FileMap* entryMap = mZip->createEntryFileMap(entry);
+    mZip->releaseEntry(entry);
+    ALOGE_IF(!entryMap, "entryMap is null");
+    if (!entryMap) {
+        return false;
+    }
+
+    outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
+    entryMap->release();
+    return true;
+}
+
 bool BootAnimation::movie()
 {
-    ZipEntryRO desc = mZip->findEntryByName("desc.txt");
-    ALOGE_IF(!desc, "couldn't find desc.txt");
-    if (!desc) {
+    String8 desString;
+
+    if (!readFile("desc.txt", desString)) {
         return false;
     }
-
-    FileMap* descMap = mZip->createEntryFileMap(desc);
-    mZip->releaseEntry(desc);
-    ALOGE_IF(!descMap, "descMap is null");
-    if (!descMap) {
-        return false;
-    }
-
-    String8 desString((char const*)descMap->getDataPtr(),
-            descMap->getDataLength());
-    descMap->release();
     char const* s = desString.string();
 
+    // Create and initialize an AudioPlayer if we have an audio_conf.txt file
+    String8 audioConf;
+    if (readFile("audio_conf.txt", audioConf)) {
+        mAudioPlayer = new AudioPlayer;
+        if (!mAudioPlayer->init(audioConf.string())) {
+            ALOGE("mAudioPlayer.init failed");
+            mAudioPlayer = NULL;
+        }
+    }
+
     Animation animation;
 
     // Parse the description file
@@ -468,6 +494,7 @@
             part.count = count;
             part.pause = pause;
             part.path = path;
+            part.audioFile = NULL;
             if (!parseColor(color, part.backgroundColor)) {
                 ALOGE("> invalid color '#%s'", color);
                 part.backgroundColor[0] = 0.0f;
@@ -508,11 +535,16 @@
                         if (method == ZipFileRO::kCompressStored) {
                             FileMap* map = mZip->createEntryFileMap(entry);
                             if (map) {
-                                Animation::Frame frame;
-                                frame.name = leaf;
-                                frame.map = map;
                                 Animation::Part& part(animation.parts.editItemAt(j));
-                                part.frames.add(frame);
+                                if (leaf == "audio.wav") {
+                                    // a part may have at most one audio file
+                                    part.audioFile = map;
+                                } else {
+                                    Animation::Frame frame;
+                                    frame.name = leaf;
+                                    frame.map = map;
+                                    part.frames.add(frame);
+                                }
                             }
                         }
                     }
@@ -559,6 +591,11 @@
             if(exitPending() && !part.playUntilComplete)
                 break;
 
+            // only play audio file the first time we animate the part
+            if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
+                mAudioPlayer->playFile(part.audioFile);
+            }
+
             glClearColor(
                     part.backgroundColor[0],
                     part.backgroundColor[1],
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 72cd62b..f968b25 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -21,7 +21,7 @@
 #include <sys/types.h>
 
 #include <androidfw/AssetManager.h>
-#include <utils/threads.h>
+#include <utils/Thread.h>
 
 #include <EGL/egl.h>
 #include <GLES/gl.h>
@@ -30,6 +30,7 @@
 
 namespace android {
 
+class AudioPlayer;
 class Surface;
 class SurfaceComposerClient;
 class SurfaceControl;
@@ -72,6 +73,7 @@
             SortedVector<Frame> frames;
             bool playUntilComplete;
             float backgroundColor[3];
+            FileMap* audioFile;
         };
         int fps;
         int width;
@@ -82,11 +84,13 @@
     status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
     status_t initTexture(const Animation::Frame& frame);
     bool android();
+    bool readFile(const char* name, String8& outString);
     bool movie();
 
     void checkExit();
 
     sp<SurfaceComposerClient>       mSession;
+    sp<AudioPlayer>                 mAudioPlayer;
     AssetManager mAssets;
     Texture     mAndroid[2];
     int         mWidth;