| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <audio-utils/audio-input.h> |
| #include <audio-utils/audio-stream.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <limits> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| namespace audio { |
| namespace utils { |
| |
| static constexpr zx_duration_t CHUNK_TIME = ZX_MSEC(100); |
| static constexpr float MIN_DURATION = 0.100f; |
| static constexpr float MAX_DURATION = 86400.0f; |
| |
| fbl::unique_ptr<AudioInput> AudioInput::Create(uint32_t dev_id) { |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_id)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| fbl::unique_ptr<AudioInput> AudioInput::Create(const char* dev_path) { |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_path)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| zx_status_t AudioInput::Record(AudioSink& sink, float duration_seconds) { |
| AudioStream::Format fmt = { |
| .frame_rate = frame_rate_, |
| .channels = static_cast<uint16_t>(channel_cnt_), |
| .sample_format = sample_format_, |
| }; |
| |
| duration_seconds = fbl::clamp(duration_seconds, MIN_DURATION, MAX_DURATION); |
| |
| zx_status_t res = sink.SetFormat(fmt); |
| if (res != ZX_OK) { |
| printf("Failed to set sink format (rate %u, chan_count %u, fmt 0x%08x, res %d)\n", |
| frame_rate_, channel_cnt_, sample_format_, res); |
| return res; |
| } |
| |
| uint64_t ring_bytes_64 = |
| (zx_duration_mul_int64(CHUNK_TIME, frame_rate_) / ZX_SEC(1)) * frame_sz_; |
| if (ring_bytes_64 > std::numeric_limits<uint32_t>::max()) { |
| printf("Invalid frame rate %u\n", frame_rate_); |
| return res; |
| } |
| |
| uint32_t ring_bytes = static_cast<uint32_t>(ring_bytes_64); |
| uint32_t ring_frames = ring_bytes / frame_sz_; |
| |
| res = GetBuffer(ring_frames, 2u); |
| if (res != ZX_OK) { |
| printf("Failed to establish ring buffer (%u frames, res %d)\n", |
| ring_frames, res); |
| return res; |
| } |
| |
| zx_duration_t duration_nsec = static_cast<zx_time_t>(ZX_SEC(1) |
| * static_cast<double>(duration_seconds)); |
| zx_time_t stop_time = zx_time_add_duration(zx_clock_get_monotonic(), duration_nsec); |
| printf("Recording for %.1f seconds\n", duration_seconds); |
| |
| res = StartRingBuffer(); |
| if (res != ZX_OK) { |
| printf("Failed to start capture (res %d)\n", res); |
| return res; |
| } |
| |
| uint32_t rd_ptr = 0; |
| bool peer_connected = true; |
| while (true) { |
| zx_signals_t sigs; |
| |
| res = rb_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| zx::time(stop_time), &sigs); |
| |
| // If we get a timeout error, we have hit our stop time. |
| if (res == ZX_ERR_TIMED_OUT) break; |
| |
| if (res != ZX_OK) { |
| printf("Failed to wait for notificiation (res %d)\n", res); |
| break; |
| } |
| |
| if (sigs & ZX_CHANNEL_PEER_CLOSED) { |
| printf("Peer closed connection during record!\n"); |
| peer_connected = false; |
| break; |
| } |
| |
| audio_rb_position_notify_t pos_notif; |
| |
| uint32_t bytes_read, junk; |
| res = rb_ch_.read(0, |
| &pos_notif, nullptr, sizeof(pos_notif), 0, |
| &bytes_read, &junk); |
| if (res != ZX_OK) { |
| printf("Failed to read notification from ring buffer channel (res %d)\n", res); |
| break; |
| } |
| |
| if (bytes_read != sizeof(pos_notif)) { |
| printf("Bad size when reading notification from ring buffer channel (%u != %zu)\n", |
| bytes_read, sizeof(pos_notif)); |
| res = ZX_ERR_INTERNAL; |
| break; |
| } |
| |
| if (pos_notif.hdr.cmd != AUDIO_RB_POSITION_NOTIFY) { |
| printf("Unexpected command type when reading notification from ring " |
| "buffer channel (cmd %04x)\n", pos_notif.hdr.cmd); |
| res = ZX_ERR_INTERNAL; |
| break; |
| } |
| |
| uint32_t todo = pos_notif.ring_buffer_pos + rb_sz_ - rd_ptr; |
| if (todo >= rb_sz_) |
| todo -= rb_sz_; |
| |
| ZX_DEBUG_ASSERT(todo < rb_sz_); |
| ZX_DEBUG_ASSERT(rd_ptr < rb_sz_); |
| |
| uint32_t space = rb_sz_ - rd_ptr; |
| uint32_t amt = fbl::min(space, todo); |
| auto data = static_cast<const uint8_t*>(rb_virt_) + rd_ptr; |
| |
| res = zx_cache_flush(data, amt, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| if (res != ZX_OK) { |
| printf("Failed to cache invalidate(res %d).\n", res); |
| break; |
| } |
| |
| res = sink.PutFrames(data, amt); |
| if (res != ZX_OK) { |
| printf("Failed to record %u bytes (res %d)\n", amt, res); |
| break; |
| } |
| |
| if (amt < todo) { |
| amt = todo - amt; |
| ZX_DEBUG_ASSERT(amt < rb_sz_); |
| |
| res = zx_cache_flush(rb_virt_, amt, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| if (res != ZX_OK) { |
| printf("Failed to cache invalidate(res %d) %d\n", res, __LINE__); |
| break; |
| } |
| |
| res = sink.PutFrames(rb_virt_, amt); |
| if (res != ZX_OK) { |
| printf("Failed to record %u bytes (res %d)\n", amt, res); |
| break; |
| } |
| |
| rd_ptr = amt; |
| } else { |
| rd_ptr += amt; |
| if (rd_ptr >= rb_sz_) { |
| ZX_DEBUG_ASSERT(rd_ptr == rb_sz_); |
| rd_ptr = 0; |
| } |
| } |
| } |
| |
| if (peer_connected) { |
| StopRingBuffer(); |
| } |
| |
| zx_status_t finalize_res = sink.Finalize(); |
| return (res == ZX_OK) ? finalize_res : res; |
| } |
| |
| } // namespace utils |
| } // namespace audio |