1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "NBAIO_Tee"
18 //#define LOG_NDEBUG 0
19 
20 #include <utils/Log.h>
21 
22 #include <deque>
23 #include <dirent.h>
24 #include <future>
25 #include <list>
26 #include <vector>
27 
28 #include <audio_utils/format.h>
29 #include <audio_utils/sndfile.h>
30 #include <media/nbaio/PipeReader.h>
31 
32 #include "Configuration.h"
33 #include "NBAIO_Tee.h"
34 
35 // Enabled with TEE_SINK in Configuration.h
36 #ifdef TEE_SINK
37 
38 namespace android {
39 
40 /*
41  Tee filenames generated as follows:
42 
43  "aftee_Date_ThreadId_C_reason.wav" RecordThread
44  "aftee_Date_ThreadId_M_reason.wav" MixerThread (Normal)
45  "aftee_Date_ThreadId_F_reason.wav" MixerThread (Fast)
46  "aftee_Date_ThreadId_TrackId_R_reason.wav" RecordTrack
47  "aftee_Date_ThreadId_TrackId_TrackName_T_reason.wav" PlaybackTrack
48 
49  where Date = YYYYmmdd_HHMMSS_MSEC
50 
51  where Reason = [ DTOR | DUMP | REMOVE ]
52 
53  Examples:
54   aftee_20180424_153811_038_13_57_2_T_REMOVE.wav
55   aftee_20180424_153811_218_13_57_2_T_REMOVE.wav
56   aftee_20180424_153811_378_13_57_2_T_REMOVE.wav
57   aftee_20180424_153825_147_62_C_DUMP.wav
58   aftee_20180424_153825_148_62_59_R_DUMP.wav
59   aftee_20180424_153825_149_13_F_DUMP.wav
60   aftee_20180424_153842_125_62_59_R_REMOVE.wav
61   aftee_20180424_153842_168_62_C_DTOR.wav
62 */
63 
64 static constexpr char DEFAULT_PREFIX[] = "aftee_";
65 static constexpr char DEFAULT_DIRECTORY[] = "/data/misc/audioserver";
66 static constexpr size_t DEFAULT_THREADPOOL_SIZE = 8;
67 
68 /** AudioFileHandler manages temporary audio wav files with a least recently created
69     retention policy.
70 
71     The temporary filenames are systematically generated. A common filename prefix,
72     storage directory, and concurrency pool are passed in on creating the object.
73 
74     Temporary files are created by "create", which returns a filename generated by
75 
76     prefix + 14 char date + suffix
77 
78     TODO Move to audio_utils.
79     TODO Avoid pointing two AudioFileHandlers to the same directory and prefix
80     as we don't have a prefix specific lock file. */
81 
82 class AudioFileHandler {
83 public:
84 
AudioFileHandler(const std::string & prefix,const std::string & directory,size_t pool)85     AudioFileHandler(const std::string &prefix, const std::string &directory, size_t pool)
86         : mThreadPool(pool)
87         , mPrefix(prefix)
88     {
89         (void)setDirectory(directory);
90     }
91 
92     /** returns filename of created audio file, else empty string on failure. */
93     std::string create(
94             std::function<ssize_t /* frames_read */
95                         (void * /* buffer */, size_t /* size_in_frames */)> reader,
96             uint32_t sampleRate,
97             uint32_t channelCount,
98             audio_format_t format,
99             const std::string &suffix);
100 
101 private:
102     /** sets the current directory. this is currently private to avoid confusion
103         when changing while pending operations are occurring (it's okay, but
104         weakly synchronized). */
105     status_t setDirectory(const std::string &directory);
106 
107     /** cleans current directory and returns the directory name done. */
108     status_t clean(std::string *dir = nullptr);
109 
110     /** creates an audio file from a reader functor passed in. */
111     status_t createInternal(
112             std::function<ssize_t /* frames_read */
113                         (void * /* buffer */, size_t /* size_in_frames */)> reader,
114             uint32_t sampleRate,
115             uint32_t channelCount,
116             audio_format_t format,
117             const std::string &filename);
118 
isDirectoryValid(const std::string & directory)119     static bool isDirectoryValid(const std::string &directory) {
120         return directory.size() > 0 && directory[0] == '/';
121     }
122 
generateFilename(const std::string & suffix) const123     std::string generateFilename(const std::string &suffix) const {
124         char fileTime[sizeof("YYYYmmdd_HHMMSS_\0")];
125         struct timeval tv;
126         gettimeofday(&tv, NULL);
127         struct tm tm;
128         localtime_r(&tv.tv_sec, &tm);
129         LOG_ALWAYS_FATAL_IF(strftime(fileTime, sizeof(fileTime), "%Y%m%d_%H%M%S_", &tm) == 0,
130             "incorrect fileTime buffer");
131         char msec[4];
132         (void)snprintf(msec, sizeof(msec), "%03d", (int)(tv.tv_usec / 1000));
133         return mPrefix + fileTime + msec + suffix + ".wav";
134     }
135 
isManagedFilename(const char * name)136     bool isManagedFilename(const char *name) {
137         constexpr size_t FILENAME_LEN_DATE = 4 + 2 + 2 // %Y%m%d%
138             + 1 + 2 + 2 + 2 // _H%M%S
139             + 1 + 3; //_MSEC
140         const size_t prefixLen = mPrefix.size();
141         const size_t nameLen = strlen(name);
142 
143         // reject on size, prefix, and .wav
144         if (nameLen < prefixLen + FILENAME_LEN_DATE + 4 /* .wav */
145              || strncmp(name, mPrefix.c_str(), prefixLen) != 0
146              || strcmp(name + nameLen - 4, ".wav") != 0) {
147             return false;
148         }
149 
150         // validate date portion
151         const char *date = name + prefixLen;
152         return std::all_of(date, date + 8, isdigit)
153             && date[8] == '_'
154             && std::all_of(date + 9, date + 15, isdigit)
155             && date[15] == '_'
156             && std::all_of(date + 16, date + 19, isdigit);
157     }
158 
159     // yet another ThreadPool implementation.
160     class ThreadPool {
161     public:
ThreadPool(size_t size)162         ThreadPool(size_t size)
163             : mThreadPoolSize(size)
164         { }
165 
166         /** launches task "name" with associated function "func".
167             if the threadpool is exhausted, it will launch on calling function */
168         status_t launch(const std::string &name, std::function<status_t()> func);
169 
170     private:
171         std::mutex mLock;
172         std::list<std::pair<
173                 std::string, std::future<status_t>>> mFutures; // GUARDED_BY(mLock)
174 
175         const size_t mThreadPoolSize;
176     } mThreadPool;
177 
178     const std::string mPrefix;
179     std::mutex mLock;
180     std::string mDirectory;         // GUARDED_BY(mLock)
181     std::deque<std::string> mFiles; // GUARDED_BY(mLock)  sorted list of files by creation time
182 
183     static constexpr size_t FRAMES_PER_READ = 1024;
184     static constexpr size_t MAX_FILES_READ = 1024;
185     static constexpr size_t MAX_FILES_KEEP = 32;
186 };
187 
188 /* static */
dumpTee(int fd,const NBAIO_SinkSource & sinkSource,const std::string & suffix)189 void NBAIO_Tee::NBAIO_TeeImpl::dumpTee(
190         int fd, const NBAIO_SinkSource &sinkSource, const std::string &suffix)
191 {
192     // Singleton. Constructed thread-safe on first call, never destroyed.
193     static AudioFileHandler audioFileHandler(
194             DEFAULT_PREFIX, DEFAULT_DIRECTORY, DEFAULT_THREADPOOL_SIZE);
195 
196     auto &source = sinkSource.second;
197     if (source.get() == nullptr) {
198         return;
199     }
200 
201     const NBAIO_Format format = source->format();
202     bool firstRead = true;
203     std::string filename = audioFileHandler.create(
204             // this functor must not hold references to stack
205             [firstRead, sinkSource] (void *buffer, size_t frames) mutable {
206                     auto &source = sinkSource.second;
207                     ssize_t actualRead = source->read(buffer, frames);
208                     if (actualRead == (ssize_t)OVERRUN && firstRead) {
209                         // recheck once
210                         actualRead = source->read(buffer, frames);
211                     }
212                     firstRead = false;
213                     return actualRead;
214                 },
215             Format_sampleRate(format),
216             Format_channelCount(format),
217             format.mFormat,
218             suffix);
219 
220     if (fd >= 0 && filename.size() > 0) {
221         dprintf(fd, "tee wrote to %s\n", filename.c_str());
222     }
223 }
224 
225 /* static */
makeSinkSource(const NBAIO_Format & format,size_t frames,bool * enabled)226 NBAIO_Tee::NBAIO_TeeImpl::NBAIO_SinkSource NBAIO_Tee::NBAIO_TeeImpl::makeSinkSource(
227         const NBAIO_Format &format, size_t frames, bool *enabled)
228 {
229     if (Format_isValid(format) && audio_is_linear_pcm(format.mFormat)) {
230         Pipe *pipe = new Pipe(frames, format);
231         size_t numCounterOffers = 0;
232         const NBAIO_Format offers[1] = {format};
233         ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
234         if (index != 0) {
235             ALOGW("pipe failure to negotiate: %zd", index);
236             goto exit;
237         }
238         PipeReader *pipeReader = new PipeReader(*pipe);
239         numCounterOffers = 0;
240         index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
241         if (index != 0) {
242             ALOGW("pipeReader failure to negotiate: %zd", index);
243             goto exit;
244         }
245         if (enabled != nullptr) *enabled = true;
246         return {pipe, pipeReader};
247     }
248 exit:
249     if (enabled != nullptr) *enabled = false;
250     return {nullptr, nullptr};
251 }
252 
create(std::function<ssize_t (void *,size_t)> reader,uint32_t sampleRate,uint32_t channelCount,audio_format_t format,const std::string & suffix)253 std::string AudioFileHandler::create(
254         std::function<ssize_t /* frames_read */
255                     (void * /* buffer */, size_t /* size_in_frames */)> reader,
256         uint32_t sampleRate,
257         uint32_t channelCount,
258         audio_format_t format,
259         const std::string &suffix)
260 {
261     const std::string filename = generateFilename(suffix);
262 
263     if (mThreadPool.launch(std::string("create ") + filename,
264             [=]() { return createInternal(reader, sampleRate, channelCount, format, filename); })
265             == NO_ERROR) {
266         return filename;
267     }
268     return "";
269 }
270 
setDirectory(const std::string & directory)271 status_t AudioFileHandler::setDirectory(const std::string &directory)
272 {
273     if (!isDirectoryValid(directory)) return BAD_VALUE;
274 
275     // TODO: consider using std::filesystem in C++17
276     DIR *dir = opendir(directory.c_str());
277 
278     if (dir == nullptr) {
279         ALOGW("%s: cannot open directory %s", __func__, directory.c_str());
280         return BAD_VALUE;
281     }
282 
283     size_t toRemove = 0;
284     decltype(mFiles) files;
285 
286     while (files.size() < MAX_FILES_READ) {
287         errno = 0;
288         const struct dirent *result = readdir(dir);
289         if (result == nullptr) {
290             ALOGW_IF(errno != 0, "%s: readdir failure %s", __func__, strerror(errno));
291             break;
292         }
293         // is it a managed filename?
294         if (!isManagedFilename(result->d_name)) {
295             continue;
296         }
297         files.emplace_back(result->d_name);
298     }
299     (void)closedir(dir);
300 
301     // OPTIMIZATION: we don't need to stat each file, the filenames names are
302     // already (roughly) ordered by creation date.  we use std::deque instead
303     // of std::set for faster insertion and sorting times.
304 
305     if (files.size() > MAX_FILES_KEEP) {
306         // removed files can use a partition (no need to do a full sort).
307         toRemove = files.size() - MAX_FILES_KEEP;
308         std::nth_element(files.begin(), files.begin() + toRemove - 1, files.end());
309     }
310 
311     // kept files must be sorted.
312     std::sort(files.begin() + toRemove, files.end());
313 
314     {
315         std::lock_guard<std::mutex> _l(mLock);
316 
317         mDirectory = directory;
318         mFiles = std::move(files);
319     }
320 
321     if (toRemove > 0) { // launch a clean in background.
322         (void)mThreadPool.launch(
323                 std::string("cleaning ") + directory, [this]() { return clean(); });
324     }
325     return NO_ERROR;
326 }
327 
clean(std::string * directory)328 status_t AudioFileHandler::clean(std::string *directory)
329 {
330     std::vector<std::string> filesToRemove;
331     std::string dir;
332     {
333         std::lock_guard<std::mutex> _l(mLock);
334 
335         if (!isDirectoryValid(mDirectory)) return NO_INIT;
336 
337         dir = mDirectory;
338         if (mFiles.size() > MAX_FILES_KEEP) {
339             size_t toRemove = mFiles.size() - MAX_FILES_KEEP;
340 
341             // use move and erase to efficiently transfer std::string
342             std::move(mFiles.begin(),
343                     mFiles.begin() + toRemove,
344                     std::back_inserter(filesToRemove));
345             mFiles.erase(mFiles.begin(), mFiles.begin() + toRemove);
346         }
347     }
348 
349     std::string dirp = dir + "/";
350     // remove files outside of lock for better concurrency.
351     for (const auto &file : filesToRemove) {
352         (void)unlink((dirp + file).c_str());
353     }
354 
355     // return the directory if requested.
356     if (directory != nullptr) {
357         *directory = dir;
358     }
359     return NO_ERROR;
360 }
361 
launch(const std::string & name,std::function<status_t ()> func)362 status_t AudioFileHandler::ThreadPool::launch(
363         const std::string &name, std::function<status_t()> func)
364 {
365     if (mThreadPoolSize > 1) {
366         std::lock_guard<std::mutex> _l(mLock);
367         if (mFutures.size() >= mThreadPoolSize) {
368             for (auto it = mFutures.begin(); it != mFutures.end();) {
369                 const std::string &filename = it->first;
370                 std::future<status_t> &future = it->second;
371                 if (!future.valid() ||
372                         future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
373                     ALOGV("%s: future %s ready", __func__, filename.c_str());
374                     it = mFutures.erase(it);
375                 } else {
376                     ALOGV("%s: future %s not ready", __func__, filename.c_str());
377                     ++it;
378                 }
379             }
380         }
381         if (mFutures.size() < mThreadPoolSize) {
382             ALOGV("%s: deferred calling %s", __func__, name.c_str());
383             mFutures.emplace_back(name, std::async(std::launch::async, func));
384             return NO_ERROR;
385         }
386     }
387     ALOGV("%s: immediate calling %s", __func__, name.c_str());
388     return func();
389 }
390 
createInternal(std::function<ssize_t (void *,size_t)> reader,uint32_t sampleRate,uint32_t channelCount,audio_format_t format,const std::string & filename)391 status_t AudioFileHandler::createInternal(
392         std::function<ssize_t /* frames_read */
393                     (void * /* buffer */, size_t /* size_in_frames */)> reader,
394         uint32_t sampleRate,
395         uint32_t channelCount,
396         audio_format_t format,
397         const std::string &filename)
398 {
399     // Attempt to choose the best matching file format.
400     // We can choose any sf_format
401     // but writeFormat must be one of 16, 32, float
402     // due to sf_writef compatibility.
403     int sf_format;
404     audio_format_t writeFormat;
405     switch (format) {
406     case AUDIO_FORMAT_PCM_8_BIT:
407     case AUDIO_FORMAT_PCM_16_BIT:
408         sf_format = SF_FORMAT_PCM_16;
409         writeFormat = AUDIO_FORMAT_PCM_16_BIT;
410         ALOGV("%s: %s using PCM_16 for format %#x", __func__, filename.c_str(), format);
411         break;
412     case AUDIO_FORMAT_PCM_8_24_BIT:
413     case AUDIO_FORMAT_PCM_24_BIT_PACKED:
414     case AUDIO_FORMAT_PCM_32_BIT:
415         sf_format = SF_FORMAT_PCM_32;
416         writeFormat = AUDIO_FORMAT_PCM_32_BIT;
417         ALOGV("%s: %s using PCM_32 for format %#x", __func__, filename.c_str(), format);
418         break;
419     case AUDIO_FORMAT_PCM_FLOAT:
420         sf_format = SF_FORMAT_FLOAT;
421         writeFormat = AUDIO_FORMAT_PCM_FLOAT;
422         ALOGV("%s: %s using PCM_FLOAT for format %#x", __func__, filename.c_str(), format);
423         break;
424     default:
425         // TODO:
426         // handle audio_has_proportional_frames() formats.
427         // handle compressed formats as single byte files.
428         return BAD_VALUE;
429     }
430 
431     std::string directory;
432     status_t status = clean(&directory);
433     if (status != NO_ERROR) return status;
434     std::string dirPrefix = directory + "/";
435 
436     const std::string path = dirPrefix + filename;
437 
438     /* const */ SF_INFO info = {
439         .frames = 0,
440         .samplerate = (int)sampleRate,
441         .channels = (int)channelCount,
442         .format = SF_FORMAT_WAV | sf_format,
443     };
444     SNDFILE *sf = sf_open(path.c_str(), SFM_WRITE, &info);
445     if (sf == nullptr) {
446         return INVALID_OPERATION;
447     }
448 
449     size_t total = 0;
450     void *buffer = malloc(FRAMES_PER_READ * std::max(
451             channelCount * audio_bytes_per_sample(writeFormat), //output framesize
452             channelCount * audio_bytes_per_sample(format))); // input framesize
453     if (buffer == nullptr) {
454         sf_close(sf);
455         return NO_MEMORY;
456     }
457 
458     for (;;) {
459         const ssize_t actualRead = reader(buffer, FRAMES_PER_READ);
460         if (actualRead <= 0) {
461             break;
462         }
463 
464         // Convert input format to writeFormat as needed.
465         if (format != writeFormat) {
466             memcpy_by_audio_format(
467                     buffer, writeFormat, buffer, format, actualRead * info.channels);
468         }
469 
470         ssize_t reallyWritten;
471         switch (writeFormat) {
472         case AUDIO_FORMAT_PCM_16_BIT:
473             reallyWritten = sf_writef_short(sf, (const int16_t *)buffer, actualRead);
474             break;
475         case AUDIO_FORMAT_PCM_32_BIT:
476             reallyWritten = sf_writef_int(sf, (const int32_t *)buffer, actualRead);
477             break;
478         case AUDIO_FORMAT_PCM_FLOAT:
479             reallyWritten = sf_writef_float(sf, (const float *)buffer, actualRead);
480             break;
481         default:
482             LOG_ALWAYS_FATAL("%s: %s writeFormat: %#x", __func__, filename.c_str(), writeFormat);
483             break;
484         }
485 
486         if (reallyWritten < 0) {
487             ALOGW("%s: %s write error: %zd", __func__, filename.c_str(), reallyWritten);
488             break;
489         }
490         total += reallyWritten;
491         if (reallyWritten < actualRead) {
492             ALOGW("%s: %s write short count: %zd < %zd",
493                      __func__, filename.c_str(), reallyWritten, actualRead);
494             break;
495         }
496     }
497     sf_close(sf);
498     free(buffer);
499     if (total == 0) {
500         (void)unlink(path.c_str());
501         return NOT_ENOUGH_DATA;
502     }
503 
504     // Success: add our name to managed files.
505     {
506         std::lock_guard<std::mutex> _l(mLock);
507         // weak synchronization - only update mFiles if the directory hasn't changed.
508         if (mDirectory == directory) {
509             mFiles.emplace_back(filename);  // add to the end to preserve sort.
510         }
511     }
512     return NO_ERROR; // return full path
513 }
514 
515 } // namespace android
516 
517 #endif // TEE_SINK
518