diff options
Diffstat (limited to 'services/audioflinger')
-rw-r--r-- | services/audioflinger/Android.mk | 4 | ||||
-rw-r--r-- | services/audioflinger/AudioFlinger.cpp | 227 | ||||
-rw-r--r-- | services/audioflinger/AudioFlinger.h | 29 | ||||
-rw-r--r-- | services/audioflinger/FastMixer.cpp | 19 | ||||
-rw-r--r-- | services/audioflinger/FastMixer.h | 10 | ||||
-rw-r--r-- | services/audioflinger/FastMixerState.cpp | 2 | ||||
-rw-r--r-- | services/audioflinger/FastMixerState.h | 3 | ||||
-rw-r--r-- | services/audioflinger/NBAIO_Tee.cpp | 517 | ||||
-rw-r--r-- | services/audioflinger/NBAIO_Tee.h | 326 | ||||
-rw-r--r-- | services/audioflinger/PlaybackTracks.h | 6 | ||||
-rw-r--r-- | services/audioflinger/Threads.cpp | 63 | ||||
-rw-r--r-- | services/audioflinger/Threads.h | 13 | ||||
-rw-r--r-- | services/audioflinger/TrackBase.h | 11 | ||||
-rw-r--r-- | services/audioflinger/Tracks.cpp | 35 |
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() |