blob: b8504603e2d5dcefc170d6a6a4330549502acc7f [file] [log] [blame]
// 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