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