summaryrefslogtreecommitdiffstats
path: root/media/libnbaio/MonoPipe.cpp
blob: 129e9effaf720f2aab538d722ac5ffe2cbbece1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <inttypes.h>

#define LOG_TAG "MonoPipe"
//#define LOG_NDEBUG 0

#include <common_time/cc_helper.h>
#include <cutils/atomic.h>
#include <cutils/compiler.h>
#include <utils/LinearTransform.h>
#include <utils/Log.h>
#include <utils/Trace.h>
#include <media/AudioBufferProvider.h>
#include <media/nbaio/MonoPipe.h>
#include <audio_utils/roundup.h>


namespace android {

static uint64_t cacheN; // output of CCHelper::getLocalFreq()
static bool cacheValid; // whether cacheN is valid
static pthread_once_t cacheOnceControl = PTHREAD_ONCE_INIT;

static void cacheOnceInit()
{
    CCHelper tmpHelper;
    status_t res;
    if (OK != (res = tmpHelper.getLocalFreq(&cacheN))) {
        ALOGE("Failed to fetch local time frequency when constructing a"
              " MonoPipe (res = %d).  getNextWriteTimestamp calls will be"
              " non-functional", res);
        return;
    }
    cacheValid = true;
}

MonoPipe::MonoPipe(size_t reqFrames, const NBAIO_Format& format, bool writeCanBlock) :
        NBAIO_Sink(format),
        mUpdateSeq(0),
        mReqFrames(reqFrames),
        mMaxFrames(roundup(reqFrames)),
        mBuffer(malloc(mMaxFrames * Format_frameSize(format))),
        mFront(0),
        mRear(0),
        mWriteTsValid(false),
        // mWriteTs
        mSetpoint((reqFrames * 11) / 16),
        mWriteCanBlock(writeCanBlock),
        mIsShutdown(false),
        // mTimestampShared
        mTimestampMutator(&mTimestampShared),
        mTimestampObserver(&mTimestampShared)
{
    uint64_t N, D;

    mNextRdPTS = AudioBufferProvider::kInvalidPTS;

    mSamplesToLocalTime.a_zero = 0;
    mSamplesToLocalTime.b_zero = 0;
    mSamplesToLocalTime.a_to_b_numer = 0;
    mSamplesToLocalTime.a_to_b_denom = 0;

    D = Format_sampleRate(format);

    (void) pthread_once(&cacheOnceControl, cacheOnceInit);
    if (!cacheValid) {
        // log has already been done
        return;
    }
    N = cacheN;

    LinearTransform::reduce(&N, &D);
    static const uint64_t kSignedHiBitsMask   = ~(0x7FFFFFFFull);
    static const uint64_t kUnsignedHiBitsMask = ~(0xFFFFFFFFull);
    if ((N & kSignedHiBitsMask) || (D & kUnsignedHiBitsMask)) {
        ALOGE("Cannot reduce sample rate to local clock frequency ratio to fit"
              " in a 32/32 bit rational.  (max reduction is 0x%016" PRIx64 "/0x%016" PRIx64
              ").  getNextWriteTimestamp calls will be non-functional", N, D);
        return;
    }

    mSamplesToLocalTime.a_to_b_numer = static_cast<int32_t>(N);
    mSamplesToLocalTime.a_to_b_denom = static_cast<uint32_t>(D);
}

MonoPipe::~MonoPipe()
{
    free(mBuffer);
}

ssize_t MonoPipe::availableToWrite() const
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    // uses mMaxFrames not mReqFrames, so allows "over-filling" the pipe beyond requested limit
    ssize_t ret = mMaxFrames - (mRear - android_atomic_acquire_load(&mFront));
    ALOG_ASSERT((0 <= ret) && (ret <= mMaxFrames));
    return ret;
}

