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