• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 "AHAL_SubmixRoute"
18 #include <android-base/logging.h>
19 #include <media/AidlConversionCppNdk.h>
20 
21 #include <Utils.h>
22 
23 #include "SubmixRoute.h"
24 
25 using aidl::android::hardware::audio::common::getChannelCount;
26 
27 namespace aidl::android::hardware::audio::core::r_submix {
28 
29 // Verify a submix input or output stream can be opened.
isStreamConfigValid(bool isInput,const AudioConfig & streamConfig)30 bool SubmixRoute::isStreamConfigValid(bool isInput, const AudioConfig& streamConfig) {
31     // If the stream is already open, don't open it again.
32     // ENABLE_LEGACY_INPUT_OPEN is default behaviour
33     if (!isInput && isStreamOutOpen()) {
34         LOG(ERROR) << __func__ << ": output stream already open.";
35         return false;
36     }
37     // If either stream is open, verify the existing pipe config matches the stream config.
38     if (hasAtleastOneStreamOpen() && !isStreamConfigCompatible(streamConfig)) {
39         return false;
40     }
41     return true;
42 }
43 
44 // Compare this stream config with existing pipe config, returning false if they do *not*
45 // match, true otherwise.
isStreamConfigCompatible(const AudioConfig & streamConfig)46 bool SubmixRoute::isStreamConfigCompatible(const AudioConfig& streamConfig) {
47     if (streamConfig.channelLayout != mPipeConfig.channelLayout) {
48         LOG(ERROR) << __func__ << ": channel count mismatch, stream channels = "
49                    << streamConfig.channelLayout.toString()
50                    << " pipe config channels = " << mPipeConfig.channelLayout.toString();
51         return false;
52     }
53     if (streamConfig.sampleRate != mPipeConfig.sampleRate) {
54         LOG(ERROR) << __func__
55                    << ": sample rate mismatch, stream sample rate = " << streamConfig.sampleRate
56                    << " pipe config sample rate = " << mPipeConfig.sampleRate;
57         return false;
58     }
59     if (streamConfig.format != mPipeConfig.format) {
60         LOG(ERROR) << __func__
61                    << ": format mismatch, stream format = " << streamConfig.format.toString()
62                    << " pipe config format = " << mPipeConfig.format.toString();
63         return false;
64     }
65     return true;
66 }
67 
hasAtleastOneStreamOpen()68 bool SubmixRoute::hasAtleastOneStreamOpen() {
69     std::lock_guard guard(mLock);
70     return (mStreamInOpen || mStreamOutOpen);
71 }
72 
73 // We DO NOT block if:
74 // - no peer input stream is present
75 // - the peer input is in standby AFTER having been active.
76 // We DO block if:
77 // - the input was never activated to avoid discarding first frames in the pipe in case capture
78 // start was delayed
shouldBlockWrite()79 bool SubmixRoute::shouldBlockWrite() {
80     std::lock_guard guard(mLock);
81     return (mStreamInOpen || (mStreamInStandby && (mReadCounterFrames != 0)));
82 }
83 
notifyReadError()84 int SubmixRoute::notifyReadError() {
85     std::lock_guard guard(mLock);
86     return ++mReadErrorCount;
87 }
88 
updateReadCounterFrames(size_t frameCount)89 long SubmixRoute::updateReadCounterFrames(size_t frameCount) {
90     std::lock_guard guard(mLock);
91     mReadCounterFrames += frameCount;
92     return mReadCounterFrames;
93 }
94 
openStream(bool isInput)95 void SubmixRoute::openStream(bool isInput) {
96     std::lock_guard guard(mLock);
97     if (isInput) {
98         if (mStreamInOpen) {
99             mInputRefCount++;
100         } else {
101             mInputRefCount = 1;
102             mStreamInOpen = true;
103         }
104         mStreamInStandby = true;
105         mReadCounterFrames = 0;
106         mReadErrorCount = 0;
107     } else {
108         mStreamOutOpen = true;
109     }
110 }
111 
closeStream(bool isInput)112 void SubmixRoute::closeStream(bool isInput) {
113     std::lock_guard guard(mLock);
114     if (isInput) {
115         mInputRefCount--;
116         if (mInputRefCount == 0) {
117             mStreamInOpen = false;
118             if (mSink != nullptr) {
119                 mSink->shutdown(true);
120             }
121         }
122     } else {
123         mStreamOutOpen = false;
124     }
125 }
126 
127 // If SubmixRoute doesn't exist for a port, create a pipe for the submix audio device of size
128 // buffer_size_frames and store config of the submix audio device.
createPipe(const AudioConfig & streamConfig)129 ::android::status_t SubmixRoute::createPipe(const AudioConfig& streamConfig) {
130     const int channelCount = getChannelCount(streamConfig.channelLayout);
131     const audio_format_t audioFormat = VALUE_OR_RETURN_STATUS(
132             aidl2legacy_AudioFormatDescription_audio_format_t(streamConfig.format));
133     const ::android::NBAIO_Format format =
134             ::android::Format_from_SR_C(streamConfig.sampleRate, channelCount, audioFormat);
135     const ::android::NBAIO_Format offers[1] = {format};
136     size_t numCounterOffers = 0;
137 
138     const size_t pipeSizeInFrames =
139             r_submix::kDefaultPipeSizeInFrames *
140             ((float)streamConfig.sampleRate / r_submix::kDefaultSampleRateHz);
141     LOG(VERBOSE) << __func__ << ": creating pipe, rate : " << streamConfig.sampleRate
142                  << ", pipe size : " << pipeSizeInFrames;
143 
144     // Create a MonoPipe with optional blocking set to true.
145     sp<MonoPipe> sink = sp<MonoPipe>::make(pipeSizeInFrames, format, true /*writeCanBlock*/);
146     if (sink == nullptr) {
147         LOG(FATAL) << __func__ << ": sink is null";
148         return ::android::UNEXPECTED_NULL;
149     }
150 
151     // Negotiation between the source and sink cannot fail as the device open operation
152     // creates both ends of the pipe using the same audio format.
153     ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers);
154     if (index != 0) {
155         LOG(FATAL) << __func__ << ": Negotiation for the sink failed, index = " << index;
156         return ::android::BAD_INDEX;
157     }
158     sp<MonoPipeReader> source = sp<MonoPipeReader>::make(sink.get());
159     if (source == nullptr) {
160         LOG(FATAL) << __func__ << ": source is null";
161         return ::android::UNEXPECTED_NULL;
162     }
163     numCounterOffers = 0;
164     index = source->negotiate(offers, 1, nullptr, numCounterOffers);
165     if (index != 0) {
166         LOG(FATAL) << __func__ << ": Negotiation for the source failed, index = " << index;
167         return ::android::BAD_INDEX;
168     }
169     LOG(VERBOSE) << __func__ << ": created pipe";
170 
171     mPipeConfig = streamConfig;
172     mPipeConfig.frameCount = sink->maxFrames();
173 
174     LOG(VERBOSE) << __func__ << ": Pipe frame size : " << mPipeConfig.frameSize
175                  << ", pipe frames : " << mPipeConfig.frameCount;
176 
177     // Save references to the source and sink.
178     {
179         std::lock_guard guard(mLock);
180         mSink = std::move(sink);
181         mSource = std::move(source);
182     }
183 
184     return ::android::OK;
185 }
186 
187 // Release references to the sink and source.
releasePipe()188 void SubmixRoute::releasePipe() {
189     std::lock_guard guard(mLock);
190     mSink.clear();
191     mSource.clear();
192 }
193 
resetPipe()194 ::android::status_t SubmixRoute::resetPipe() {
195     releasePipe();
196     return createPipe(mPipeConfig);
197 }
198 
standby(bool isInput)199 void SubmixRoute::standby(bool isInput) {
200     std::lock_guard guard(mLock);
201 
202     if (isInput) {
203         mStreamInStandby = true;
204     } else if (!mStreamOutStandby) {
205         mStreamOutStandby = true;
206         mStreamOutStandbyTransition = true;
207     }
208 }
209 
exitStandby(bool isInput)210 void SubmixRoute::exitStandby(bool isInput) {
211     std::lock_guard guard(mLock);
212 
213     if (isInput) {
214         if (mStreamInStandby || mStreamOutStandbyTransition) {
215             mStreamInStandby = false;
216             mStreamOutStandbyTransition = false;
217             // keep track of when we exit input standby (== first read == start "real recording")
218             // or when we start recording silence, and reset projected time
219             mRecordStartTime = std::chrono::steady_clock::now();
220             mReadCounterFrames = 0;
221         }
222     } else {
223         if (mStreamOutStandby) {
224             mStreamOutStandby = false;
225             mStreamOutStandbyTransition = true;
226         }
227     }
228 }
229 
230 }  // namespace aidl::android::hardware::audio::core::r_submix
231