ssize_t MonoPipe::write(const void *buffer, size_t count)
{
    if (CC_UNLIKELY(!mNegotiated)) {
        return NEGOTIATE;
    }
    size_t totalFramesWritten = 0;
    while (count > 0) {
        // can't return a negative value, as we already checked for !mNegotiated
        size_t avail = availableToWrite();
        size_t written = avail;
        if (CC_LIKELY(written > count)) {
            written = count;
        }
        size_t rear = mRear & (mMaxFrames - 1);
        size_t part1 = mMaxFrames - rear;
        if (part1 > written) {
            part1 = written;
        }
        if (CC_LIKELY(part1 > 0)) {
            memcpy((char *) mBuffer + (rear * mFrameSize), buffer, part1 * mFrameSize);
            if (CC_UNLIKELY(rear + part1 == mMaxFrames)) {
                size_t part2 = written - part1;
                if (CC_LIKELY(part2 > 0)) {
                    memcpy(mBuffer, (char *) buffer + (part1 * mFrameSize), part2 * mFrameSize);
                }
            }
            android_atomic_release_store(written + mRear, &mRear);
            totalFramesWritten += written;
        }
        if (!mWriteCanBlock || mIsShutdown) {
            break;
        }
        count -= written;
        buffer = (char *) buffer + (written * mFrameSize);
        // Simulate blocking I/O by sleeping at different rates, depending on a throttle.
        // The throttle tries to keep the mean pipe depth near the setpoint, with a slight jitter.
        uint32_t ns;
        if (written > 0) {
            size_t filled = (mMaxFrames - avail) + written;
            // FIXME cache these values to avoid re-computation
            if (filled <= mSetpoint / 2) {
                // pipe is (nearly) empty, fill quickly
                ns = written * ( 500000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 3) / 4) {
                // pipe is below setpoint, fill at slightly faster rate
                ns = written * ( 750000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 5) / 4) {
                // pipe is at setpoint, fill at nominal rate
                ns = written * (1000000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 3) / 2) {
                // pipe is above setpoint, fill at slightly slower rate
                ns = written * (1150000000 / Format_sampleRate(mFormat));
            } else if (filled <= (mSetpoint * 7) / 4) {
                // pipe is overflowing, fill slowly
                ns = written * (1350000000 / Format_sampleRate(mFormat));
            } else {
                // pipe is severely overflowing
                ns = written * (1750000000 / Format_sampleRate(mFormat));
            }
        } else {
            ns = count * (1350000000 / Format_sampleRate(mFormat));
        }
        if (ns > 999999999) {
            ns = 999999999;
        }
        struct timespec nowTs;
        bool nowTsValid = !clock_gettime(CLOCK_MONOTONIC, &nowTs);
        // deduct the elapsed time since previous write() completed
        if (nowTsValid && mWriteTsValid) {
            time_t sec = nowTs.tv_sec - mWriteTs.tv_sec;
            long nsec = nowTs.tv_nsec - mWriteTs.tv_nsec;
            ALOGE_IF(sec < 0 || (sec == 0 && nsec < 0),
                    "clock_gettime(CLOCK_MONOTONIC) failed: was %ld.%09ld but now %ld.%09ld",
                    mWriteTs.tv_sec, mWriteTs.tv_nsec, nowTs.tv_sec, nowTs.tv_nsec);
            if (nsec < 0) {
                --sec;
                nsec += 1000000000;
            }
            if (sec == 0) {
                if ((long) ns > nsec) {
                    ns -= nsec;
                } else {
                    ns = 0;
                }
            }
        }
        if (ns > 0) {
            const struct timespec req = {0, static_cast<long>(ns)};
            nanosleep(&req, NULL);
        }
        // record the time that this write() completed
        if (nowTsValid) {
            mWriteTs = nowTs;
            if ((mWriteTs.tv_nsec += ns) >= 1000000000) {
                mWriteTs.tv_nsec -= 1000000000;
                ++mWriteTs.tv_sec;
            }
        }
        mWriteTsValid = nowTsValid;
    }
    mFramesWritten += totalFramesWritten;
    return totalFramesWritten;
}

void MonoPipe::setAvgFrames(size_t setpoint)
{
    mSetpoint = setpoint;
}

