summaryrefslogtreecommitdiffstats
path: root/services/audioflinger
diff options
context:
space:
mode:
Diffstat (limited to 'services/audioflinger')
-rw-r--r--services/audioflinger/Android.mk4
-rw-r--r--services/audioflinger/AudioFlinger.cpp227
-rw-r--r--services/audioflinger/AudioFlinger.h29
-rw-r--r--services/audioflinger/FastMixer.cpp19
-rw-r--r--services/audioflinger/FastMixer.h10
-rw-r--r--services/audioflinger/FastMixerState.cpp2
-rw-r--r--services/audioflinger/FastMixerState.h3
-rw-r--r--services/audioflinger/NBAIO_Tee.cpp517
-rw-r--r--services/audioflinger/NBAIO_Tee.h326
-rw-r--r--services/audioflinger/PlaybackTracks.h6
-rw-r--r--services/audioflinger/Threads.cpp63
-rw-r--r--services/audioflinger/Threads.h13
-rw-r--r--services/audioflinger/TrackBase.h11
-rw-r--r--services/audioflinger/Tracks.cpp35
14 files changed, 928 insertions, 337 deletions
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 78a62ca8dc..c0aa4777b7 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -13,7 +13,8 @@ LOCAL_SRC_FILES:= \
PatchPanel.cpp \
StateQueue.cpp \
BufLog.cpp \
- TypedLogger.cpp
+ TypedLogger.cpp \
+ NBAIO_Tee.cpp \
LOCAL_C_INCLUDES := \
frameworks/av/services/audiopolicy \
@@ -41,6 +42,7 @@ LOCAL_SHARED_LIBRARIES := \
LOCAL_STATIC_LIBRARIES := \
libcpustats \
+ libsndfile \
LOCAL_MULTILIB := $(AUDIOSERVER_MULTILIB)
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index f165c319e9..9d1142fa97 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -47,6 +47,7 @@
#include <system/audio.h>
#include "AudioFlinger.h"
+#include "NBAIO_Tee.h"
#include <media/AudioResamplerPublic.h>
@@ -55,7 +56,6 @@
#include <system/audio_effects/effect_aec.h>
#include <audio_utils/primitives.h>
-#include <audio_utils/string.h>
#include <powermanager/PowerManager.h>
@@ -100,17 +100,6 @@ nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
uint32_t AudioFlinger::mScreenState;
-
-#ifdef TEE_SINK
-bool AudioFlinger::mTeeSinkInputEnabled = false;
-bool AudioFlinger::mTeeSinkOutputEnabled = false;
-bool AudioFlinger::mTeeSinkTrackEnabled = false;
-
-size_t AudioFlinger::mTeeSinkInputFrames = kTeeSinkInputFramesDefault;
-size_t AudioFlinger::mTeeSinkOutputFrames = kTeeSinkOutputFramesDefault;
-size_t AudioFlinger::mTeeSinkTrackFrames = kTeeSinkTrackFramesDefault;
-#endif
-
// In order to avoid invalidating offloaded tracks each time a Visualizer is turned on and off
// we define a minimum time during which a global effect is considered enabled.
static const nsecs_t kMinGlobalEffectEnabletimeNs = seconds(7200);
@@ -186,27 +175,6 @@ AudioFlinger::AudioFlinger()
mEffectsFactoryHal = EffectsFactoryHalInterface::create();
mMediaLogNotifier->run("MediaLogNotifier");
-
-#ifdef TEE_SINK
- char value[PROPERTY_VALUE_MAX];
- (void) property_get("ro.debuggable", value, "0");
- int debuggable = atoi(value);
- int teeEnabled = 0;
- if (debuggable) {
- (void) property_get("af.tee", value, "0");
- teeEnabled = atoi(value);
- }
- // FIXME symbolic constants here
- if (teeEnabled & 1) {
- mTeeSinkInputEnabled = true;
- }
- if (teeEnabled & 2) {
- mTeeSinkOutputEnabled = true;
- }
- if (teeEnabled & 4) {
- mTeeSinkTrackEnabled = true;
- }
-#endif
}
void AudioFlinger::onFirstRef()
@@ -535,19 +503,16 @@ status_t AudioFlinger::dump(int fd, const Vector<String16>& args)
mPatchPanel.dump(fd);
-#ifdef TEE_SINK
- // dump the serially shared record tee sink
- if (mRecordTeeSource != 0) {
- dumpTee(fd, mRecordTeeSource, AUDIO_IO_HANDLE_NONE, 'C');
- }
-#endif
-
BUFLOG_RESET;
if (locked) {
mLock.unlock();
}
+#ifdef TEE_SINK
+ // NBAIO_Tee dump is safe to call outside of AF lock.
+ NBAIO_Tee::dumpAll(fd, "_DUMP");
+#endif
// append a copy of media.log here by forwarding fd to it, but don't attempt
// to lookup the service if it's not running, as it will block for a second
if (sMediaLogServiceAsBinder != 0) {
@@ -2430,55 +2395,6 @@ sp<AudioFlinger::ThreadBase> AudioFlinger::openInput_l(audio_module_handle_t mod
thread.get());
return thread;
} else {
-#ifdef TEE_SINK
- // Try to re-use most recently used Pipe to archive a copy of input for dumpsys,
- // or (re-)create if current Pipe is idle and does not match the new format
- sp<NBAIO_Sink> teeSink;
- enum {
- TEE_SINK_NO, // don't copy input
- TEE_SINK_NEW, // copy input using a new pipe
- TEE_SINK_OLD, // copy input using an existing pipe
- } kind;
- NBAIO_Format format = Format_from_SR_C(halconfig.sample_rate,
- audio_channel_count_from_in_mask(halconfig.channel_mask), halconfig.format);
- if (!mTeeSinkInputEnabled) {
- kind = TEE_SINK_NO;
- } else if (!Format_isValid(format)) {
- kind = TEE_SINK_NO;
- } else if (mRecordTeeSink == 0) {
- kind = TEE_SINK_NEW;
- } else if (mRecordTeeSink->getStrongCount() != 1) {
- kind = TEE_SINK_NO;
- } else if (Format_isEqual(format, mRecordTeeSink->format())) {
- kind = TEE_SINK_OLD;
- } else {
- kind = TEE_SINK_NEW;
- }
- switch (kind) {
- case TEE_SINK_NEW: {
- Pipe *pipe = new Pipe(mTeeSinkInputFrames, format);
- size_t numCounterOffers = 0;
- const NBAIO_Format offers[1] = {format};
- ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- PipeReader *pipeReader = new PipeReader(*pipe);
- numCounterOffers = 0;
- index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- mRecordTeeSink = pipe;
- mRecordTeeSource = pipeReader;
- teeSink = pipe;
- }
- break;
- case TEE_SINK_OLD:
- teeSink = mRecordTeeSink;
- break;
- case TEE_SINK_NO:
- default:
- break;
- }
-#endif
-
// Start record thread
// RecordThread requires both input and output device indication to forward to audio
// pre processing modules
@@ -2488,9 +2404,6 @@ sp<AudioFlinger::ThreadBase> AudioFlinger::openInput_l(audio_module_handle_t mod
primaryOutputDevice_l(),
devices,
mSystemReady
-#ifdef TEE_SINK
- , teeSink
-#endif
);
mRecordThreads.add(*input, thread);
ALOGV("openInput_l() created record thread: ID %d thread %p", *input, thread.get());
@@ -3393,136 +3306,6 @@ bool AudioFlinger::updateOrphanEffectChains(const sp<AudioFlinger::EffectModule>
}
-struct Entry {
-#define TEE_MAX_FILENAME 32 // %Y%m%d%H%M%S_%d.wav = 4+2+2+2+2+2+1+1+4+1 = 21
- char mFileName[TEE_MAX_FILENAME];
-};
-
-int comparEntry(const void *p1, const void *p2)
-{
- return strcmp(((const Entry *) p1)->mFileName, ((const Entry *) p2)->mFileName);
-}
-
-#ifdef TEE_SINK
-void AudioFlinger::dumpTee(int fd, const sp<NBAIO_Source>& source, audio_io_handle_t id, char suffix)
-{
- NBAIO_Source *teeSource = source.get();
- if (teeSource != NULL) {
- // .wav rotation
- // There is a benign race condition if 2 threads call this simultaneously.
- // They would both traverse the directory, but the result would simply be
- // failures at unlink() which are ignored. It's also unlikely since
- // normally dumpsys is only done by bugreport or from the command line.
- char teePath[PATH_MAX] = "/data/misc/audioserver";
- size_t teePathLen = strlen(teePath);
- DIR *dir = opendir(teePath);
- teePath[teePathLen++] = '/';
- if (dir != NULL) {
-#define TEE_MAX_SORT 20 // number of entries to sort
-#define TEE_MAX_KEEP 10 // number of entries to keep
- struct Entry entries[TEE_MAX_SORT];
- size_t entryCount = 0;
- while (entryCount < TEE_MAX_SORT) {
- errno = 0; // clear errno before readdir() to track potential errors.
- const struct dirent *result = readdir(dir);
- if (result == nullptr) {
- ALOGW_IF(errno != 0, "tee readdir() failure %s", strerror(errno));
- break;
- }
- // ignore non .wav file entries
- const size_t nameLen = strlen(result->d_name);
- if (nameLen <= 4 || nameLen >= TEE_MAX_FILENAME ||
- strcmp(&result->d_name[nameLen - 4], ".wav")) {
- continue;
- }
- (void)audio_utils_strlcpy(entries[entryCount++].mFileName, result->d_name);
- }
- (void) closedir(dir);
- if (entryCount > TEE_MAX_KEEP) {
- qsort(entries, entryCount, sizeof(Entry), comparEntry);
- for (size_t i = 0; i < entryCount - TEE_MAX_KEEP; ++i) {
- strcpy(&teePath[teePathLen], entries[i].mFileName);
- (void) unlink(teePath);
- }
- }
- } else {
- if (fd >= 0) {
- dprintf(fd, "unable to rotate tees in %.*s: %s\n", (int) teePathLen, teePath,
- strerror(errno));
- }
- }
- char teeTime[16];
- struct timeval tv;
- gettimeofday(&tv, NULL);
- struct tm tm;
- localtime_r(&tv.tv_sec, &tm);
- strftime(teeTime, sizeof(teeTime), "%Y%m%d%H%M%S", &tm);
- snprintf(&teePath[teePathLen], sizeof(teePath) - teePathLen, "%s_%d_%c.wav", teeTime, id,
- suffix);
- // if 2 dumpsys are done within 1 second, and rotation didn't work, then discard 2nd
- int teeFd = open(teePath, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, S_IRUSR | S_IWUSR);
- if (teeFd >= 0) {
- // FIXME use libsndfile
- char wavHeader[44];
- memcpy(wavHeader,
- "RIFF\0\0\0\0WAVEfmt \20\0\0\0\1\0\2\0\104\254\0\0\0\0\0\0\4\0\20\0data\0\0\0\0",
- sizeof(wavHeader));
- NBAIO_Format format = teeSource->format();
- unsigned channelCount = Format_channelCount(format);
- uint32_t sampleRate = Format_sampleRate(format);
- size_t frameSize = Format_frameSize(format);
- wavHeader[22] = channelCount; // number of channels
- wavHeader[24] = sampleRate; // sample rate
- wavHeader[25] = sampleRate >> 8;
- wavHeader[32] = frameSize; // block alignment
- wavHeader[33] = frameSize >> 8;
- write(teeFd, wavHeader, sizeof(wavHeader));
- size_t total = 0;
- bool firstRead = true;
-#define TEE_SINK_READ 1024 // frames per I/O operation
- void *buffer = malloc(TEE_SINK_READ * frameSize);
- for (;;) {
- size_t count = TEE_SINK_READ;
- ssize_t actual = teeSource->read(buffer, count);
- bool wasFirstRead = firstRead;
- firstRead = false;
- if (actual <= 0) {
- if (actual == (ssize_t) OVERRUN && wasFirstRead) {
- continue;
- }
- break;
- }
- ALOG_ASSERT(actual <= (ssize_t)count);
- write(teeFd, buffer, actual * frameSize);
- total += actual;
- }
- free(buffer);
- lseek(teeFd, (off_t) 4, SEEK_SET);
- uint32_t temp = 44 + total * frameSize - 8;
- // FIXME not big-endian safe
- write(teeFd, &temp, sizeof(temp));
- lseek(teeFd, (off_t) 40, SEEK_SET);
- temp = total * frameSize;
- // FIXME not big-endian safe
- write(teeFd, &temp, sizeof(temp));
- close(teeFd);
- // TODO Should create file with temporary name and then rename to final if non-empty.
- if (total > 0) {
- if (fd >= 0) {
- dprintf(fd, "tee copied to %s\n", teePath);
- }
- } else {
- unlink(teePath);
- }
- } else {
- if (fd >= 0) {
- dprintf(fd, "unable to create tee %s: %s\n", teePath, strerror(errno));
- }
- }
- }
-}
-#endif
-
// ----------------------------------------------------------------------------
status_t AudioFlinger::onTransact(
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 7cfe542412..a59c13ee1b 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -71,6 +71,7 @@
#include "AudioStreamOut.h"
#include "SpdifStreamOut.h"
#include "AudioHwDevice.h"
+#include "NBAIO_Tee.h"
#include <powermanager/IPowerManager.h>
@@ -800,35 +801,7 @@ private:
void filterReservedParameters(String8& keyValuePairs, uid_t callingUid);
-#ifdef TEE_SINK
- // all record threads serially share a common tee sink, which is re-created on format change
- sp<NBAIO_Sink> mRecordTeeSink;
- sp<NBAIO_Source> mRecordTeeSource;
-#endif
-
public:
-
-#ifdef TEE_SINK
- // tee sink, if enabled by property, allows dumpsys to write most recent audio to .wav file
- static void dumpTee(int fd, const sp<NBAIO_Source>& source, audio_io_handle_t id, char suffix);
-
- // whether tee sink is enabled by property
- static bool mTeeSinkInputEnabled;
- static bool mTeeSinkOutputEnabled;
- static bool mTeeSinkTrackEnabled;
-
- // runtime configured size of each tee sink pipe, in frames
- static size_t mTeeSinkInputFrames;
- static size_t mTeeSinkOutputFrames;
- static size_t mTeeSinkTrackFrames;
-
- // compile-time default size of tee sink pipes, in frames
- // 0x200000 stereo 16-bit PCM frames = 47.5 seconds at 44.1 kHz, 8 megabytes
- static const size_t kTeeSinkInputFramesDefault = 0x200000;
- static const size_t kTeeSinkOutputFramesDefault = 0x200000;
- static const size_t kTeeSinkTrackFramesDefault = 0x200000;
-#endif
-
// These methods read variables atomically without mLock,
// though the variables are updated with mLock.
bool isLowRamDevice() const { return mIsLowRamDevice; }
diff --git a/services/audioflinger/FastMixer.cpp b/services/audioflinger/FastMixer.cpp
index 79bb9fe039..7a029637de 100644
--- a/services/audioflinger/FastMixer.cpp
+++ b/services/audioflinger/FastMixer.cpp
@@ -47,7 +47,8 @@ namespace android {
/*static*/ const FastMixerState FastMixer::sInitial;
-FastMixer::FastMixer() : FastThread("cycle_ms", "load_us"),
+FastMixer::FastMixer(audio_io_handle_t parentIoHandle)
+ : FastThread("cycle_ms", "load_us"),
// mFastTrackNames
// mGenerations
mOutputSink(NULL),
@@ -66,8 +67,11 @@ FastMixer::FastMixer() : FastThread("cycle_ms", "load_us"),
mTotalNativeFramesWritten(0),
// timestamp
mNativeFramesWrittenButNotPresented(0), // the = 0 is to silence the compiler
- mMasterMono(false)
+ mMasterMono(false),
+ mThreadIoHandle(parentIoHandle)
{
+ (void)mThreadIoHandle; // prevent unused warning, see C++17 [[maybe_unused]]
+
// FIXME pass sInitial as parameter to base class constructor, and make it static local
mPrevious = &sInitial;
mCurrent = &sInitial;
@@ -220,6 +224,10 @@ void FastMixer::onStateChange()
previousTrackMask = 0;
mFastTracksGen = current->mFastTracksGen - 1;
dumpState->mFrameCount = frameCount;
+#ifdef TEE_SINK
+ mTee.set(mFormat, NBAIO_Tee::TEE_FLAG_OUTPUT_THREAD);
+ mTee.setId(std::string("_") + std::to_string(mThreadIoHandle) + "_F");
+#endif
} else {
previousTrackMask = previous->mTrackMask;
}
@@ -446,10 +454,9 @@ void FastMixer::onWork()
frameCount * Format_channelCount(mFormat));
}
// if non-NULL, then duplicate write() to this non-blocking sink
- NBAIO_Sink* teeSink;
- if ((teeSink = current->mTeeSink) != NULL) {
- (void) teeSink->write(buffer, frameCount);
- }
+#ifdef TEE_SINK
+ mTee.write(buffer, frameCount);
+#endif
// FIXME write() is non-blocking and lock-free for a properly implemented NBAIO sink,
// but this code should be modified to handle both non-blocking and blocking sinks
dumpState->mWriteSequence++;
diff --git a/services/audioflinger/FastMixer.h b/services/audioflinger/FastMixer.h
index 235d23faf9..1c86d9ae7d 100644
--- a/services/audioflinger/FastMixer.h
+++ b/services/audioflinger/FastMixer.h
@@ -22,6 +22,7 @@
#include "StateQueue.h"
#include "FastMixerState.h"
#include "FastMixerDumpState.h"
+#include "NBAIO_Tee.h"
namespace android {
@@ -32,7 +33,9 @@ typedef StateQueue<FastMixerState> FastMixerStateQueue;
class FastMixer : public FastThread {
public:
- FastMixer();
+ /** FastMixer constructor takes as param the parent MixerThread's io handle (id)
+ for purposes of identification. */
+ explicit FastMixer(audio_io_handle_t threadIoHandle);
virtual ~FastMixer();
FastMixerStateQueue* sq();
@@ -87,6 +90,11 @@ private:
// accessed without lock between multiple threads.
std::atomic_bool mMasterMono;
std::atomic_int_fast64_t mBoottimeOffset;
+
+ const audio_io_handle_t mThreadIoHandle; // parent thread id for debugging purposes
+#ifdef TEE_SINK
+ NBAIO_Tee mTee;
+#endif
}; // class FastMixer
} // namespace android
diff --git a/services/audioflinger/FastMixerState.cpp b/services/audioflinger/FastMixerState.cpp
index 36d8eef543..b98842d4a7 100644
--- a/services/audioflinger/FastMixerState.cpp
+++ b/services/audioflinger/FastMixerState.cpp
@@ -35,7 +35,7 @@ FastTrack::~FastTrack()
FastMixerState::FastMixerState() : FastThreadState(),
// mFastTracks
mFastTracksGen(0), mTrackMask(0), mOutputSink(NULL), mOutputSinkGen(0),
- mFrameCount(0), mTeeSink(NULL)
+ mFrameCount(0)
{
int ok = pthread_once(&sMaxFastTracksOnce, sMaxFastTracksInit);
if (ok != 0) {
diff --git a/services/audioflinger/FastMixerState.h b/services/audioflinger/FastMixerState.h
index 2be1e91930..c7fcbd83ce 100644
--- a/services/audioflinger/FastMixerState.h
+++ b/services/audioflinger/FastMixerState.h
@@ -77,9 +77,6 @@ struct FastMixerState : FastThreadState {
WRITE = 0x10, // write to output sink
MIX_WRITE = 0x18; // mix tracks and write to output sink
- // This might be a one-time configuration rather than per-state
- NBAIO_Sink* mTeeSink; // if non-NULL, then duplicate write()s to this non-blocking sink
-
// never returns NULL; asserts if command is invalid
static const char *commandToString(Command command);
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
diff --git a/services/audioflinger/NBAIO_Tee.h b/services/audioflinger/NBAIO_Tee.h
new file mode 100644
index 0000000000..fed8cc81c8
--- /dev/null
+++ b/services/audioflinger/NBAIO_Tee.h
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+
+// Enabled with TEE_SINK in Configuration.h
+#ifndef ANDROID_NBAIO_TEE_H
+#define ANDROID_NBAIO_TEE_H
+
+#ifdef TEE_SINK
+
+#include <atomic>
+#include <mutex>
+#include <set>
+
+#include <cutils/properties.h>
+#include <media/nbaio/NBAIO.h>
+
+namespace android {
+
+/**
+ * The NBAIO_Tee uses the NBAIO Pipe and PipeReader for nonblocking
+ * data collection, for eventual dump to log files.
+ * See https://source.android.com/devices/audio/debugging for how to
+ * enable by ro.debuggable and af.tee properties.
+ *
+ * The write() into the NBAIO_Tee is therefore nonblocking,
+ * but changing NBAIO_Tee formats with set() cannot be done during a write();
+ * usually the caller already implements this mutual exclusion.
+ *
+ * All other calls except set() vs write() may occur at any time.
+ *
+ * dump() disruption is minimized to the caller since system calls are executed
+ * in an asynchronous thread (when possible).
+ *
+ * Currently the NBAIO_Tee is "hardwired" for AudioFlinger support.
+ *
+ * Some AudioFlinger specific notes:
+ *
+ * 1) Tees capture only linear PCM data.
+ * 2) Tees without any data written are considered empty and do not generate
+ * any output files.
+ * 2) Once a Tee dumps data, it is considered "emptied" and new data
+ * needs to be written before another Tee file is generated.
+ * 3) Tee file format is
+ * WAV integer PCM 16 bit for AUDIO_FORMAT_PCM_8_BIT, AUDIO_FORMAT_PCM_16_BIT.
+ * WAV integer PCM 32 bit for AUDIO_FORMAT_PCM_8_24_BIT, AUDIO_FORMAT_PCM_24_BIT_PACKED
+ * AUDIO_FORMAT_PCM_32_BIT.
+ * WAV float PCM 32 bit for AUDIO_FORMAT_PCM_FLOAT.
+ *
+ * Input_Thread:
+ * 1) Capture buffer is teed when read from the HAL, before resampling for the AudioRecord
+ * client.
+ *
+ * Output_Thread:
+ * 1) MixerThreads will tee at the FastMixer output (if it has one) or at the
+ * NormalMixer output (if no FastMixer).
+ * 2) DuplicatingThreads do not tee any mixed data. Apply a tee on the downstream OutputTrack
+ * or on the upstream playback Tracks.
+ * 3) DirectThreads and OffloadThreads do not tee any data. The upstream track
+ * (if linear PCM format) may be teed to discover data.
+ * 4) MmapThreads are not supported.
+ *
+ * Tracks:
+ * 1) RecordTracks and playback Tracks tee as data is being written to or
+ * read from the shared client-server track buffer by the associated Threads.
+ * 2) The mechanism is on the AudioBufferProvider release() so large static Track
+ * playback may not show any Tee data depending on when it is released.
+ * 3) When a track becomes inactive, the Thread will trigger a dump.
+ */
+
+class NBAIO_Tee {
+public:
+ /* TEE_FLAG is used in set() and must match the flags for the af.tee property
+ given in https://source.android.com/devices/audio/debugging
+ */
+ enum TEE_FLAG {
+ TEE_FLAG_NONE = 0,
+ TEE_FLAG_INPUT_THREAD = (1 << 0), // treat as a Tee for input (Capture) Threads
+ TEE_FLAG_OUTPUT_THREAD = (1 << 1), // treat as a Tee for output (Playback) Threads
+ TEE_FLAG_TRACK = (1 << 2), // treat as a Tee for tracks (Record and Playback)
+ };
+
+ NBAIO_Tee()
+ : mTee(std::make_shared<NBAIO_TeeImpl>())
+ {
+ getRunningTees().add(mTee);
+ }
+
+ ~NBAIO_Tee() {
+ getRunningTees().remove(mTee);
+ dump(-1, "_DTOR"); // log any data remaining in Tee.
+ }
+
+ /**
+ * \brief set is used for deferred configuration of Tee.
+ *
+ * May be called anytime except concurrently with write().
+ *
+ * \param format NBAIO_Format used to open NBAIO pipes
+ * \param flags (https://source.android.com/devices/audio/debugging)
+ * - TEE_FLAG_NONE to bypass af.tee property checks (default);
+ * - TEE_FLAG_INPUT_THREAD to check af.tee if input thread logging set;
+ * - TEE_FLAG_OUTPUT_THREAD to check af.tee if output thread logging set;
+ * - TEE_FLAG_TRACK to check af.tee if track logging set.
+ * \param frames number of frames to open the NBAIO pipe (set to 0 to use default).
+ *
+ * \return
+ * - NO_ERROR on success (or format unchanged)
+ * - BAD_VALUE if format or flags invalid.
+ * - PERMISSION_DENIED if flags not allowed by af.tee
+ */
+
+ status_t set(const NBAIO_Format &format,
+ TEE_FLAG flags = TEE_FLAG_NONE, size_t frames = 0) const {
+ return mTee->set(format, flags, frames);
+ }
+
+ status_t set(uint32_t sampleRate, uint32_t channelCount, audio_format_t format,
+ TEE_FLAG flags = TEE_FLAG_NONE, size_t frames = 0) const {
+ return mTee->set(Format_from_SR_C(sampleRate, channelCount, format), flags, frames);
+ }
+
+ /**
+ * \brief write data to the tee.
+ *
+ * This call is lock free (as shared pointer and NBAIO is lock free);
+ * may be called simultaneous to all methods except set().
+ *
+ * \param buffer to write to pipe.
+ * \param frameCount in frames as specified by the format passed to set()
+ */
+
+ void write(const void *buffer, size_t frameCount) const {
+ mTee->write(buffer, frameCount);
+ }
+
+ /** sets Tee id string which identifies the generated file (should be unique). */
+ void setId(const std::string &id) const {
+ mTee->setId(id);
+ }
+
+ /**
+ * \brief dump the audio content written to the Tee.
+ *
+ * \param fd file descriptor to write dumped filename for logging, use -1 to ignore.
+ * \param reason string suffix to append to the generated file.
+ */
+ void dump(int fd, const std::string &reason = "") const {
+ mTee->dump(fd, reason);
+ }
+
+ /**
+ * \brief dump all Tees currently alive.
+ *
+ * \param fd file descriptor to write dumped filename for logging, use -1 to ignore.
+ * \param reason string suffix to append to the generated file.
+ */
+ static void dumpAll(int fd, const std::string &reason = "") {
+ getRunningTees().dump(fd, reason);
+ }
+
+private:
+
+ /** The underlying implementation of the Tee - the lifetime is through
+ a shared pointer so destruction of the NBAIO_Tee container may proceed
+ even though dumping is occurring. */
+ class NBAIO_TeeImpl {
+ public:
+ status_t set(const NBAIO_Format &format, TEE_FLAG flags, size_t frames) {
+ static const int teeConfig = property_get_bool("ro.debuggable", false)
+ ? property_get_int32("af.tee", 0) : 0;
+
+ // check the type of Tee
+ const TEE_FLAG type = TEE_FLAG(
+ flags & (TEE_FLAG_INPUT_THREAD | TEE_FLAG_OUTPUT_THREAD | TEE_FLAG_TRACK));
+
+ // parameter flags can't select multiple types.
+ if (__builtin_popcount(type) > 1) {
+ return BAD_VALUE;
+ }
+
+ // if type is set, we check to see if it is permitted by configuration.
+ if (type != 0 && (type & teeConfig) == 0) {
+ return PERMISSION_DENIED;
+ }
+
+ // determine number of frames for Tee
+ if (frames == 0) {
+ // TODO: consider varying frame count based on type.
+ frames = DEFAULT_TEE_FRAMES;
+ }
+
+ // TODO: should we check minimum number of frames?
+
+ // don't do anything if format and frames are the same.
+ if (Format_isEqual(format, mFormat) && frames == mFrames) {
+ return NO_ERROR;
+ }
+
+ bool enabled = false;
+ auto sinksource = makeSinkSource(format, frames, &enabled);
+
+ // enabled is set if makeSinkSource is successful.
+ // Note: as mentioned in NBAIO_Tee::set(), don't call set() while write() is
+ // ongoing.
+ if (enabled) {
+ std::lock_guard<std::mutex> _l(mLock);
+ mFlags = flags;
+ mFormat = format; // could get this from the Sink.
+ mFrames = frames;
+ mSinkSource = std::move(sinksource);
+ mEnabled.store(true);
+ return NO_ERROR;
+ }
+ return BAD_VALUE;
+ }
+
+ void setId(const std::string &id) {
+ std::lock_guard<std::mutex> _l(mLock);
+ mId = id;
+ }
+
+ void dump(int fd, const std::string &reason) {
+ if (!mDataReady.exchange(false)) return;
+ std::string suffix;
+ NBAIO_SinkSource sinkSource;
+ {
+ std::lock_guard<std::mutex> _l(mLock);
+ suffix = mId + reason;
+ sinkSource = mSinkSource;
+ }
+ dumpTee(fd, sinkSource, suffix);
+ }
+
+ void write(const void *buffer, size_t frameCount) {
+ if (!mEnabled.load() || frameCount == 0) return;
+ (void)mSinkSource.first->write(buffer, frameCount);
+ mDataReady.store(true);
+ }
+
+ private:
+ // TRICKY: We need to keep the NBAIO_Sink and NBAIO_Source both alive at the same time
+ // because PipeReader holds a naked reference (not a strong or weak pointer) to Pipe.
+ using NBAIO_SinkSource = std::pair<sp<NBAIO_Sink>, sp<NBAIO_Source>>;
+
+ static void dumpTee(int fd, const NBAIO_SinkSource& sinkSource, const std::string& suffix);
+
+ static NBAIO_SinkSource makeSinkSource(
+ const NBAIO_Format &format, size_t frames, bool *enabled);
+
+ // 0x200000 stereo 16-bit PCM frames = 47.5 seconds at 44.1 kHz, 8 megabytes
+ static constexpr size_t DEFAULT_TEE_FRAMES = 0x200000;
+
+ // atomic status checking
+ std::atomic<bool> mEnabled{false};
+ std::atomic<bool> mDataReady{false};
+
+ // locked dump information
+ mutable std::mutex mLock;
+ std::string mId; // GUARDED_BY(mLock)
+ TEE_FLAG mFlags = TEE_FLAG_NONE; // GUARDED_BY(mLock)
+ NBAIO_Format mFormat = Format_Invalid; // GUARDED_BY(mLock)
+ size_t mFrames = 0; // GUARDED_BY(mLock)
+ NBAIO_SinkSource mSinkSource; // GUARDED_BY(mLock)
+ };
+
+ /** RunningTees tracks current running tees for dump purposes.
+ It is implemented to have minimal locked regions, to be transparent to the caller. */
+ class RunningTees {
+ public:
+ void add(const std::shared_ptr<NBAIO_TeeImpl> &tee) {
+ std::lock_guard<std::mutex> _l(mLock);
+ ALOGW_IF(!mTees.emplace(tee).second,
+ "%s: %p already exists in mTees", __func__, tee.get());
+ }
+
+ void remove(const std::shared_ptr<NBAIO_TeeImpl> &tee) {
+ std::lock_guard<std::mutex> _l(mLock);
+ ALOGW_IF(mTees.erase(tee) != 1,
+ "%s: %p doesn't exist in mTees", __func__, tee.get());
+ }
+
+ void dump(int fd, const std::string &reason) {
+ std::vector<std::shared_ptr<NBAIO_TeeImpl>> tees; // safe snapshot of tees
+ {
+ std::lock_guard<std::mutex> _l(mLock);
+ tees.insert(tees.end(), mTees.begin(), mTees.end());
+ }
+ for (const auto &tee : tees) {
+ tee->dump(fd, reason);
+ }
+ }
+
+ private:
+ std::mutex mLock;
+ std::set<std::shared_ptr<NBAIO_TeeImpl>> mTees; // GUARDED_BY(mLock)
+ };
+
+ // singleton
+ static RunningTees &getRunningTees() {
+ static RunningTees runningTees;
+ return runningTees;
+ }
+
+ // The NBAIO TeeImpl may have lifetime longer than NBAIO_Tee if
+ // RunningTees::dump() is being called simultaneous to ~NBAIO_Tee().
+ // This is allowed for maximum concurrency.
+ const std::shared_ptr<NBAIO_TeeImpl> mTee;
+}; // NBAIO_Tee
+
+} // namespace android
+
+#endif // TEE_SINK
+#endif // !ANDROID_NBAIO_TEE_H
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index a78be99294..ff9444da56 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -56,6 +56,12 @@ public:
LOG_ALWAYS_FATAL_IF(mName >= 0 && name >= 0,
"%s both old name %d and new name %d are valid", __func__, mName, name);
mName = name;
+#ifdef TEE_SINK
+ mTee.setId(std::string("_") + std::to_string(mThreadIoHandle)
+ + "_" + std::to_string(mId)
+ + "_" + std::to_string(mName)
+ + "_T");
+#endif
}
virtual uint32_t sampleRate() const;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 7b5d9e640a..c1b091227c 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1579,6 +1579,9 @@ ssize_t AudioFlinger::ThreadBase::ActiveTracks<T>::remove(const sp<T> &track) {
--mBatteryCounter[track->uid()].second;
// mLatestActiveTrack is not cleared even if is the same as track.
mHasChanged = true;
+#ifdef TEE_SINK
+ track->dumpTee(-1 /* fd */, "_REMOVE");
+#endif
return index;
}
@@ -2853,6 +2856,9 @@ ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
ATRACE_END();
if (framesWritten > 0) {
bytesWritten = framesWritten * mFrameSize;
+#ifdef TEE_SINK
+ mTee.write((char *)mSinkBuffer + offset, framesWritten);
+#endif
} else {
bytesWritten = framesWritten;
}
@@ -3866,9 +3872,7 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
// create a MonoPipe to connect our submix to FastMixer
NBAIO_Format format = mOutputSink->format();
-#ifdef TEE_SINK
- NBAIO_Format origformat = format;
-#endif
+
// adjust format to match that of the Fast Mixer
ALOGV("format changed from %#x to %#x", format.mFormat, fastMixerFormat);
format.mFormat = fastMixerFormat;
@@ -3880,7 +3884,7 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
MonoPipe *monoPipe = new MonoPipe(mNormalFrameCount * 4, format, true /*writeCanBlock*/);
const NBAIO_Format offers[1] = {format};
size_t numCounterOffers = 0;
-#if !LOG_NDEBUG || defined(TEE_SINK)
+#if !LOG_NDEBUG
ssize_t index =
#else
(void)
@@ -3891,25 +3895,8 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
(monoPipe->maxFrames() * 7) / 8 : mNormalFrameCount * 2);
mPipeSink = monoPipe;
-#ifdef TEE_SINK
- if (mTeeSinkOutputEnabled) {
- // create a Pipe to archive a copy of FastMixer's output for dumpsys
- Pipe *teeSink = new Pipe(mTeeSinkOutputFrames, origformat);
- const NBAIO_Format offers2[1] = {origformat};
- numCounterOffers = 0;
- index = teeSink->negotiate(offers2, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- mTeeSink = teeSink;
- PipeReader *teeSource = new PipeReader(*teeSink);
- numCounterOffers = 0;
- index = teeSource->negotiate(offers2, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- mTeeSource = teeSource;
- }
-#endif
-
// create fast mixer and configure it initially with just one fast track for our submix
- mFastMixer = new FastMixer();
+ mFastMixer = new FastMixer(mId);
FastMixerStateQueue *sq = mFastMixer->sq();
#ifdef STATE_QUEUE_DUMP
sq->setObserverDump(&mStateQueueObserverDump);
@@ -3935,9 +3922,6 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
state->mColdFutexAddr = &mFastMixerFutex;
state->mColdGen++;
state->mDumpState = &mFastMixerDumpState;
-#ifdef TEE_SINK
- state->mTeeSink = mTeeSink.get();
-#endif
mFastMixerNBLogWriter = audioFlinger->newWriter_l(kFastMixerLogSize, "FastMixer");
state->mNBLogWriter = mFastMixerNBLogWriter.get();
sq->end();
@@ -3957,7 +3941,12 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
tid = mAudioWatchdog->getTid();
sendPrioConfigEvent(getpid_cached, tid, kPriorityFastMixer, false /*forApp*/);
#endif
-
+ } else {
+#ifdef TEE_SINK
+ // Only use the MixerThread tee if there is no FastMixer.
+ mTee.set(mOutputSink->format(), NBAIO_Tee::TEE_FLAG_OUTPUT_THREAD);
+ mTee.setId(std::string("_") + std::to_string(mId) + "_M");
+#endif
}
switch (kUseFastMixer) {
@@ -5064,12 +5053,6 @@ void AudioFlinger::MixerThread::dumpInternals(int fd, const Vector<String16>& ar
} else {
dprintf(fd, " No FastMixer\n");
}
-
-#ifdef TEE_SINK
- // Write the tee output to a .wav file
- dumpTee(fd, mTeeSource, mId, 'M');
-#endif
-
}
uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() const
@@ -6235,9 +6218,6 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
audio_devices_t outDevice,
audio_devices_t inDevice,
bool systemReady
-#ifdef TEE_SINK
- , const sp<NBAIO_Sink>& teeSink
-#endif
) :
ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD, systemReady),
mInput(input),
@@ -6245,9 +6225,6 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
mRsmpInBuffer(NULL),
// mRsmpInFrames, mRsmpInFramesP2, and mRsmpInFramesOA are set by readInputParameters_l()
mRsmpInRear(0)
-#ifdef TEE_SINK
- , mTeeSink(teeSink)
-#endif
, mReadOnlyHeap(new MemoryDealer(kRecordThreadReadOnlyHeapSize,
"RecordThreadRO", MemoryHeapBase::READ_ONLY))
// mFastCapture below
@@ -6370,6 +6347,10 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
mFastTrackAvail = true;
}
+#ifdef TEE_SINK
+ mTee.set(mInputSource->format(), NBAIO_Tee::TEE_FLAG_INPUT_THREAD);
+ mTee.setId(std::string("_") + std::to_string(mId) + "_C");
+#endif
failed: ;
// FIXME mNormalSource
@@ -6712,9 +6693,9 @@ reacquire_wakelock:
}
ALOG_ASSERT(framesRead > 0);
- if (mTeeSink != 0) {
- (void) mTeeSink->write((uint8_t*)mRsmpInBuffer + rear * mFrameSize, framesRead);
- }
+#ifdef TEE_SINK
+ (void)mTee.write((uint8_t*)mRsmpInBuffer + rear * mFrameSize, framesRead);
+#endif
// If destination is non-contiguous, we now correct for reading past end of buffer.
{
size_t part1 = mRsmpInFramesP2 - rear;
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 5e5e9487fc..f18294b728 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -497,6 +497,9 @@ protected:
// we must not wait for async write callback in the thread loop before evaluating it
bool mSignalPending;
+#ifdef TEE_SINK
+ NBAIO_Tee mTee;
+#endif
// ActiveTracks is a sorted vector of track type T representing the
// active tracks of threadLoop() to be considered by the locked prepare portion.
// ActiveTracks should be accessed with the ThreadBase lock held.
@@ -1054,11 +1057,6 @@ private:
sp<NBAIO_Sink> mPipeSink;
// The current sink for the normal mixer to write it's (sub)mix, mOutputSink or mPipeSink
sp<NBAIO_Sink> mNormalSink;
-#ifdef TEE_SINK
- // For dumpsys
- sp<NBAIO_Sink> mTeeSink;
- sp<NBAIO_Source> mTeeSource;
-#endif
uint32_t mScreenState; // cached copy of gScreenState
// TODO: add comment and adjust size as needed
static const size_t kFastMixerLogSize = 8 * 1024;
@@ -1374,9 +1372,6 @@ public:
audio_devices_t outDevice,
audio_devices_t inDevice,
bool systemReady
-#ifdef TEE_SINK
- , const sp<NBAIO_Sink>& teeSink
-#endif
);
virtual ~RecordThread();
@@ -1506,8 +1501,6 @@ private:
int32_t mRsmpInRear; // last filled frame + 1
// For dumpsys
- const sp<NBAIO_Sink> mTeeSink;
-
const sp<MemoryDealer> mReadOnlyHeap;
// one-time initialization, no locks required
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index ccfb69fbb2..9f17c3ce72 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -100,6 +100,12 @@ public:
audio_attributes_t attributes() const { return mAttr; }
+#ifdef TEE_SINK
+ void dumpTee(int fd, const std::string &reason) const {
+ mTee.dump(fd, reason);
+ }
+#endif
+
protected:
DISALLOW_COPY_AND_ASSIGN(TrackBase);
@@ -208,8 +214,9 @@ protected:
const bool mIsOut;
sp<ServerProxy> mServerProxy;
const int mId;
- sp<NBAIO_Sink> mTeeSink;
- sp<NBAIO_Source> mTeeSource;
+#ifdef TEE_SINK
+ NBAIO_Tee mTee;
+#endif
bool mTerminated;
track_type mType; // must be one of TYPE_DEFAULT, TYPE_OUTPUT, TYPE_PATCH ...
audio_io_handle_t mThreadIoHandle; // I/O handle of the thread the track is attached to
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index fc8f34b5f0..9fd17c9930 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -210,22 +210,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase(
mBufferSize = bufferSize;
#ifdef TEE_SINK
- if (mTeeSinkTrackEnabled) {
- NBAIO_Format pipeFormat = Format_from_SR_C(mSampleRate, mChannelCount, mFormat);
- if (Format_isValid(pipeFormat)) {
- Pipe *pipe = new Pipe(mTeeSinkTrackFrames, pipeFormat);
- size_t numCounterOffers = 0;
- const NBAIO_Format offers[1] = {pipeFormat};
- ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- PipeReader *pipeReader = new PipeReader(*pipe);
- numCounterOffers = 0;
- index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
- ALOG_ASSERT(index == 0);
- mTeeSink = pipe;
- mTeeSource = pipeReader;
- }
- }
+ mTee.set(sampleRate, mChannelCount, format, NBAIO_Tee::TEE_FLAG_TRACK);
#endif
}
@@ -244,9 +229,6 @@ status_t AudioFlinger::ThreadBase::TrackBase::initCheck() const
AudioFlinger::ThreadBase::TrackBase::~TrackBase()
{
-#ifdef TEE_SINK
- dumpTee(-1, mTeeSource, mId, 'T');
-#endif
// delete the proxy before deleting the shared memory it refers to, to avoid dangling reference
mServerProxy.clear();
if (mCblk != NULL) {
@@ -274,9 +256,7 @@ AudioFlinger::ThreadBase::TrackBase::~TrackBase()
void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer)
{
#ifdef TEE_SINK
- if (mTeeSink != 0) {
- (void) mTeeSink->write(buffer->raw, buffer->frameCount);
- }
+ mTee.write(buffer->raw, buffer->frameCount);
#endif
ServerProxy::Buffer buf;
@@ -454,6 +434,12 @@ AudioFlinger::PlaybackThread::Track::Track(
thread->mFastTrackAvailMask &= ~(1 << i);
}
mName = TRACK_NAME_PENDING;
+
+#ifdef TEE_SINK
+ mTee.setId(std::string("_") + std::to_string(mThreadIoHandle)
+ + "_" + std::to_string(mId) +
+ + "_PEND_T");
+#endif
}
AudioFlinger::PlaybackThread::Track::~Track()
@@ -1695,6 +1681,11 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack(
ALOG_ASSERT(thread->mFastTrackAvail);
thread->mFastTrackAvail = false;
}
+#ifdef TEE_SINK
+ mTee.setId(std::string("_") + std::to_string(mThreadIoHandle)
+ + "_" + std::to_string(mId)
+ + "_R");
+#endif
}
AudioFlinger::RecordThread::RecordTrack::~RecordTrack()