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