1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <audio-utils/audio-output.h>
6#include <audio-utils/audio-stream.h>
7#include <zircon/device/audio.h>
8#include <fbl/algorithm.h>
9#include <fbl/alloc_checker.h>
10#include <fbl/limits.h>
11#include <stdio.h>
12#include <string.h>
13
14namespace audio {
15namespace utils {
16
17fbl::unique_ptr<AudioOutput> AudioOutput::Create(uint32_t dev_id) {
18    fbl::AllocChecker ac;
19    fbl::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_id));
20    if (!ac.check())
21        return nullptr;
22    return res;
23}
24
25fbl::unique_ptr<AudioOutput> AudioOutput::Create(const char* dev_path) {
26    fbl::AllocChecker ac;
27    fbl::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_path));
28    if (!ac.check())
29        return nullptr;
30    return res;
31}
32
33zx_status_t AudioOutput::Play(AudioSource& source) {
34    zx_status_t res;
35
36    if (source.finished())
37        return ZX_OK;
38
39    AudioSource::Format format;
40    res = source.GetFormat(&format);
41    if (res != ZX_OK) {
42        printf("Failed to get source's format (res %d)\n", res);
43        return res;
44    }
45
46    res = SetFormat(format.frame_rate, format.channels, format.sample_format);
47    if (res != ZX_OK) {
48        printf("Failed to set source format [%u Hz, %hu Chan, %08x fmt] (res %d)\n",
49                format.frame_rate, format.channels, format.sample_format, res);
50        return res;
51    }
52
53    // ALSA under QEMU required huge buffers.
54    //
55    // TODO(johngro) : Add the ability to determine what type of read-ahead the
56    // HW is going to require so we can adjust our buffer size to what the HW
57    // requires, not what ALSA under QEMU requires.
58    res = GetBuffer(480 * 20 * 3, 3);
59    if (res != ZX_OK) {
60        printf("Failed to set output format (res %d)\n", res);
61        return res;
62    }
63
64    memset(rb_virt_, 0, rb_sz_);
65
66    auto buf = reinterpret_cast<uint8_t*>(rb_virt_);
67    uint32_t rd, wr;
68    uint32_t playout_rd, playout_amt;
69    bool started = false;
70    rd = wr = 0;
71    playout_rd = playout_amt = 0;
72
73    while (true) {
74        uint32_t bytes_read, junk;
75        audio_rb_position_notify_t pos_notif;
76        zx_signals_t sigs;
77
78        // Top up the buffer.  In theory, we should only need to loop 2 times in
79        // order to handle a ring discontinuity
80        for (uint32_t i = 0; i < 2; ++i) {
81            uint32_t space = (rb_sz_ + rd - wr - 1) % rb_sz_;
82            uint32_t todo  = fbl::min(space, rb_sz_ - wr);
83            ZX_DEBUG_ASSERT(space < rb_sz_);
84
85            if (!todo)
86                break;
87
88            if (source.finished()) {
89                memset(buf + wr, 0, todo);
90                zx_cache_flush(buf + wr, todo, ZX_CACHE_FLUSH_DATA);
91
92                wr += todo;
93            } else {
94                uint32_t done;
95                res = source.GetFrames(buf + wr, fbl::min(space, rb_sz_ - wr), &done);
96                if (res != ZX_OK) {
97                    printf("Error packing frames (res %d)\n", res);
98                    break;
99                }
100                zx_cache_flush(buf + wr, done, ZX_CACHE_FLUSH_DATA);
101                wr += done;
102
103                if (source.finished()) {
104                    playout_rd  = rd;
105                    playout_amt = (rb_sz_ + wr - rd) % rb_sz_;
106
107                    // We have just become finished.  Reset the loop counter and
108                    // start over, this time filling with as much silence as we
109                    // can.
110                    i = 0;
111                }
112            }
113
114            if (wr < rb_sz_)
115                break;
116
117            ZX_DEBUG_ASSERT(wr == rb_sz_);
118            wr = 0;
119        }
120
121        if (res != ZX_OK)
122            break;
123
124        // If we have not started yet, do so.
125        if (!started) {
126            res = StartRingBuffer();
127            if (res != ZX_OK) {
128                printf("Failed to start ring buffer!\n");
129                break;
130            }
131            started = true;
132        }
133
134        res = rb_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
135                              zx::time::infinite(), &sigs);
136
137        if (res != ZX_OK) {
138            printf("Failed to wait for notificiation (res %d)\n", res);
139            break;
140        }
141
142        if (sigs & ZX_CHANNEL_PEER_CLOSED) {
143            printf("Peer closed connection during playback!\n");
144            break;
145        }
146
147        res = rb_ch_.read(0,
148                          &pos_notif, sizeof(pos_notif), &bytes_read,
149                          nullptr, 0, &junk);
150        if (res != ZX_OK) {
151            printf("Failed to read notification from ring buffer channel (res %d)\n", res);
152            break;
153        }
154
155        if (bytes_read != sizeof(pos_notif)) {
156            printf("Bad size when reading notification from ring buffer channel (%u != %zu)\n",
157                   bytes_read, sizeof(pos_notif));
158            res = ZX_ERR_INTERNAL;
159            break;
160        }
161
162        if (pos_notif.hdr.cmd != AUDIO_RB_POSITION_NOTIFY) {
163            printf("Unexpected command type when reading notification from ring "
164                   "buffer channel (cmd %04x)\n", pos_notif.hdr.cmd);
165            res = ZX_ERR_INTERNAL;
166            break;
167        }
168
169        rd = pos_notif.ring_buffer_pos;
170
171        // rd has moved.  If the source has finished and rd has moved at least
172        // the playout distance, we are finsihed.
173        if (source.finished()) {
174            uint32_t dist = (rb_sz_ + rd - playout_rd) % rb_sz_;
175
176            if (dist >= playout_amt)
177                break;
178
179            playout_amt -= dist;
180            playout_rd   = rd;
181        }
182    }
183
184    if (res == ZX_OK) {
185        // We have already let the DMA engine catch up, but we still need to
186        // wait for the fifo to play out.  For now, just hard code this as
187        // 30uSec.
188        //
189        // TODO: base this on the start time and the number of frames queued
190        // instead of just making a number up.
191        zx_nanosleep(zx_deadline_after(ZX_MSEC(30)));
192    }
193
194    zx_status_t stop_res = StopRingBuffer();
195    if (res == ZX_OK)
196        res = stop_res;
197
198    return res;
199}
200
201}  // namespace utils
202}  // namespace audio
203