普通文本  |  243行  |  7.2 KB

/*
 * 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.
 */

#include <chre.h>
#include <cinttypes>

#include "chre/util/macros.h"
#include "chre/util/nanoapp/audio.h"
#include "chre/util/nanoapp/log.h"
#include "chre/util/time.h"

#define LOG_TAG "[AudioStress]"

/**
 * @file
 *
 * This nanoapp is designed to subscribe to audio for varying durations of
 * time and verify that audio data is delivered when it is expected to be.
 */

using chre::Milliseconds;
using chre::Nanoseconds;
using chre::Seconds;

namespace {

//! The required buffer size for the stress test.
constexpr Nanoseconds kBufferDuration = Nanoseconds(Seconds(2));

//! The required sample format for the stress test.
constexpr uint8_t kBufferFormat = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM;

//! The required sample rate for the stress test.
constexpr uint32_t kBufferSampleRate = 16000;

//! The maximum amount of time that audio will not be delivered for.
constexpr Seconds kMaxAudioGap = Seconds(300);

//! The list of durations to subscribe to audio for. Even durations are for when
//! audio is enabled and odd is for when audio is disabled.
constexpr Milliseconds kStressPlan[] = {
  // Enabled, Disabled
  Milliseconds(20000), Milliseconds(20000),
  Milliseconds(30000), Milliseconds(200),
  Milliseconds(10000), Milliseconds(1000),
  Milliseconds(10000), Milliseconds(1999),
  Milliseconds(8000), Milliseconds(60000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
  Milliseconds(1000), Milliseconds(1000),
};

//! The discovered audio handle found at startup.
uint32_t gAudioHandle;

//! The current position in the stress plan.
size_t gTestPosition = 0;

//! The timer handle to advance through the stress test.
uint32_t gTimerHandle;

//! Whether or not audio is currently suspended. If audio is delivered when this
//! is set to true, this is considered a test failure.
bool gAudioIsSuspended = true;

//! The timestamp of the last audio data event.
Nanoseconds gLastAudioTimestamp;

/**
 * @return true when the current test phase is expecting audio data events to be
 *         delivered.
 */
bool audioIsExpected() {
  // Even test intervals are expected to return audio events. The current test
  // interval is gTestPosition - 1 so there is no need to invert the bit.
  return (gTestPosition % 2);
}

/**
 * Discovers an audio source to use for the stress test. The gAudioHandle will
 * be set if the audio source was found.
 *
 * @return true if a matching source was discovered successfully.
 */
bool discoverAudioHandle() {
  bool success = false;
  struct chreAudioSource source;
  for (uint32_t i = 0; !success && chreAudioGetSource(i, &source); i++) {
    LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data",
         source.name, source.sampleRate,
         chre::getChreAudioFormatString(source.format));
    LOGI("  buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]",
        source.minBufferDuration, source.maxBufferDuration);

    if (source.sampleRate == kBufferSampleRate
        && source.minBufferDuration <= kBufferDuration.toRawNanoseconds()
        && source.maxBufferDuration >= kBufferDuration.toRawNanoseconds()
        && source.format == kBufferFormat) {
      gAudioHandle = i;
      success = true;
    }
  }

  if (!success) {
    LOGW("Failed to find suitable audio source");
  }

  return success;
}

void checkTestPassing() {
  auto lastAudioDuration = Nanoseconds(chreGetTime()) - gLastAudioTimestamp;
  if (lastAudioDuration > kMaxAudioGap) {
    LOGE("Test fail - audio not received for %" PRIu64 "ns",
         lastAudioDuration.toRawNanoseconds());
    chreAbort(-1);
  }
}

bool requestAudioForCurrentTestState(const Nanoseconds& testStateDuration) {
  bool success = false;
  LOGD("Test stage %zu", gTestPosition);
  if (audioIsExpected()) {
    if (!chreAudioConfigureSource(gAudioHandle, true, kBufferDuration.toRawNanoseconds(),
                                  kBufferDuration.toRawNanoseconds())) {
      LOGE("Failed to enable audio");
    } else {
      LOGI("Enabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
      success = true;
    }
  } else {
    if (!chreAudioConfigureSource(0, false, 0, 0)) {
      LOGE("Failed to disable audio");
    } else {
      LOGI("Disabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
      success = true;
    }
  }

  return success;
}

bool advanceTestPosition() {
  checkTestPassing();
  gTimerHandle = chreTimerSet(kStressPlan[gTestPosition].toRawNanoseconds(),
                              nullptr, true /* oneShot */);
  bool success = (gTimerHandle != CHRE_TIMER_INVALID);
  if (!success) {
    LOGE("Failed to set timer");
  } else {
    // Grab the duration prior to incrementing the test position.
    Nanoseconds timerDuration = kStressPlan[gTestPosition++];
    if (gTestPosition >= ARRAY_SIZE(kStressPlan)) {
      gTestPosition = 0;
    }

    success = requestAudioForCurrentTestState(timerDuration);
  }

  return success;
}

void handleTimerEvent() {
  if (!advanceTestPosition()) {
    LOGE("Test fail");
  }
}

void handleAudioDataEvent(const chreAudioDataEvent *audioDataEvent) {
  LOGI("Handling audio data event");
  gLastAudioTimestamp = Nanoseconds(audioDataEvent->timestamp);

  if (gAudioIsSuspended) {
    LOGE("Test fail - received audio when suspended");
  } else if (!audioIsExpected()) {
    LOGE("Test fail - received audio unexpectedly");
  } else {
    LOGI("Test passing - received audio when expected");
  }
}

void handleAudioSamplingChangeEvent(
    const chreAudioSourceStatusEvent *audioSourceStatusEvent) {
  LOGI("Handling audio sampling change event - suspended: %d",
       audioSourceStatusEvent->status.suspended);
  gAudioIsSuspended = audioSourceStatusEvent->status.suspended;
}

}  // namespace


bool nanoappStart() {
  LOGI("start");
  gLastAudioTimestamp = Nanoseconds(chreGetTime());
  return (discoverAudioHandle() && advanceTestPosition());
}

void nanoappHandleEvent(uint32_t senderInstanceId,
                        uint16_t eventType,
                        const void *eventData) {
  switch (eventType) {
    case CHRE_EVENT_TIMER:
      handleTimerEvent();
      break;

    case CHRE_EVENT_AUDIO_DATA:
      handleAudioDataEvent(
          static_cast<const chreAudioDataEvent *>(eventData));
      break;

    case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
      handleAudioSamplingChangeEvent(
          static_cast<const chreAudioSourceStatusEvent *>(eventData));
      break;

    default:
      LOGW("Unexpected event %" PRIu16, eventType);
      break;
  }
}

void nanoappEnd() {
  LOGI("stop");
}