Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dfd393a1 authored by Geoffrey Pitsch's avatar Geoffrey Pitsch Committed by android-build-merger
Browse files

Merge \"OpenSL-based audio support for BootAnimation\" into nyc-mr1-dev

am: 859029ff

Change-Id: Idecb0041af13b9d263568c078bc0461ebb19e4ed
parents b1e09821 859029ff
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -3,14 +3,16 @@ include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    bootanimation_main.cpp \
    AudioPlayer.cpp \
    audioplay.cpp \
    BootAnimation.cpp

LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES

LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code

LOCAL_C_INCLUDES += external/tinyalsa/include
LOCAL_C_INCLUDES += \
    external/tinyalsa/include \
    frameworks/wilhelm/include

LOCAL_SHARED_LIBRARIES := \
    libcutils \
@@ -23,6 +25,7 @@ LOCAL_SHARED_LIBRARIES := \
    libEGL \
    libGLESv1_CM \
    libgui \
    libOpenSLES \
    libtinyalsa

LOCAL_MODULE:= bootanimation
@@ -33,4 +36,8 @@ ifdef TARGET_32_BIT_SURFACEFLINGER
LOCAL_32_BIT_ONLY := true
endif

# get asserts to work
APP_OPTIM := debug
LOCAL_CFLAGS += -UNDEBUG

include $(BUILD_EXECUTABLE)
+0 −313
Original line number Diff line number Diff line
/*
 * 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 %s", values, 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 %s", values, 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(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;
    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;
    config.start_threshold = mPeriodSize / 4;
    config.stop_threshold = INT_MAX;
    config.avail_min = config.start_threshold;
    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);
    delete mCurrentFile;
    mCurrentFile = NULL;
    return false;
}

} // namespace android
+24 −22
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@
#include <EGL/eglext.h>

#include "BootAnimation.h"
#include "AudioPlayer.h"
#include "audioplay.h"

namespace android {

@@ -106,9 +106,7 @@ 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();
    }
    audioplay::destroy();
}

status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
@@ -400,9 +398,6 @@ void BootAnimation::checkExit() {
    int exitnow = atoi(value);
    if (exitnow) {
        requestExit();
        if (mAudioPlayer != NULL) {
            mAudioPlayer->requestExit();
        }
    }
}

@@ -524,16 +519,6 @@ bool BootAnimation::parseAnimationDesc(Animation& animation)
    }
    char const* s = desString.string();

    // Create and initialize an AudioPlayer if we have an audio_conf.txt file
    String8 audioConf;
    if (readFile(animation.zip, "audio_conf.txt", audioConf)) {
        mAudioPlayer = new AudioPlayer;
        if (!mAudioPlayer->init(audioConf.string())) {
            ALOGE("mAudioPlayer.init failed");
            mAudioPlayer = NULL;
        }
    }

    // Parse the description file
    for (;;) {
        const char* endl = strstr(s, "\n");
@@ -564,7 +549,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation)
            part.pause = pause;
            part.path = path;
            part.clockPosY = clockPosY;
            part.audioFile = NULL;
            part.audioData = NULL;
            part.animation = NULL;
            if (!parseColor(color, part.backgroundColor)) {
                ALOGE("> invalid color '#%s'", color);
@@ -580,7 +565,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation)
            part.playUntilComplete = false;
            part.count = 1;
            part.pause = 0;
            part.audioFile = NULL;
            part.audioData = NULL;
            part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE));
            if (part.animation != NULL)
                animation.parts.add(part);
@@ -601,6 +586,7 @@ bool BootAnimation::preloadZip(Animation& animation)
        return false;
    }

    bool hasAudio = false;
    ZipEntryRO entry;
    char name[ANIM_ENTRY_NAME_MAX];
    while ((entry = zip->nextEntry(cookie)) != NULL) {
@@ -624,8 +610,10 @@ bool BootAnimation::preloadZip(Animation& animation)
                            if (map) {
                                Animation::Part& part(animation.parts.editItemAt(j));
                                if (leaf == "audio.wav") {
                                    hasAudio = true;
                                    // a part may have at most one audio file
                                    part.audioFile = map;
                                    part.audioData = (uint8_t *)map->getDataPtr();
                                    part.audioLength = map->getDataLength();
                                } else if (leaf == "trim.txt") {
                                    part.trimData.setTo((char const*)map->getDataPtr(),
                                                        map->getDataLength());
@@ -640,6 +628,8 @@ bool BootAnimation::preloadZip(Animation& animation)
                                    part.frames.add(frame);
                                }
                            }
                        } else {
                            ALOGE("bootanimation.zip is compressed; must be only stored");
                        }
                    }
                }
@@ -673,6 +663,12 @@ bool BootAnimation::preloadZip(Animation& animation)
        }
    }

    // Create and initialize audioplay if there is a wav file in any of the animations.
    if (hasAudio) {
        ALOGD("found audio.wav, creating playback engine");
        audioplay::create();
    }

    zip->endIteration(cookie);

    return true;
@@ -777,8 +773,9 @@ bool BootAnimation::playAnimation(const Animation& animation)
                break;

            // only play audio file the first time we animate the part
            if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
                mAudioPlayer->playFile(part.audioFile);
            if (r == 0 && part.audioData) {
                ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength);
                audioplay::playClip(part.audioData, part.audioLength);
            }

            glClearColor(
@@ -865,6 +862,11 @@ bool BootAnimation::playAnimation(const Animation& animation)
            }
        }
    }

    // we've finally played everything we're going to play
    audioplay::setPlaying(false);
    audioplay::destroy();

    return true;
}

+2 −3
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ class SkBitmap;

namespace android {

class AudioPlayer;
class Surface;
class SurfaceComposerClient;
class SurfaceControl;
@@ -98,7 +97,8 @@ private:
            SortedVector<Frame> frames;
            bool playUntilComplete;
            float backgroundColor[3];
            FileMap* audioFile;
            uint8_t* audioData;
            int audioLength;
            Animation* animation;
        };
        int fps;
@@ -124,7 +124,6 @@ private:
    void checkExit();

    sp<SurfaceComposerClient>       mSession;
    sp<AudioPlayer>                 mAudioPlayer;
    AssetManager mAssets;
    Texture     mAndroid[2];
    Texture     mClock;
+2 −28
Original line number Diff line number Diff line
@@ -67,34 +67,8 @@ If the file is not present, each frame is assumed to be the same size as the ani

## audio.wav

Each part may optionally play a `wav` sample when it starts. To enable this for an animation,
you must also include a `audio_conf.txt` file in the ZIP archive. Its format is as follows:

    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>

Here's an example `audio_conf.txt` from Shamu:

    card=0
    device=15
    period_size=1024
    period_count=4

    mixer "QUAT_MI2S_RX Audio Mixer MultiMedia5" = 1
    mixer "Playback Channel Map" = 0 220 157 195 0 0 0 0
    mixer "QUAT_MI2S_RX Channels" = Two
    mixer "BOOST_STUB Right Mixer right" = 1
    mixer "BOOST_STUB Left Mixer left" = 1
    mixer "Compress Playback 9 Volume" = 80 80

You will probably need to get these mixer names and values out of `audio_platform_info.xml`
and `mixer_paths.xml` for your device.
Each part may optionally play a `wav` sample when it starts. To enable this, add a file
with the name `audio.wav` in the part directory.

## exiting

Loading