summaryrefslogtreecommitdiffstats
path: root/services/audioflinger/NBAIO_Tee.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'services/audioflinger/NBAIO_Tee.cpp')
-rw-r--r--services/audioflinger/NBAIO_Tee.cpp517
1 files changed, 517 insertions, 0 deletions
diff --git a/services/audioflinger/NBAIO_Tee.cpp b/services/audioflinger/NBAIO_Tee.cpp
new file mode 100644
index 0000000000..53083d5036
--- /dev/null
+++ b/services/audioflinger/NBAIO_Tee.cpp
@@ -0,0 +1,517 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NBAIO_Tee"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include <deque>
+#include <dirent.h>
+#include <future>
+#include <list>
+#include <vector>
+
+#include <audio_utils/format.h>
+#include <audio_utils/sndfile.h>
+#include <media/nbaio/PipeReader.h>
+
+#include "Configuration.h"
+#include "NBAIO_Tee.h"
+
+// Enabled with TEE_SINK in Configuration.h
+#ifdef TEE_SINK
+
+namespace android {
+
+/*
+ Tee filenames generated as follows:
+
+ "aftee_Date_ThreadId_C_reason.wav" RecordThread
+ "aftee_Date_ThreadId_M_reason.wav" MixerThread (Normal)
+ "aftee_Date_ThreadId_F_reason.wav" MixerThread (Fast)
+ "aftee_Date_ThreadId_TrackId_R_reason.wav" RecordTrack
+ "aftee_Date_ThreadId_TrackId_TrackName_T_reason.wav" PlaybackTrack
+
+ where Date = YYYYmmdd_HHMMSS_MSEC
+
+ where Reason = [ DTOR | DUMP | REMOVE ]
+
+ Examples:
+ aftee_20180424_153811_038_13_57_2_T_REMOVE.wav
+ aftee_20180424_153811_218_13_57_2_T_REMOVE.wav
+ aftee_20180424_153811_378_13_57_2_T_REMOVE.wav
+ aftee_20180424_153825_147_62_C_DUMP.wav
+ aftee_20180424_153825_148_62_59_R_DUMP.wav
+ aftee_20180424_153825_149_13_F_DUMP.wav
+ aftee_20180424_153842_125_62_59_R_REMOVE.wav
+ aftee_20180424_153842_168_62_C_DTOR.wav
+*/
+
+static constexpr char DEFAULT_PREFIX[] = "aftee_";
+static constexpr char DEFAULT_DIRECTORY[] = "/data/misc/audioserver";
+static constexpr size_t DEFAULT_THREADPOOL_SIZE = 8;
+
+/** AudioFileHandler manages temporary audio wav files with a least recently created
+ retention policy.
+
+ The temporary filenames are systematically generated. A common filename prefix,
+ storage directory, and concurrency pool are passed in on creating the object.
+
+ Temporary files are created by "create", which returns a filename generated by
+
+ prefix + 14 char date + suffix
+
+ TODO Move to audio_utils.
+ TODO Avoid pointing two AudioFileHandlers to the same directory and prefix
+ as we don't have a prefix specific lock file. */
+
+class AudioFileHandler {
+public:
+
+ AudioFileHandler(const std::string &prefix, const std::string &directory, size_t pool)
+ : mThreadPool(pool)
+ , mPrefix(prefix)
+ {
+ (void)setDirectory(directory);
+ }
+
+ /** returns filename of created audio file, else empty string on failure. */
+ std::string create(
+ std::function<ssize_t /* frames_read */
+ (void * /* buffer */, size_t /* size_in_frames */)> reader,
+ uint32_t sampleRate,
+ uint32_t channelCount,
+ audio_format_t format,
+ const std::string &suffix);
+
+private:
+ /** sets the current directory. this is currently private to avoid confusion
+ when changing while pending operations are occurring (it's okay, but
+ weakly synchronized). */
+ status_t setDirectory(const std::string &directory);
+
+ /** cleans current directory and returns the directory name done. */
+ status_t clean(std::string *dir = nullptr);
+
+ /** creates an audio file from a reader functor passed in. */
+ status_t createInternal(
+ std::function<ssize_t /* frames_read */
+ (void * /* buffer */, size_t /* size_in_frames */)> reader,
+ uint32_t sampleRate,
+ uint32_t channelCount,
+ audio_format_t format,
+ const std::string &filename);
+
+ static bool isDirectoryValid(const std::string &directory) {
+ return directory.size() > 0 && directory[0] == '/';
+ }
+
+ std::string generateFilename(const std::string &suffix) const {
+ char fileTime[sizeof("YYYYmmdd_HHMMSS_\0")];
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ struct tm tm;
+ localtime_r(&tv.tv_sec, &tm);
+ LOG_ALWAYS_FATAL_IF(strftime(fileTime, sizeof(fileTime), "%Y%m%d_%H%M%S_", &tm) == 0,
+ "incorrect fileTime buffer");
+ char msec[4];
+ (void)snprintf(msec, sizeof(msec), "%03d", (int)(tv.tv_usec / 1000));
+ return mPrefix + fileTime + msec + suffix + ".wav";
+ }
+
+ bool isManagedFilename(const char *name) {
+ constexpr size_t FILENAME_LEN_DATE = 4 + 2 + 2 // %Y%m%d%
+ + 1 + 2 + 2 + 2 // _H%M%S
+ + 1 + 3; //_MSEC
+ const size_t prefixLen = mPrefix.size();
+ const size_t nameLen = strlen(name);
+
+ // reject on size, prefix, and .wav
+ if (nameLen < prefixLen + FILENAME_LEN_DATE + 4 /* .wav */
+ || strncmp(name, mPrefix.c_str(), prefixLen) != 0
+ || strcmp(name + nameLen - 4, ".wav") != 0) {
+ return false;
+ }
+
+ // validate date portion
+ const char *date = name + prefixLen;
+ return std::all_of(date, date + 8, isdigit)
+ && date[8] == '_'
+ && std::all_of(date + 9, date + 15, isdigit)
+ && date[15] == '_'
+ && std::all_of(date + 16, date + 19, isdigit);
+ }
+
+ // yet another ThreadPool implementation.
+ class ThreadPool {
+ public:
+ ThreadPool(size_t size)
+ : mThreadPoolSize(size)
+ { }
+
+ /** launches task "name" with associated function "func".
+ if the threadpool is exhausted, it will launch on calling function */
+ status_t launch(const std::string &name, std::function<status_t()> func);
+
+ private:
+ std::mutex mLock;
+ std::list<std::pair<
+ std::string, std::future<status_t>>> mFutures; // GUARDED_BY(mLock)
+
+ const size_t mThreadPoolSize;
+ } mThreadPool;
+
+ const std::string mPrefix;
+ std::mutex mLock;
+ std::string mDirectory; // GUARDED_BY(mLock)
+ std::deque<std::string> mFiles; // GUARDED_BY(mLock) sorted list of files by creation time
+
+ static constexpr size_t FRAMES_PER_READ = 1024;
+ static constexpr size_t MAX_FILES_READ = 1024;
+ static constexpr size_t MAX_FILES_KEEP = 32;
+};
+
+/* static */
+void NBAIO_Tee::NBAIO_TeeImpl::dumpTee(
+ int fd, const NBAIO_SinkSource &sinkSource, const std::string &suffix)
+{
+ // Singleton. Constructed thread-safe on first call, never destroyed.
+ static AudioFileHandler audioFileHandler(
+ DEFAULT_PREFIX, DEFAULT_DIRECTORY, DEFAULT_THREADPOOL_SIZE);
+
+ auto &source = sinkSource.second;
+ if (source.get() == nullptr) {
+ return;
+ }
+
+ const NBAIO_Format format = source->format();
+ bool firstRead = true;
+ std::string filename = audioFileHandler.create(
+ // this functor must not hold references to stack
+ [firstRead, sinkSource] (void *buffer, size_t frames) mutable {
+ auto &source = sinkSource.second;
+ ssize_t actualRead = source->read(buffer, frames);
+ if (actualRead == (ssize_t)OVERRUN && firstRead) {
+ // recheck once
+ actualRead = source->read(buffer, frames);
+ }
+ firstRead = false;
+ return actualRead;
+ },
+ Format_sampleRate(format),
+ Format_channelCount(format),
+ format.mFormat,
+ suffix);
+
+ if (fd >= 0 && filename.size() > 0) {
+ dprintf(fd, "tee wrote to %s\n", filename.c_str());
+ }
+}
+
+/* static */
+NBAIO_Tee::NBAIO_TeeImpl::NBAIO_SinkSource NBAIO_Tee::NBAIO_TeeImpl::makeSinkSource(
+ const NBAIO_Format &format, size_t frames, bool *enabled)
+{
+ if (Format_isValid(format) && audio_is_linear_pcm(format.mFormat)) {
+ Pipe *pipe = new Pipe(frames, format);
+ size_t numCounterOffers = 0;
+ const NBAIO_Format offers[1] = {format};
+ ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
+ if (index != 0) {
+ ALOGW("pipe failure to negotiate: %zd", index);
+ goto exit;
+ }
+ PipeReader *pipeReader = new PipeReader(*pipe);
+ numCounterOffers = 0;
+ index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
+ if (index != 0) {
+ ALOGW("pipeReader failure to negotiate: %zd", index);
+ goto exit;
+ }
+ if (enabled != nullptr) *enabled = true;
+ return {pipe, pipeReader};
+ }
+exit:
+ if (enabled != nullptr) *enabled = false;
+ return {nullptr, nullptr};
+}
+
+std::string AudioFileHandler::create(
+ std::function<ssize_t /* frames_read */
+ (void * /* buffer */, size_t /* size_in_frames */)> reader,
+ uint32_t sampleRate,
+ uint32_t channelCount,
+ audio_format_t format,
+ const std::string &suffix)
+{
+ const std::string filename = generateFilename(suffix);
+
+ if (mThreadPool.launch(std::string("create ") + filename,
+ [=]() { return createInternal(reader, sampleRate, channelCount, format, filename); })
+ == NO_ERROR) {
+ return filename;
+ }
+ return "";
+}
+
+status_t AudioFileHandler::setDirectory(const std::string &directory)
+{
+ if (!isDirectoryValid(directory)) return BAD_VALUE;
+
+ // TODO: consider using std::filesystem in C++17
+ DIR *dir = opendir(directory.c_str());
+
+ if (dir == nullptr) {
+ ALOGW("%s: cannot open directory %s", __func__, directory.c_str());
+ return BAD_VALUE;
+ }
+
+ size_t toRemove = 0;
+ decltype(mFiles) files;
+
+ while (files.size() < MAX_FILES_READ) {
+ errno = 0;
+ const struct dirent *result = readdir(dir);
+ if (result == nullptr) {
+ ALOGW_IF(errno != 0, "%s: readdir failure %s", __func__, strerror(errno));
+ break;
+ }
+ // is it a managed filename?
+ if (!isManagedFilename(result->d_name)) {
+ continue;
+ }
+ files.emplace_back(result->d_name);
+ }
+ (void)closedir(dir);
+
+ // OPTIMIZATION: we don't need to stat each file, the filenames names are
+ // already (roughly) ordered by creation date. we use std::deque instead
+ // of std::set for faster insertion and sorting times.
+
+ if (files.size() > MAX_FILES_KEEP) {
+ // removed files can use a partition (no need to do a full sort).
+ toRemove = files.size() - MAX_FILES_KEEP;
+ std::nth_element(files.begin(), files.begin() + toRemove - 1, files.end());
+ }
+
+ // kept files must be sorted.
+ std::sort(files.begin() + toRemove, files.end());
+
+ {
+ std::lock_guard<std::mutex> _l(mLock);
+
+ mDirectory = directory;
+ mFiles = std::move(files);
+ }
+
+ if (toRemove > 0) { // launch a clean in background.
+ (void)mThreadPool.launch(
+ std::string("cleaning ") + directory, [this]() { return clean(); });
+ }
+ return NO_ERROR;
+}
+
+status_t AudioFileHandler::clean(std::string *directory)
+{
+ std::vector<std::string> filesToRemove;
+ std::string dir;
+ {
+ std::lock_guard<std::mutex> _l(mLock);
+
+ if (!isDirectoryValid(mDirectory)) return NO_INIT;
+
+ dir = mDirectory;
+ if (mFiles.size() > MAX_FILES_KEEP) {
+ size_t toRemove = mFiles.size() - MAX_FILES_KEEP;
+
+ // use move and erase to efficiently transfer std::string
+ std::move(mFiles.begin(),
+ mFiles.begin() + toRemove,
+ std::back_inserter(filesToRemove));
+ mFiles.erase(mFiles.begin(), mFiles.begin() + toRemove);
+ }
+ }
+
+ std::string dirp = dir + "/";
+ // remove files outside of lock for better concurrency.
+ for (const auto &file : filesToRemove) {
+ (void)unlink((dirp + file).c_str());
+ }
+
+ // return the directory if requested.
+ if (directory != nullptr) {
+ *directory = dir;
+ }
+ return NO_ERROR;
+}
+
+status_t AudioFileHandler::ThreadPool::launch(
+ const std::string &name, std::function<status_t()> func)
+{
+ if (mThreadPoolSize > 1) {
+ std::lock_guard<std::mutex> _l(mLock);
+ if (mFutures.size() >= mThreadPoolSize) {
+ for (auto it = mFutures.begin(); it != mFutures.end();) {
+ const std::string &filename = it->first;
+ std::future<status_t> &future = it->second;
+ if (!future.valid() ||
+ future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ ALOGV("%s: future %s ready", __func__, filename.c_str());
+ it = mFutures.erase(it);
+ } else {
+ ALOGV("%s: future %s not ready", __func__, filename.c_str());
+ ++it;
+ }
+ }
+ }
+ if (mFutures.size() < mThreadPoolSize) {
+ ALOGV("%s: deferred calling %s", __func__, name.c_str());
+ mFutures.emplace_back(name, std::async(std::launch::async, func));
+ return NO_ERROR;
+ }
+ }
+ ALOGV("%s: immediate calling %s", __func__, name.c_str());
+ return func();
+}
+
+status_t AudioFileHandler::createInternal(
+ std::function<ssize_t /* frames_read */
+ (void * /* buffer */, size_t /* size_in_frames */)> reader,
+ uint32_t sampleRate,
+ uint32_t channelCount,
+ audio_format_t format,
+ const std::string &filename)
+{
+ // Attempt to choose the best matching file format.
+ // We can choose any sf_format
+ // but writeFormat must be one of 16, 32, float
+ // due to sf_writef compatibility.
+ int sf_format;
+ audio_format_t writeFormat;
+ switch (format) {
+ case AUDIO_FORMAT_PCM_8_BIT:
+ case AUDIO_FORMAT_PCM_16_BIT:
+ sf_format = SF_FORMAT_PCM_16;
+ writeFormat = AUDIO_FORMAT_PCM_16_BIT;
+ ALOGV("%s: %s using PCM_16 for format %#x", __func__, filename.c_str(), format);
+ break;
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ case AUDIO_FORMAT_PCM_32_BIT:
+ sf_format = SF_FORMAT_PCM_32;
+ writeFormat = AUDIO_FORMAT_PCM_32_BIT;
+ ALOGV("%s: %s using PCM_32 for format %#x", __func__, filename.c_str(), format);
+ break;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ sf_format = SF_FORMAT_FLOAT;
+ writeFormat = AUDIO_FORMAT_PCM_FLOAT;
+ ALOGV("%s: %s using PCM_FLOAT for format %#x", __func__, filename.c_str(), format);
+ break;
+ default:
+ // TODO:
+ // handle audio_has_proportional_frames() formats.
+ // handle compressed formats as single byte files.
+ return BAD_VALUE;
+ }
+
+ std::string directory;
+ status_t status = clean(&directory);
+ if (status != NO_ERROR) return status;
+ std::string dirPrefix = directory + "/";
+
+ const std::string path = dirPrefix + filename;
+
+ /* const */ SF_INFO info = {
+ .frames = 0,
+ .samplerate = (int)sampleRate,
+ .channels = (int)channelCount,
+ .format = SF_FORMAT_WAV | sf_format,
+ };
+ SNDFILE *sf = sf_open(path.c_str(), SFM_WRITE, &info);
+ if (sf == nullptr) {
+ return INVALID_OPERATION;
+ }
+
+ size_t total = 0;
+ void *buffer = malloc(FRAMES_PER_READ * std::max(
+ channelCount * audio_bytes_per_sample(writeFormat), //output framesize
+ channelCount * audio_bytes_per_sample(format))); // input framesize
+ if (buffer == nullptr) {
+ sf_close(sf);
+ return NO_MEMORY;
+ }
+
+ for (;;) {
+ const ssize_t actualRead = reader(buffer, FRAMES_PER_READ);
+ if (actualRead <= 0) {
+ break;
+ }
+
+ // Convert input format to writeFormat as needed.
+ if (format != writeFormat) {
+ memcpy_by_audio_format(
+ buffer, writeFormat, buffer, format, actualRead * info.channels);
+ }
+
+ ssize_t reallyWritten;
+ switch (writeFormat) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ reallyWritten = sf_writef_short(sf, (const int16_t *)buffer, actualRead);
+ break;
+ case AUDIO_FORMAT_PCM_32_BIT:
+ reallyWritten = sf_writef_int(sf, (const int32_t *)buffer, actualRead);
+ break;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ reallyWritten = sf_writef_float(sf, (const float *)buffer, actualRead);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("%s: %s writeFormat: %#x", __func__, filename.c_str(), writeFormat);
+ break;
+ }
+
+ if (reallyWritten < 0) {
+ ALOGW("%s: %s write error: %zd", __func__, filename.c_str(), reallyWritten);
+ break;
+ }
+ total += reallyWritten;
+ if (reallyWritten < actualRead) {
+ ALOGW("%s: %s write short count: %zd < %zd",
+ __func__, filename.c_str(), reallyWritten, actualRead);
+ break;
+ }
+ }
+ sf_close(sf);
+ free(buffer);
+ if (total == 0) {
+ (void)unlink(path.c_str());
+ return NOT_ENOUGH_DATA;
+ }
+
+ // Success: add our name to managed files.
+ {
+ std::lock_guard<std::mutex> _l(mLock);
+ // weak synchronization - only update mFiles if the directory hasn't changed.
+ if (mDirectory == directory) {
+ mFiles.emplace_back(filename); // add to the end to preserve sort.
+ }
+ }
+ return NO_ERROR; // return full path
+}
+
+} // namespace android
+
+#endif // TEE_SINK