| // 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 <lib/affine/transform.h> |
| #include <lib/zx/clock.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <limits> |
| #include <memory> |
| |
| #include <audio-utils/audio-input.h> |
| #include <audio-utils/audio-stream.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.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; |
| |
| std::unique_ptr<AudioInput> AudioInput::Create(uint32_t dev_id) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_id)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| std::unique_ptr<AudioInput> AudioInput::Create(const char* dev_path) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<AudioInput> res(new (&ac) AudioInput(dev_path)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| zx_status_t AudioInput::Record(AudioSink& sink, Duration duration) { |
| auto res = RecordPrepare(sink); |
| if (res != ZX_OK) |
| return res; |
| res = StartRingBuffer(); |
| if (res != ZX_OK) { |
| printf("Failed to start capture (res %d)\n", res); |
| return res; |
| } |
| return RecordToCompletion(sink, duration); |
| } |
| |
| zx_status_t AudioInput::RecordPrepare(AudioSink& sink) { |
| AudioStream::Format fmt = { |
| .frame_rate = frame_rate_, |
| .channels = static_cast<uint16_t>(channel_cnt_), |
| .sample_format = sample_format_, |
| }; |
| |
| 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, 8u); |
| if (res != ZX_OK) { |
| printf("Failed to establish ring buffer (%u frames, res %d)\n", ring_frames, res); |
| return res; |
| } |
| |
| return res; |
| } |
| |
| zx_status_t AudioInput::RecordToCompletion(AudioSink& sink, Duration duration) { |
| zx_status_t res = ZX_OK; |
| long frames_expected = 0; |
| int64_t bytes_expected = 0; |
| const bool loop = std::holds_alternative<LoopingDoneCallback>(duration); |
| if (!loop) { |
| std::get<float>(duration) = std::clamp(std::get<float>(duration), MIN_DURATION, MAX_DURATION); |
| printf("Recording for %.1f seconds\n", std::get<float>(duration)); |
| frames_expected = std::lround(frame_rate_ * std::get<float>(duration)); |
| bytes_expected = frame_sz_ * frames_expected; |
| } |
| |
| uint32_t rd_ptr = 0; // Our read ptr for the ring buffer. |
| uint32_t wr_ptr = 0; // Estimated write ptr in the ring buffer. |
| uint32_t consumed = 0; // Total bytes consumed. |
| uint32_t produced = 0; // Estimated total bytes produced. |
| |
| // A transformation from time to bytes captured safe to read. We wait until we have received about |
| // 4 FIFOs before start reading to make sure we are behind the HW. We start the transformation at |
| // -2 FIFOs and wake up after another 2 FIFOs. |
| auto mono_to_safe_read_bytes = affine::Transform{static_cast<int64_t>(start_time_), |
| -2 * static_cast<int64_t>(fifo_depth_), |
| {frame_rate_ * frame_sz_, zx::sec(1).get()}}; |
| auto next_wake_time = zx::time(mono_to_safe_read_bytes.ApplyInverse(2 * fifo_depth_)); |
| |
| // Repeat until looping is done or until consumed >= bytes_expected. |
| while ((loop && std::get<LoopingDoneCallback>(duration)()) || |
| (!loop && consumed < bytes_expected)) { |
| // We specify a floor to avoid not having a reasonable deadline per loop. |
| constexpr auto kFloorWait = zx::msec(10); |
| auto floor_wake_time = zx::clock::get_monotonic() + kFloorWait; |
| if (next_wake_time < floor_wake_time) { |
| next_wake_time = floor_wake_time; |
| } |
| zx::nanosleep(next_wake_time); |
| auto safe_read = mono_to_safe_read_bytes.Apply(zx::clock::get_monotonic().get()); |
| |
| if (loop) { |
| consumed = static_cast<uint32_t>(safe_read) - (static_cast<uint32_t>(safe_read) % frame_sz_); |
| } else { |
| consumed = std::min(safe_read, bytes_expected); |
| } |
| uint32_t increment = consumed - produced; |
| |
| // We want to process about 2 FIFOs worth of samples in each loop. |
| next_wake_time = zx::time(mono_to_safe_read_bytes.ApplyInverse(safe_read + 2 * fifo_depth_)); |
| |
| wr_ptr += increment; |
| produced += increment; |
| if (wr_ptr > rb_sz_) { |
| wr_ptr -= rb_sz_; |
| } |
| |
| uint32_t todo = wr_ptr + 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 = std::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; |
| } |
| } |
| } |
| |
| StopRingBuffer(); |
| |
| zx_status_t finalize_res = sink.Finalize(); |
| return (res == ZX_OK) ? finalize_res : res; |
| } |
| |
| } // namespace utils |
| } // namespace audio |