OSDN Git Service

Add support for playing audio during bootanimation
authorMike Lockwood <lockwood@google.com>
Thu, 2 Oct 2014 23:08:47 +0000 (16:08 -0700)
committerMike Lockwood <lockwood@google.com>
Fri, 3 Oct 2014 22:38:15 +0000 (15:38 -0700)
The bootanimation daemon will play 16 bit WAV files contained in bootanimation.zip
For this to work, the bootanimation.zip must contain an audio_conf.txt file,
which contains parameters to be used for the tinyalsa pcm_open call
as well as mixer parameters to set before attempting to play the sound.

If the bootanimation finds an audio_conf.txt file, then it will look for a file named
"audio.wav" in each of the part subdirectories. If audio.wav is found, it will play that
WAV file starting at the beginning of that part.

The code for this is based on the tinyplay utility in tinyalsa.

The 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.

Finally I should mention that this change is not the right approach.
Instead of going straight to ALSA we should be using the mediaserver instead.
But mediaserver isn't ready in time due to interactions with the system server, and there
isn't time to fix this for the current release. We need to fix that for the next one.

Bug: 17674304

Change-Id: Ic391ade61c941d0a24f4d64fe005ac9375a23fa9

cmds/bootanimation/Android.mk
cmds/bootanimation/AudioPlayer.cpp [new file with mode: 0644]
cmds/bootanimation/AudioPlayer.h [new file with mode: 0644]
cmds/bootanimation/BootAnimation.cpp
cmds/bootanimation/BootAnimation.h

index c4fe6cf..d6ecbe3 100644 (file)
@@ -3,10 +3,13 @@ include $(CLEAR_VARS)
 
 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 @@ LOCAL_SHARED_LIBRARIES := \
        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 (file)
index 0000000..a2ee7ea
--- /dev/null
@@ -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 (file)
index 0000000..7e82a07
--- /dev/null
@@ -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
index 0892311..b2474f2 100644 (file)
@@ -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 @@ void BootAnimation::binderDied(const wp<IBinder>&)
     // 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 @@ void BootAnimation::checkExit() {
     int exitnow = atoi(value);
     if (exitnow) {
         requestExit();
+        if (mAudioPlayer != NULL) {
+            mAudioPlayer->requestExit();
+        }
     }
 }
 
@@ -422,26 +429,45 @@ static bool parseColor(const char str[7], float color[3]) {
     return true;
 }
 
-bool BootAnimation::movie()
+bool BootAnimation::readFile(const char* name, String8& outString)
 {
-    ZipEntryRO desc = mZip->findEntryByName("desc.txt");
-    ALOGE_IF(!desc, "couldn't find desc.txt");
-    if (!desc) {
+    ZipEntryRO entry = mZip->findEntryByName(name);
+    ALOGE_IF(!entry, "couldn't find %s", name);
+    if (!entry) {
         return false;
     }
 
-    FileMap* descMap = mZip->createEntryFileMap(desc);
-    mZip->releaseEntry(desc);
-    ALOGE_IF(!descMap, "descMap is null");
-    if (!descMap) {
+    FileMap* entryMap = mZip->createEntryFileMap(entry);
+    mZip->releaseEntry(entry);
+    ALOGE_IF(!entryMap, "entryMap is null");
+    if (!entryMap) {
         return false;
     }
 
-    String8 desString((char const*)descMap->getDataPtr(),
-            descMap->getDataLength());
-    descMap->release();
+    outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
+    entryMap->release();
+    return true;
+}
+
+bool BootAnimation::movie()
+{
+    String8 desString;
+
+    if (!readFile("desc.txt", desString)) {
+        return false;
+    }
     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 @@ bool BootAnimation::movie()
             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 @@ bool BootAnimation::movie()
                         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 @@ bool BootAnimation::movie()
             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],
index 72cd62b..f968b25 100644 (file)
@@ -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 @@ class SkBitmap;
 
 namespace android {
 
+class AudioPlayer;
 class Surface;
 class SurfaceComposerClient;
 class SurfaceControl;
@@ -72,6 +73,7 @@ private:
             SortedVector<Frame> frames;
             bool playUntilComplete;
             float backgroundColor[3];
+            FileMap* audioFile;
         };
         int fps;
         int width;
@@ -82,11 +84,13 @@ private:
     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;