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