AudioFlinger: Fix Tee logging

1) Fix bad WAV files for tee sources that aren't 16 bit PCM.
2) Allow multiple RecordThread tees instead of one.
3) Allow dump from non-fast MixerThreads (e.g. deep buffer).
4) Parallelize tee dumping for improved concurrency;
   dump outside of AF lock.
5) Async dumping to allow dump to be issued in time critical code.
6) Improve file naming to distinguish record vs playback tracks,
   threads, and dump reason.
7) Allow Tee insertion anywhere in code with global running
   Tee management.
8) Increase resolution of filename time to msec avoid file overwrite.
9) Dump track data upon removal from active tracks to improve
   timeliness of dumped data.
10) Dump tee data on tee destruction.
11) Refactor Tee code out of AudioFlinger.cpp; minimize footprint.

AudioFlinger enabling code requires Configuration.h define TEE_SINK;
this is disabled, hence avoiding regression risk.

Test: Enable tee log, repeatedly call dumpsys, examine files.
Bug: 78369241
Change-Id: I7b22cfa7dbbc1601828de931522065a509ab4047
gugelfrei
Andy Hung 6 years ago
parent acb7d9ddc1
commit 8946a28aaa

@ -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)

@ -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(

@ -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; }

@ -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++;

@ -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

@ -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) {

@ -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);

@ -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

@ -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

@ -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;

@ -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;

@ -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

@ -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

@ -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()

Loading…
Cancel
Save