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-input.h>
6 #include <audio-utils/audio-stream.h>
7 #include <fbl/algorithm.h>
8 #include <fbl/alloc_checker.h>
9 #include <limits>
10 #include <zircon/time.h>
11 #include <zircon/types.h>
12
13 namespace audio {
14 namespace utils {
15
16 static constexpr zx_duration_t CHUNK_TIME = ZX_MSEC(100);
17 static constexpr float MIN_DURATION = 0.100f;
18 static constexpr float MAX_DURATION = 86400.0f;
19
Create(uint32_t dev_id)20 fbl::unique_ptr<AudioInput> AudioInput::Create(uint32_t dev_id) {
21 fbl::AllocChecker ac;
22 fbl::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_id));
23 if (!ac.check())
24 return nullptr;
25 return res;
26 }
27
Create(const char * dev_path)28 fbl::unique_ptr<AudioInput> AudioInput::Create(const char* dev_path) {
29 fbl::AllocChecker ac;
30 fbl::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_path));
31 if (!ac.check())
32 return nullptr;
33 return res;
34 }
35
Record(AudioSink & sink,float duration_seconds)36 zx_status_t AudioInput::Record(AudioSink& sink, float duration_seconds) {
37 AudioStream::Format fmt = {
38 .frame_rate = frame_rate_,
39 .channels = static_cast<uint16_t>(channel_cnt_),
40 .sample_format = sample_format_,
41 };
42
43 duration_seconds = fbl::clamp(duration_seconds, MIN_DURATION, MAX_DURATION);
44
45 zx_status_t res = sink.SetFormat(fmt);
46 if (res != ZX_OK) {
47 printf("Failed to set sink format (rate %u, chan_count %u, fmt 0x%08x, res %d)\n",
48 frame_rate_, channel_cnt_, sample_format_, res);
49 return res;
50 }
51
52 uint64_t ring_bytes_64 =
53 (zx_duration_mul_int64(CHUNK_TIME, frame_rate_) / ZX_SEC(1)) * frame_sz_;
54 if (ring_bytes_64 > std::numeric_limits<uint32_t>::max()) {
55 printf("Invalid frame rate %u\n", frame_rate_);
56 return res;
57 }
58
59 uint32_t ring_bytes = static_cast<uint32_t>(ring_bytes_64);
60 uint32_t ring_frames = ring_bytes / frame_sz_;
61
62 res = GetBuffer(ring_frames, 2u);
63 if (res != ZX_OK) {
64 printf("Failed to establish ring buffer (%u frames, res %d)\n",
65 ring_frames, res);
66 return res;
67 }
68
69 zx_duration_t duration_nsec = static_cast<zx_time_t>(ZX_SEC(1)
70 * static_cast<double>(duration_seconds));
71 zx_time_t stop_time = zx_time_add_duration(zx_clock_get_monotonic(), duration_nsec);
72 printf("Recording for %.1f seconds\n", duration_seconds);
73
74 res = StartRingBuffer();
75 if (res != ZX_OK) {
76 printf("Failed to start capture (res %d)\n", res);
77 return res;
78 }
79
80 uint32_t rd_ptr = 0;
81 bool peer_connected = true;
82 while (true) {
83 zx_signals_t sigs;
84
85 res = rb_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
86 zx::time(stop_time), &sigs);
87
88 // If we get a timeout error, we have hit our stop time.
89 if (res == ZX_ERR_TIMED_OUT) break;
90
91 if (res != ZX_OK) {
92 printf("Failed to wait for notificiation (res %d)\n", res);
93 break;
94 }
95
96 if (sigs & ZX_CHANNEL_PEER_CLOSED) {
97 printf("Peer closed connection during record!\n");
98 peer_connected = false;
99 break;
100 }
101
102 audio_rb_position_notify_t pos_notif;
103
104 uint32_t bytes_read, junk;
105 res = rb_ch_.read(0,
106 &pos_notif, sizeof(pos_notif), &bytes_read,
107 nullptr, 0, &junk);
108 if (res != ZX_OK) {
109 printf("Failed to read notification from ring buffer channel (res %d)\n", res);
110 break;
111 }
112
113 if (bytes_read != sizeof(pos_notif)) {
114 printf("Bad size when reading notification from ring buffer channel (%u != %zu)\n",
115 bytes_read, sizeof(pos_notif));
116 res = ZX_ERR_INTERNAL;
117 break;
118 }
119
120 if (pos_notif.hdr.cmd != AUDIO_RB_POSITION_NOTIFY) {
121 printf("Unexpected command type when reading notification from ring "
122 "buffer channel (cmd %04x)\n", pos_notif.hdr.cmd);
123 res = ZX_ERR_INTERNAL;
124 break;
125 }
126
127 uint32_t todo = pos_notif.ring_buffer_pos + rb_sz_ - rd_ptr;
128 if (todo >= rb_sz_)
129 todo -= rb_sz_;
130
131 ZX_DEBUG_ASSERT(todo < rb_sz_);
132 ZX_DEBUG_ASSERT(rd_ptr < rb_sz_);
133
134 uint32_t space = rb_sz_ - rd_ptr;
135 uint32_t amt = fbl::min(space, todo);
136 auto data = static_cast<const uint8_t*>(rb_virt_) + rd_ptr;
137
138 res = zx_cache_flush(data, amt, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
139 if (res != ZX_OK) {
140 printf("Failed to cache invalidate(res %d).\n", res);
141 break;
142 }
143
144 res = sink.PutFrames(data, amt);
145 if (res != ZX_OK) {
146 printf("Failed to record %u bytes (res %d)\n", amt, res);
147 break;
148 }
149
150 if (amt < todo) {
151 amt = todo - amt;
152 ZX_DEBUG_ASSERT(amt < rb_sz_);
153
154 res = zx_cache_flush(rb_virt_, amt, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE);
155 if (res != ZX_OK) {
156 printf("Failed to cache invalidate(res %d) %d\n", res, __LINE__);
157 break;
158 }
159
160 res = sink.PutFrames(rb_virt_, amt);
161 if (res != ZX_OK) {
162 printf("Failed to record %u bytes (res %d)\n", amt, res);
163 break;
164 }
165
166 rd_ptr = amt;
167 } else {
168 rd_ptr += amt;
169 if (rd_ptr >= rb_sz_) {
170 ZX_DEBUG_ASSERT(rd_ptr == rb_sz_);
171 rd_ptr = 0;
172 }
173 }
174 }
175
176 if (peer_connected) {
177 StopRingBuffer();
178 }
179
180 zx_status_t finalize_res = sink.Finalize();
181 return (res == ZX_OK) ? finalize_res : res;
182 }
183
184 } // namespace utils
185 } // namespace audio
186