Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 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 | #ifndef ANDROID_AUDIO_NBAIO_H |
| 18 | #define ANDROID_AUDIO_NBAIO_H |
| 19 | |
| 20 | // Non-blocking audio I/O interface |
| 21 | // |
| 22 | // This header file has the abstract interfaces only. Concrete implementation classes are declared |
| 23 | // elsewhere. Implementations _should_ be non-blocking for all methods, especially read() and |
| 24 | // write(), but this is not enforced. In general, implementations do not need to be multi-thread |
| 25 | // safe, and any exceptions are noted in the particular implementation. |
| 26 | |
| 27 | #include <limits.h> |
| 28 | #include <stdlib.h> |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 29 | #include <utils/Errors.h> |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 30 | #include <utils/RefBase.h> |
Glenn Kasten | 767094d | 2013-08-23 13:51:43 -0700 | [diff] [blame] | 31 | #include <media/AudioTimestamp.h> |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 32 | |
| 33 | namespace android { |
| 34 | |
| 35 | // In addition to the usual status_t |
| 36 | enum { |
| 37 | NEGOTIATE = 0x80000010, // Must (re-)negotiate format. For negotiate() only, the offeree |
| 38 | // doesn't accept offers, and proposes counter-offers |
| 39 | OVERRUN = 0x80000011, // availableToRead(), read(), or readVia() detected lost input due |
| 40 | // to overrun; an event is counted and the caller should re-try |
| 41 | UNDERRUN = 0x80000012, // availableToWrite(), write(), or writeVia() detected a gap in |
| 42 | // output due to underrun (not being called often enough, or with |
| 43 | // enough data); an event is counted and the caller should re-try |
| 44 | }; |
| 45 | |
| 46 | // Negotiation of format is based on the data provider and data sink, or the data consumer and |
| 47 | // data source, exchanging prioritized arrays of offers and counter-offers until a single offer is |
| 48 | // mutually agreed upon. Each offer is an NBAIO_Format. For simplicity and performance, |
Glenn Kasten | b64497e | 2012-10-01 09:47:30 -0700 | [diff] [blame] | 49 | // NBAIO_Format is a typedef that ties together the most important combinations of the various |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 50 | // attributes, rather than a struct with separate fields for format, sample rate, channel count, |
| 51 | // interleave, packing, alignment, etc. The reason is that NBAIO_Format tries to abstract out only |
Glenn Kasten | b64497e | 2012-10-01 09:47:30 -0700 | [diff] [blame] | 52 | // the combinations that are actually needed within AudioFlinger. If the list of combinations grows |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 53 | // too large, then this decision should be re-visited. |
Glenn Kasten | b64497e | 2012-10-01 09:47:30 -0700 | [diff] [blame] | 54 | // Sample rate and channel count are explicit, PCM interleaved 16-bit is assumed. |
Glenn Kasten | c4b8b32 | 2014-01-31 09:39:01 -0800 | [diff] [blame] | 55 | struct NBAIO_Format { |
| 56 | //private: |
| 57 | unsigned mPacked; |
| 58 | }; |
Glenn Kasten | 51d53cd | 2014-01-31 09:38:33 -0800 | [diff] [blame] | 59 | |
| 60 | extern const NBAIO_Format Format_Invalid; |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 61 | |
| 62 | // Return the frame size of an NBAIO_Format in bytes |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 63 | size_t Format_frameSize(const NBAIO_Format& format); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 64 | |
| 65 | // Return the frame size of an NBAIO_Format as a bit shift |
Glenn Kasten | 4d7b3f8 | 2014-01-31 10:38:16 -0800 | [diff] [blame] | 66 | // or -1 if frame size is not a power of 2 |
| 67 | int Format_frameBitShift(const NBAIO_Format& format); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 68 | |
| 69 | // Convert a sample rate in Hz and channel count to an NBAIO_Format |
Glenn Kasten | 1ec712f | 2014-01-31 09:47:15 -0800 | [diff] [blame] | 70 | // FIXME The sample format is hard-coded to AUDIO_FORMAT_PCM_16_BIT |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 71 | NBAIO_Format Format_from_SR_C(unsigned sampleRate, unsigned channelCount); |
| 72 | |
| 73 | // Return the sample rate in Hz of an NBAIO_Format |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 74 | unsigned Format_sampleRate(const NBAIO_Format& format); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 75 | |
| 76 | // Return the channel count of an NBAIO_Format |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 77 | unsigned Format_channelCount(const NBAIO_Format& format); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 78 | |
| 79 | // Callbacks used by NBAIO_Sink::writeVia() and NBAIO_Source::readVia() below. |
| 80 | typedef ssize_t (*writeVia_t)(void *user, void *buffer, size_t count); |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 81 | typedef ssize_t (*readVia_t)(void *user, const void *buffer, |
| 82 | size_t count, int64_t readPTS); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 83 | |
Glenn Kasten | cc1e0e8 | 2014-01-31 09:48:42 -0800 | [diff] [blame] | 84 | // Check whether an NBAIO_Format is valid |
| 85 | bool Format_isValid(const NBAIO_Format& format); |
| 86 | |
| 87 | // Compare two NBAIO_Format values |
| 88 | bool Format_isEqual(const NBAIO_Format& format1, const NBAIO_Format& format2); |
| 89 | |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 90 | // Abstract class (interface) representing a data port. |
| 91 | class NBAIO_Port : public RefBase { |
| 92 | |
| 93 | public: |
| 94 | |
| 95 | // negotiate() must called first. The purpose of negotiate() is to check compatibility of |
| 96 | // formats, not to automatically adapt if they are incompatible. It's the responsibility of |
| 97 | // whoever sets up the graph connections to make sure formats are compatible, and this method |
| 98 | // just verifies that. The edges are "dumb" and don't attempt to adapt to bad connections. |
| 99 | // How it works: offerer proposes an array of formats, in descending order of preference from |
| 100 | // offers[0] to offers[numOffers - 1]. If offeree accepts one of these formats, it returns |
| 101 | // the index of that offer. Otherwise, offeree sets numCounterOffers to the number of |
| 102 | // counter-offers (up to a maximumum of the entry value of numCounterOffers), fills in the |
| 103 | // provided array counterOffers[] with its counter-offers, in descending order of preference |
| 104 | // from counterOffers[0] to counterOffers[numCounterOffers - 1], and returns NEGOTIATE. |
| 105 | // Note that since the offerer allocates space for counter-offers, but only the offeree knows |
| 106 | // how many counter-offers it has, there may be insufficient space for all counter-offers. |
| 107 | // In that case, the offeree sets numCounterOffers to the requested number of counter-offers |
| 108 | // (which is greater than the entry value of numCounterOffers), fills in as many of the most |
| 109 | // important counterOffers as will fit, and returns NEGOTIATE. As this implies a re-allocation, |
| 110 | // it should be used as a last resort. It is preferable for the offerer to simply allocate a |
| 111 | // larger space to begin with, and/or for the offeree to tolerate a smaller space than desired. |
| 112 | // Alternatively, the offerer can pass NULL for offers and counterOffers, and zero for |
| 113 | // numOffers. This indicates that it has not allocated space for any counter-offers yet. |
| 114 | // In this case, the offerree should set numCounterOffers appropriately and return NEGOTIATE. |
| 115 | // Then the offerer will allocate the correct amount of memory and retry. |
| 116 | // Format_Invalid is not allowed as either an offer or counter-offer. |
| 117 | // Returns: |
| 118 | // >= 0 Offer accepted. |
| 119 | // NEGOTIATE No offer accepted, and counter-offer(s) optionally made. See above for details. |
| 120 | virtual ssize_t negotiate(const NBAIO_Format offers[], size_t numOffers, |
| 121 | NBAIO_Format counterOffers[], size_t& numCounterOffers); |
| 122 | |
| 123 | // Return the current negotiated format, or Format_Invalid if negotiation has not been done, |
| 124 | // or if re-negotiation is required. |
| 125 | virtual NBAIO_Format format() const { return mNegotiated ? mFormat : Format_Invalid; } |
| 126 | |
| 127 | protected: |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 128 | NBAIO_Port(const NBAIO_Format& format) : mNegotiated(false), mFormat(format), |
Glenn Kasten | ac3e9db | 2014-03-06 08:00:31 -0800 | [diff] [blame^] | 129 | mBitShift(Format_frameBitShift(format)), |
| 130 | mFrameSize(Format_frameSize(format)) { } |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 131 | virtual ~NBAIO_Port() { } |
| 132 | |
| 133 | // Implementations are free to ignore these if they don't need them |
| 134 | |
| 135 | bool mNegotiated; // mNegotiated implies (mFormat != Format_Invalid) |
| 136 | NBAIO_Format mFormat; // (mFormat != Format_Invalid) does not imply mNegotiated |
| 137 | size_t mBitShift; // assign in parallel with any assignment to mFormat |
Glenn Kasten | ac3e9db | 2014-03-06 08:00:31 -0800 | [diff] [blame^] | 138 | size_t mFrameSize; // assign in parallel with any assignment to mFormat |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 139 | }; |
| 140 | |
| 141 | // Abstract class (interface) representing a non-blocking data sink, for use by a data provider. |
| 142 | class NBAIO_Sink : public NBAIO_Port { |
| 143 | |
| 144 | public: |
| 145 | |
| 146 | // For the next two APIs: |
| 147 | // 32 bits rolls over after 27 hours at 44.1 kHz; if that concerns you then poll periodically. |
| 148 | |
| 149 | // Return the number of frames written successfully since construction. |
| 150 | virtual size_t framesWritten() const { return mFramesWritten; } |
| 151 | |
| 152 | // Number of frames lost due to underrun since construction. |
| 153 | virtual size_t framesUnderrun() const { return 0; } |
| 154 | |
| 155 | // Number of underruns since construction, where a set of contiguous lost frames is one event. |
| 156 | virtual size_t underruns() const { return 0; } |
| 157 | |
| 158 | // Estimate of number of frames that could be written successfully now without blocking. |
| 159 | // When a write() is actually attempted, the implementation is permitted to return a smaller or |
| 160 | // larger transfer count, however it will make a good faith effort to give an accurate estimate. |
| 161 | // Errors: |
| 162 | // NEGOTIATE (Re-)negotiation is needed. |
| 163 | // UNDERRUN write() has not been called frequently enough, or with enough frames to keep up. |
| 164 | // An underrun event is counted, and the caller should re-try this operation. |
| 165 | // WOULD_BLOCK Determining how many frames can be written without blocking would itself block. |
| 166 | virtual ssize_t availableToWrite() const { return SSIZE_MAX; } |
| 167 | |
| 168 | // Transfer data to sink from single input buffer. Implies a copy. |
| 169 | // Inputs: |
| 170 | // buffer Non-NULL buffer owned by provider. |
| 171 | // count Maximum number of frames to transfer. |
| 172 | // Return value: |
| 173 | // > 0 Number of frames successfully transferred prior to first error. |
| 174 | // = 0 Count was zero. |
| 175 | // < 0 status_t error occurred prior to the first frame transfer. |
| 176 | // Errors: |
| 177 | // NEGOTIATE (Re-)negotiation is needed. |
| 178 | // WOULD_BLOCK No frames can be transferred without blocking. |
| 179 | // UNDERRUN write() has not been called frequently enough, or with enough frames to keep up. |
| 180 | // An underrun event is counted, and the caller should re-try this operation. |
| 181 | virtual ssize_t write(const void *buffer, size_t count) = 0; |
| 182 | |
| 183 | // Transfer data to sink using a series of callbacks. More suitable for zero-fill, synthesis, |
| 184 | // and non-contiguous transfers (e.g. circular buffer or writev). |
| 185 | // Inputs: |
| 186 | // via Callback function that the sink will call as many times as needed to consume data. |
| 187 | // total Estimate of the number of frames the provider has available. This is an estimate, |
| 188 | // and it can provide a different number of frames during the series of callbacks. |
| 189 | // user Arbitrary void * reserved for data provider. |
| 190 | // block Number of frames per block, that is a suggested value for 'count' in each callback. |
| 191 | // Zero means no preference. This parameter is a hint only, and may be ignored. |
| 192 | // Return value: |
| 193 | // > 0 Total number of frames successfully transferred prior to first error. |
| 194 | // = 0 Count was zero. |
| 195 | // < 0 status_t error occurred prior to the first frame transfer. |
| 196 | // Errors: |
| 197 | // NEGOTIATE (Re-)negotiation is needed. |
| 198 | // WOULD_BLOCK No frames can be transferred without blocking. |
| 199 | // UNDERRUN write() has not been called frequently enough, or with enough frames to keep up. |
| 200 | // An underrun event is counted, and the caller should re-try this operation. |
| 201 | // |
| 202 | // The 'via' callback is called by the data sink as follows: |
| 203 | // Inputs: |
| 204 | // user Arbitrary void * reserved for data provider. |
| 205 | // buffer Non-NULL buffer owned by sink that callback should fill in with data, |
| 206 | // up to a maximum of 'count' frames. |
| 207 | // count Maximum number of frames to transfer during this callback. |
| 208 | // Return value: |
| 209 | // > 0 Number of frames successfully transferred during this callback prior to first error. |
| 210 | // = 0 Count was zero. |
| 211 | // < 0 status_t error occurred prior to the first frame transfer during this callback. |
| 212 | virtual ssize_t writeVia(writeVia_t via, size_t total, void *user, size_t block = 0); |
| 213 | |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 214 | // Get the time (on the LocalTime timeline) at which the first frame of audio of the next write |
| 215 | // operation to this sink will be eventually rendered by the HAL. |
| 216 | // Inputs: |
| 217 | // ts A pointer pointing to the int64_t which will hold the result. |
| 218 | // Return value: |
| 219 | // OK Everything went well, *ts holds the time at which the first audio frame of the next |
| 220 | // write operation will be rendered, or AudioBufferProvider::kInvalidPTS if this sink |
| 221 | // does not know the answer for some reason. Sinks which eventually lead to a HAL |
| 222 | // which implements get_next_write_timestamp may return Invalid temporarily if the DMA |
| 223 | // output of the audio driver has not started yet. Sinks which lead to a HAL which |
| 224 | // does not implement get_next_write_timestamp, or which don't lead to a HAL at all, |
| 225 | // will always return kInvalidPTS. |
| 226 | // <other> Something unexpected happened internally. Check the logs and start debugging. |
| 227 | virtual status_t getNextWriteTimestamp(int64_t *ts) { return INVALID_OPERATION; } |
| 228 | |
Glenn Kasten | 767094d | 2013-08-23 13:51:43 -0700 | [diff] [blame] | 229 | // Returns NO_ERROR if a timestamp is available. The timestamp includes the total number |
| 230 | // of frames presented to an external observer, together with the value of CLOCK_MONOTONIC |
| 231 | // as of this presentation count. |
| 232 | virtual status_t getTimestamp(AudioTimestamp& timestamp) { return INVALID_OPERATION; } |
| 233 | |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 234 | protected: |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 235 | NBAIO_Sink(const NBAIO_Format& format = Format_Invalid) : NBAIO_Port(format), mFramesWritten(0) { } |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 236 | virtual ~NBAIO_Sink() { } |
| 237 | |
| 238 | // Implementations are free to ignore these if they don't need them |
| 239 | size_t mFramesWritten; |
| 240 | }; |
| 241 | |
| 242 | // Abstract class (interface) representing a non-blocking data source, for use by a data consumer. |
| 243 | class NBAIO_Source : public NBAIO_Port { |
| 244 | |
| 245 | public: |
| 246 | |
| 247 | // For the next two APIs: |
| 248 | // 32 bits rolls over after 27 hours at 44.1 kHz; if that concerns you then poll periodically. |
| 249 | |
| 250 | // Number of frames read successfully since construction. |
| 251 | virtual size_t framesRead() const { return mFramesRead; } |
| 252 | |
| 253 | // Number of frames lost due to overrun since construction. |
| 254 | // Not const because implementations may need to do I/O. |
| 255 | virtual size_t framesOverrun() /*const*/ { return 0; } |
| 256 | |
| 257 | // Number of overruns since construction, where a set of contiguous lost frames is one event. |
| 258 | // Not const because implementations may need to do I/O. |
| 259 | virtual size_t overruns() /*const*/ { return 0; } |
| 260 | |
| 261 | // Estimate of number of frames that could be read successfully now. |
| 262 | // When a read() is actually attempted, the implementation is permitted to return a smaller or |
| 263 | // larger transfer count, however it will make a good faith effort to give an accurate estimate. |
| 264 | // Errors: |
| 265 | // NEGOTIATE (Re-)negotiation is needed. |
| 266 | // OVERRUN One or more frames were lost due to overrun, try again to read more recent data. |
| 267 | // WOULD_BLOCK Determining how many frames can be read without blocking would itself block. |
| 268 | virtual ssize_t availableToRead() { return SSIZE_MAX; } |
| 269 | |
| 270 | // Transfer data from source into single destination buffer. Implies a copy. |
| 271 | // Inputs: |
| 272 | // buffer Non-NULL destination buffer owned by consumer. |
| 273 | // count Maximum number of frames to transfer. |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 274 | // readPTS The presentation time (on the LocalTime timeline) for which data |
| 275 | // is being requested, or kInvalidPTS if not known. |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 276 | // Return value: |
| 277 | // > 0 Number of frames successfully transferred prior to first error. |
| 278 | // = 0 Count was zero. |
| 279 | // < 0 status_t error occurred prior to the first frame transfer. |
| 280 | // Errors: |
| 281 | // NEGOTIATE (Re-)negotiation is needed. |
| 282 | // WOULD_BLOCK No frames can be transferred without blocking. |
| 283 | // OVERRUN read() has not been called frequently enough, or with enough frames to keep up. |
| 284 | // One or more frames were lost due to overrun, try again to read more recent data. |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 285 | virtual ssize_t read(void *buffer, size_t count, int64_t readPTS) = 0; |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 286 | |
| 287 | // Transfer data from source using a series of callbacks. More suitable for zero-fill, |
| 288 | // synthesis, and non-contiguous transfers (e.g. circular buffer or readv). |
| 289 | // Inputs: |
| 290 | // via Callback function that the source will call as many times as needed to provide data. |
| 291 | // total Estimate of the number of frames the consumer desires. This is an estimate, |
| 292 | // and it can consume a different number of frames during the series of callbacks. |
| 293 | // user Arbitrary void * reserved for data consumer. |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 294 | // readPTS The presentation time (on the LocalTime timeline) for which data |
| 295 | // is being requested, or kInvalidPTS if not known. |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 296 | // block Number of frames per block, that is a suggested value for 'count' in each callback. |
| 297 | // Zero means no preference. This parameter is a hint only, and may be ignored. |
| 298 | // Return value: |
| 299 | // > 0 Total number of frames successfully transferred prior to first error. |
| 300 | // = 0 Count was zero. |
| 301 | // < 0 status_t error occurred prior to the first frame transfer. |
| 302 | // Errors: |
| 303 | // NEGOTIATE (Re-)negotiation is needed. |
| 304 | // WOULD_BLOCK No frames can be transferred without blocking. |
| 305 | // OVERRUN read() has not been called frequently enough, or with enough frames to keep up. |
| 306 | // One or more frames were lost due to overrun, try again to read more recent data. |
| 307 | // |
| 308 | // The 'via' callback is called by the data source as follows: |
| 309 | // Inputs: |
| 310 | // user Arbitrary void * reserved for data consumer. |
| 311 | // dest Non-NULL buffer owned by source that callback should consume data from, |
| 312 | // up to a maximum of 'count' frames. |
| 313 | // count Maximum number of frames to transfer during this callback. |
| 314 | // Return value: |
| 315 | // > 0 Number of frames successfully transferred during this callback prior to first error. |
| 316 | // = 0 Count was zero. |
| 317 | // < 0 status_t error occurred prior to the first frame transfer during this callback. |
John Grossman | 2c3b2da | 2012-08-02 17:08:54 -0700 | [diff] [blame] | 318 | virtual ssize_t readVia(readVia_t via, size_t total, void *user, |
| 319 | int64_t readPTS, size_t block = 0); |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 320 | |
Glenn Kasten | 894d6be | 2013-08-26 10:29:28 -0700 | [diff] [blame] | 321 | // Invoked asynchronously by corresponding sink when a new timestamp is available. |
| 322 | // Default implementation ignores the timestamp. |
| 323 | virtual void onTimestamp(const AudioTimestamp& timestamp) { } |
| 324 | |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 325 | protected: |
Glenn Kasten | 72e54af | 2014-01-31 09:37:35 -0800 | [diff] [blame] | 326 | NBAIO_Source(const NBAIO_Format& format = Format_Invalid) : NBAIO_Port(format), mFramesRead(0) { } |
Glenn Kasten | 0106623 | 2012-02-27 11:50:44 -0800 | [diff] [blame] | 327 | virtual ~NBAIO_Source() { } |
| 328 | |
| 329 | // Implementations are free to ignore these if they don't need them |
| 330 | size_t mFramesRead; |
| 331 | }; |
| 332 | |
| 333 | } // namespace android |
| 334 | |
| 335 | #endif // ANDROID_AUDIO_NBAIO_H |