status_t MonoPipe::getNextWriteTimestamp(int64_t *timestamp)
{
    int32_t front;

    ALOG_ASSERT(NULL != timestamp);

    if (0 == mSamplesToLocalTime.a_to_b_denom)
        return UNKNOWN_ERROR;

    observeFrontAndNRPTS(&front, timestamp);

    if (AudioBufferProvider::kInvalidPTS != *timestamp) {
        // If we have a valid read-pointer and next read timestamp pair, then
        // use the current value of the write pointer to figure out how many
        // frames are in the buffer, and offset the timestamp by that amt.  Then
        // next time we write to the MonoPipe, the data will hit the speakers at
        // the next read timestamp plus the current amount of data in the
        // MonoPipe.
        size_t pendingFrames = (mRear - front) & (mMaxFrames - 1);
        *timestamp = offsetTimestampByAudioFrames(*timestamp, pendingFrames);
    }

    return OK;
}

void MonoPipe::updateFrontAndNRPTS(int32_t newFront, int64_t newNextRdPTS)
{
    // Set the MSB of the update sequence number to indicate that there is a
    // multi-variable update in progress.  Use an atomic store with an "acquire"
    // barrier to make sure that the next operations cannot be re-ordered and
    // take place before the change to mUpdateSeq is commited..
    int32_t tmp = mUpdateSeq | 0x80000000;
    android_atomic_acquire_store(tmp, &mUpdateSeq);

    // Update mFront and mNextRdPTS
    mFront = newFront;
    mNextRdPTS = newNextRdPTS;

    // We are finished with the update.  Compute the next sequnce number (which
    // should be the old sequence number, plus one, and with the MSB cleared)
    // and then store it in mUpdateSeq using an atomic store with a "release"
    // barrier so our update operations cannot be re-ordered past the update of
    // the sequence number.
    tmp = (tmp + 1) & 0x7FFFFFFF;
    android_atomic_release_store(tmp, &mUpdateSeq);
}

void MonoPipe::observeFrontAndNRPTS(int32_t *outFront, int64_t *outNextRdPTS)
{
    // Perform an atomic observation of mFront and mNextRdPTS.  Basically,
    // atomically observe the sequence number, then observer the variables, then
    // atomically observe the sequence number again.  If the two observations of
    // the sequence number match, and the update-in-progress bit was not set,
    // then we know we have a successful atomic observation.  Otherwise, we loop
    // around and try again.
    //
    // Note, it is very important that the observer be a lower priority thread
    // than the updater.  If the updater is lower than the observer, or they are
    // the same priority and running with SCHED_FIFO (implying that quantum
    // based premption is disabled) then we run the risk of deadlock.
    int32_t seqOne, seqTwo;

    do {
        seqOne        = android_atomic_acquire_load(&mUpdateSeq);
        *outFront     = mFront;
        *outNextRdPTS = mNextRdPTS;
        seqTwo        = android_atomic_release_load(&mUpdateSeq);
    } while ((seqOne != seqTwo) || (seqOne & 0x80000000));
}

int64_t MonoPipe::offsetTimestampByAudioFrames(int64_t ts, size_t audFrames)
{
    if (0 == mSamplesToLocalTime.a_to_b_denom)
        return AudioBufferProvider::kInvalidPTS;

    if (ts == AudioBufferProvider::kInvalidPTS)
        return AudioBufferProvider::kInvalidPTS;

    int64_t frame_lt_duration;
    if (!mSamplesToLocalTime.doForwardTransform(audFrames,
                                                &frame_lt_duration)) {
        // This should never fail, but if there is a bug which is causing it
        // to fail, this message would probably end up flooding the logs
        // because the conversion would probably fail forever.  Log the
        // error, but then zero out the ratio in the linear transform so
        // that we don't try to do any conversions from now on.  This
        // MonoPipe's getNextWriteTimestamp is now broken for good.
        ALOGE("Overflow when attempting to convert %zu audio frames to"
              " duration in local time.  getNextWriteTimestamp will fail from"
              " now on.", audFrames);
        mSamplesToLocalTime.a_to_b_numer = 0;
        mSamplesToLocalTime.a_to_b_denom = 0;
        return AudioBufferProvider::kInvalidPTS;
    }

    return ts + frame_lt_duration;
}

void MonoPipe::shutdown(bool newState)
{
    mIsShutdown = newState;
}

bool MonoPipe::isShutdown()
{
    return mIsShutdown;
}

status_t MonoPipe::getTimestamp(AudioTimestamp& timestamp)
{
    if (mTimestampObserver.poll(timestamp)) {
        return OK;
    }
    return INVALID_OPERATION;
}

}   // namespace android