• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_D_reason.raw" DirectOutputThread (SpdifStreamOut)
47  "aftee_Date_ThreadId_TrackId_R_reason.wav" RecordTrack
48  "aftee_Date_ThreadId_TrackId_TrackName_T_reason.wav" PlaybackTrack
49 
50  where Date = YYYYmmdd_HHMMSS_MSEC
51 
52  where Reason = [ DTOR | DUMP | REMOVE ]
53 
54  Examples:
55   aftee_20180424_153811_038_13_57_2_T_REMOVE.wav
56   aftee_20180424_153811_218_13_57_2_T_REMOVE.wav
57   aftee_20180424_153811_378_13_57_2_T_REMOVE.wav
58   aftee_20180424_153825_147_62_C_DUMP.wav
59   aftee_20180424_153825_148_62_59_R_DUMP.wav
60   aftee_20180424_153825_149_13_F_DUMP.wav
61   aftee_20180424_153842_125_62_59_R_REMOVE.wav
62   aftee_20180424_153842_168_62_C_DTOR.wav
63 */
64 
65 static constexpr char DEFAULT_PREFIX[] = "aftee_";
66 static constexpr char DEFAULT_DIRECTORY[] = "/data/misc/audioserver";
67 static constexpr size_t DEFAULT_THREADPOOL_SIZE = 8;
68 
69 /** AudioFileHandler manages temporary audio wav files with a least recently created
70     retention policy.
71 
72     The temporary filenames are systematically generated. A common filename prefix,
73     storage directory, and concurrency pool are passed in on creating the object.
74 
75     Temporary files are created by "create", which returns a filename generated by
76 
77     prefix + 14 char date + suffix
78 
79     TODO Move to audio_utils.
80     TODO Avoid pointing two AudioFileHandlers to the same directory and prefix
81     as we don't have a prefix specific lock file. */
82 
83 class AudioFileHandler {
84 public:
85 
AudioFileHandler(const std::string & prefix,const std::string & directory,size_t pool)86     AudioFileHandler(const std::string &prefix, const std::string &directory, size_t pool)
87         : mThreadPool(pool)
88         , mPrefix(prefix)
89     {
90         (void)setDirectory(directory);
91     }
92 
93     /** returns filename of created audio file, else empty string on failure. */
94     std::string create(
95             const std::function<ssize_t /* frames_read */
96                         (void * /* buffer */, size_t /* size_in_frames */)>& reader,
97             uint32_t sampleRate,
98             uint32_t channelCount,
99             audio_format_t format,
100             const std::string &suffix);
101 
102 private:
103     /** sets the current directory. this is currently private to avoid confusion
104         when changing while pending operations are occurring (it's okay, but
105         weakly synchronized). */
106     status_t setDirectory(const std::string &directory);
107 
108     /** cleans current directory and returns the directory name done. */
109     status_t clean(std::string *dir = nullptr);
110 
111     /** creates an audio file from a reader functor passed in. */
112     status_t createInternal(
113             const std::function<ssize_t /* frames_read */
114                         (void * /* buffer */, size_t /* size_in_frames */)>& reader,
115             uint32_t sampleRate,
116             uint32_t channelCount,
117             audio_format_t format,
118             const std::string &filename);
119 
isDirectoryValid(const std::string & directory)120     static bool isDirectoryValid(const std::string &directory) {
121         return directory.size() > 0 && directory[0] == '/';
122     }
123 
generateFilename(const std::string & suffix,audio_format_t format) const124     std::string generateFilename(const std::string &suffix, audio_format_t format) const {
125         char fileTime[sizeof("YYYYmmdd_HHMMSS_\0")];
126         struct timeval tv;
127         gettimeofday(&tv, nullptr /* struct timezone */);
128         struct tm tm;
129         localtime_r(&tv.tv_sec, &tm);
130         LOG_ALWAYS_FATAL_IF(strftime(fileTime, sizeof(fileTime), "%Y%m%d_%H%M%S_", &tm) == 0,
131             "incorrect fileTime buffer");
132         char msec[4];
133         (void)snprintf(msec, sizeof(msec), "%03d", (int)(tv.tv_usec / 1000));
134         return mPrefix + fileTime + msec + suffix + (audio_is_linear_pcm(format) ? ".wav" : ".raw");
135     }
136 
isManagedFilename(const char * name)137     bool isManagedFilename(const char *name) {
138         constexpr size_t FILENAME_LEN_DATE = 4 + 2 + 2 // %Y%m%d%
139             + 1 + 2 + 2 + 2 // _H%M%S
140             + 1 + 3; //_MSEC
141         const size_t prefixLen = mPrefix.size();
142         const size_t nameLen = strlen(name);
143 
144         // reject on size, prefix, and .wav
145         if (nameLen < prefixLen + FILENAME_LEN_DATE + 4 /* .wav */
146              || strncmp(name, mPrefix.c_str(), prefixLen) != 0
147              || strcmp(name + nameLen - 4, ".wav") != 0) {
148             return false;
149         }
150 
151         // validate date portion
152         const char *date = name + prefixLen;
153         return std::all_of(date, date + 8, isdigit)
154             && date[8] == '_'
155             && std::all_of(date + 9, date + 15, isdigit)
156             && date[15] == '_'
157             && std::all_of(date + 16, date + 19, isdigit);
158     }
159 
160     // yet another ThreadPool implementation.
161     class ThreadPool {
162     public:
ThreadPool(size_t size)163         explicit ThreadPool(size_t size)
164             : mThreadPoolSize(size)
165         { }
166 
167         /** launches task "name" with associated function "func".
168             if the threadpool is exhausted, it will launch on calling function */
169         status_t launch(const std::string &name, const std::function<status_t()>& func);
170 
171     private:
172         const size_t mThreadPoolSize;
173         std::mutex mLock;
174         std::list<std::pair<
175                 std::string, std::future<status_t>>> mFutures; // GUARDED_BY(mLock);
176     } mThreadPool;
177 
178     static constexpr size_t FRAMES_PER_READ = 1024;
179     static constexpr size_t MAX_FILES_READ = 1024;
180     static constexpr size_t MAX_FILES_KEEP = 32;
181 
182     const std::string mPrefix;
183     std::mutex mLock;
184     std::string mDirectory;         // GUARDED_BY(mLock);
185     std::deque<std::string> mFiles; // GUARDED_BY(mLock); // sorted list of files by creation time
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     const 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_has_proportional_frames(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(
234                 offers, 1 /* numOffers */, nullptr /* counterOffers */, numCounterOffers);
235         if (index != 0) {
236             ALOGW("pipe failure to negotiate: %zd", index);
237             goto exit;
238         }
239         PipeReader *pipeReader = new PipeReader(*pipe);
240         numCounterOffers = 0;
241         index = pipeReader->negotiate(
242                 offers, 1 /* numOffers */, nullptr /* counterOffers */, numCounterOffers);
243         if (index != 0) {
244             ALOGW("pipeReader failure to negotiate: %zd", index);
245             goto exit;
246         }
247         if (enabled != nullptr) *enabled = true;
248         return {pipe, pipeReader};
249     }
250 exit:
251     if (enabled != nullptr) *enabled = false;
252     return {nullptr, nullptr};
253 }
254 
create(const std::function<ssize_t (void *,size_t)> & reader,uint32_t sampleRate,uint32_t channelCount,audio_format_t format,const std::string & suffix)255 std::string AudioFileHandler::create(
256         const std::function<ssize_t /* frames_read */
257                     (void * /* buffer */, size_t /* size_in_frames */)>& reader,
258         uint32_t sampleRate,
259         uint32_t channelCount,
260         audio_format_t format,
261         const std::string &suffix)
262 {
263     std::string filename = generateFilename(suffix, format);
264 
265     if (mThreadPool.launch(std::string("create ") + filename,
266             [=]() { return createInternal(reader, sampleRate, channelCount, format, filename); })
267             == NO_ERROR) {
268         return filename;
269     }
270     return "";
271 }
272 
setDirectory(const std::string & directory)273 status_t AudioFileHandler::setDirectory(const std::string &directory)
274 {
275     if (!isDirectoryValid(directory)) return BAD_VALUE;
276 
277     // TODO: consider using std::filesystem in C++17
278     DIR *dir = opendir(directory.c_str());
279 
280     if (dir == nullptr) {
281         ALOGW("%s: cannot open directory %s", __func__, directory.c_str());
282         return BAD_VALUE;
283     }
284 
285     size_t toRemove = 0;
286     decltype(mFiles) files;
287 
288     while (files.size() < MAX_FILES_READ) {
289         errno = 0;
290         const struct dirent *result = readdir(dir);
291         if (result == nullptr) {
292             ALOGW_IF(errno != 0, "%s: readdir failure %s", __func__, strerror(errno));
293             break;
294         }
295         // is it a managed filename?
296         if (!isManagedFilename(result->d_name)) {
297             continue;
298         }
299         files.emplace_back(result->d_name);
300     }
301     (void)closedir(dir);
302 
303     // OPTIMIZATION: we don't need to stat each file, the filenames names are
304     // already (roughly) ordered by creation date.  we use std::deque instead
305     // of std::set for faster insertion and sorting times.
306 
307     if (files.size() > MAX_FILES_KEEP) {
308         // removed files can use a partition (no need to do a full sort).
309         toRemove = files.size() - MAX_FILES_KEEP;
310         std::nth_element(files.begin(), files.begin() + toRemove - 1, files.end());
311     }
312 
313     // kept files must be sorted.
314     std::sort(files.begin() + toRemove, files.end());
315 
316     {
317         const std::lock_guard<std::mutex> _l(mLock);
318 
319         mDirectory = directory;
320         mFiles = std::move(files);
321     }
322 
323     if (toRemove > 0) { // launch a clean in background.
324         (void)mThreadPool.launch(
325                 std::string("cleaning ") + directory, [this]() { return clean(); });
326     }
327     return NO_ERROR;
328 }
329 
clean(std::string * directory)330 status_t AudioFileHandler::clean(std::string *directory)
331 {
332     std::vector<std::string> filesToRemove;
333     std::string dir;
334     {
335         const std::lock_guard<std::mutex> _l(mLock);
336 
337         if (!isDirectoryValid(mDirectory)) return NO_INIT;
338 
339         dir = mDirectory;
340         if (mFiles.size() > MAX_FILES_KEEP) {
341             const size_t toRemove = mFiles.size() - MAX_FILES_KEEP;
342 
343             // use move and erase to efficiently transfer std::string
344             std::move(mFiles.begin(),
345                     mFiles.begin() + toRemove,
346                     std::back_inserter(filesToRemove));
347             mFiles.erase(mFiles.begin(), mFiles.begin() + toRemove);
348         }
349     }
350 
351     const std::string dirp = dir + "/";
352     // remove files outside of lock for better concurrency.
353     for (const auto &file : filesToRemove) {
354         (void)unlink((dirp + file).c_str());
355     }
356 
357     // return the directory if requested.
358     if (directory != nullptr) {
359         *directory = dir;
360     }
361     return NO_ERROR;
362 }
363 
launch(const std::string & name,const std::function<status_t ()> & func)364 status_t AudioFileHandler::ThreadPool::launch(
365         const std::string &name, const std::function<status_t()>& func)
366 {
367     if (mThreadPoolSize > 1) {
368         const std::lock_guard<std::mutex> _l(mLock);
369         if (mFutures.size() >= mThreadPoolSize) {
370             for (auto it = mFutures.begin(); it != mFutures.end();) {
371                 const std::string &filename = it->first;
372                 const std::future<status_t> &future = it->second;
373                 if (!future.valid() ||
374                         future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
375                     ALOGV("%s: future %s ready", __func__, filename.c_str());
376                     it = mFutures.erase(it);
377                 } else {
378                     ALOGV("%s: future %s not ready", __func__, filename.c_str());
379                     ++it;
380                 }
381             }
382         }
383         if (mFutures.size() < mThreadPoolSize) {
384             ALOGV("%s: deferred calling %s", __func__, name.c_str());
385             mFutures.emplace_back(name, std::async(std::launch::async, func));
386             return NO_ERROR;
387         }
388     }
389     ALOGV("%s: immediate calling %s", __func__, name.c_str());
390     return func();
391 }
392 
createInternal(const std::function<ssize_t (void *,size_t)> & reader,uint32_t sampleRate,uint32_t channelCount,audio_format_t format,const std::string & filename)393 status_t AudioFileHandler::createInternal(
394         const std::function<ssize_t /* frames_read */
395                     (void * /* buffer */, size_t /* size_in_frames */)>& reader,
396         uint32_t sampleRate,
397         uint32_t channelCount,
398         audio_format_t format,
399         const std::string &filename)
400 {
401     // Attempt to choose the best matching file format.
402     // We can choose any sf_format
403     // but writeFormat must be one of 16, 32, float
404     // due to sf_writef compatibility.
405     int sf_format;
406     audio_format_t writeFormat;
407     switch (format) {
408     case AUDIO_FORMAT_PCM_8_BIT:
409     case AUDIO_FORMAT_PCM_16_BIT:
410     case AUDIO_FORMAT_IEC61937:
411         sf_format = SF_FORMAT_PCM_16;
412         writeFormat = AUDIO_FORMAT_PCM_16_BIT;
413         ALOGV("%s: %s using PCM_16 for format %#x", __func__, filename.c_str(), format);
414         break;
415     case AUDIO_FORMAT_PCM_8_24_BIT:
416     case AUDIO_FORMAT_PCM_24_BIT_PACKED:
417     case AUDIO_FORMAT_PCM_32_BIT:
418         sf_format = SF_FORMAT_PCM_32;
419         writeFormat = AUDIO_FORMAT_PCM_32_BIT;
420         ALOGV("%s: %s using PCM_32 for format %#x", __func__, filename.c_str(), format);
421         break;
422     case AUDIO_FORMAT_PCM_FLOAT:
423         sf_format = SF_FORMAT_FLOAT;
424         writeFormat = AUDIO_FORMAT_PCM_FLOAT;
425         ALOGV("%s: %s using PCM_FLOAT for format %#x", __func__, filename.c_str(), format);
426         break;
427     default:
428         // TODO:
429         // handle compressed formats as single byte files.
430         return BAD_VALUE;
431     }
432 
433     std::string directory;
434     const status_t status = clean(&directory);
435     if (status != NO_ERROR) return status;
436     const std::string dirPrefix = directory + "/";
437 
438     const std::string path = dirPrefix + filename;
439 
440     /* const */ SF_INFO info = {
441         .frames = 0,
442         .samplerate = (int)sampleRate,
443         .channels = (int)channelCount,
444         .format = sf_format | (audio_is_linear_pcm(format) ? SF_FORMAT_WAV : 0 /* RAW */),
445     };
446     SNDFILE *sf = sf_open(path.c_str(), SFM_WRITE, &info);
447     if (sf == nullptr) {
448         return INVALID_OPERATION;
449     }
450 
451     size_t total = 0;
452     void *buffer = malloc(FRAMES_PER_READ * std::max(
453             channelCount * audio_bytes_per_sample(writeFormat), //output framesize
454             channelCount * audio_bytes_per_sample(format))); // input framesize
455     if (buffer == nullptr) {
456         sf_close(sf);
457         return NO_MEMORY;
458     }
459 
460     for (;;) {
461         const ssize_t actualRead = reader(buffer, FRAMES_PER_READ);
462         if (actualRead <= 0) {
463             break;
464         }
465 
466         // Convert input format to writeFormat as needed.
467         if (format != writeFormat && audio_is_linear_pcm(format)) {
468             memcpy_by_audio_format(
469                     buffer, writeFormat, buffer, format, actualRead * info.channels);
470         }
471 
472         ssize_t reallyWritten;
473         switch (writeFormat) {
474         case AUDIO_FORMAT_PCM_16_BIT:
475             reallyWritten = sf_writef_short(sf, (const int16_t *)buffer, actualRead);
476             break;
477         case AUDIO_FORMAT_PCM_32_BIT:
478             reallyWritten = sf_writef_int(sf, (const int32_t *)buffer, actualRead);
479             break;
480         case AUDIO_FORMAT_PCM_FLOAT:
481             reallyWritten = sf_writef_float(sf, (const float *)buffer, actualRead);
482             break;
483         default:
484             LOG_ALWAYS_FATAL("%s: %s writeFormat: %#x", __func__, filename.c_str(), writeFormat);
485             break;
486         }
487 
488         if (reallyWritten < 0) {
489             ALOGW("%s: %s write error: %zd", __func__, filename.c_str(), reallyWritten);
490             break;
491         }
492         total += reallyWritten;
493         if (reallyWritten < actualRead) {
494             ALOGW("%s: %s write short count: %zd < %zd",
495                      __func__, filename.c_str(), reallyWritten, actualRead);
496             break;
497         }
498     }
499     sf_close(sf);
500     free(buffer);
501     if (total == 0) {
502         (void)unlink(path.c_str());
503         return NOT_ENOUGH_DATA;
504     }
505 
506     // Success: add our name to managed files.
507     {
508         const std::lock_guard<std::mutex> _l(mLock);
509         // weak synchronization - only update mFiles if the directory hasn't changed.
510         if (mDirectory == directory) {
511             mFiles.emplace_back(filename);  // add to the end to preserve sort.
512         }
513     }
514     return NO_ERROR; // return full path
515 }
516 
517 /* static */
getRunningTees()518 NBAIO_Tee::RunningTees& NBAIO_Tee::getRunningTees() {
519     [[clang::no_destroy]] static RunningTees runningTees;
520     return runningTees;
521 }
522 
523 } // namespace android
524 
525 #endif // TEE_SINK
526