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