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