blob: 231e03f36b4ca68fc30336a9a93fbc88850ca603 [file] [log] [blame]
// Copyright 2016 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 "topaz/examples/media/vu_meter/vu_meter_view.h"
#include <hid/usages.h>
#include <iomanip>
#include "lib/component/cpp/connect.h"
#include "src/lib/fxl/logging.h"
#include "lib/media/audio/types.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
constexpr zx_duration_t kCaptureDuration = ZX_MSEC(20);
constexpr uint64_t kBytesPerFrame = 4;
namespace examples {
VuMeterView::VuMeterView(scenic::ViewContext view_context, async::Loop* loop)
: SkiaView(std::move(view_context), "VU Meter"),
loop_(loop),
fast_left_(kFastDecay),
fast_right_(kFastDecay),
slow_left_(kSlowDecay),
slow_right_(kSlowDecay) {
FXL_DCHECK(loop);
auto audio =
startup_context()->ConnectToEnvironmentService<fuchsia::media::Audio>();
audio->CreateAudioCapturer(audio_capturer_.NewRequest(), false);
audio_capturer_.set_error_handler([this](zx_status_t status) {
FXL_LOG(ERROR) << "Connection error occurred. Quitting.";
Shutdown();
});
audio_capturer_->GetStreamType([this](fuchsia::media::StreamType type) {
OnDefaultFormatFetched(std::move(type));
});
}
void VuMeterView::OnInputEvent(fuchsia::ui::input::InputEvent event) {
if (event.is_pointer()) {
auto& pointer = event.pointer();
if (pointer.phase == fuchsia::ui::input::PointerEventPhase::DOWN) {
ToggleStartStop();
}
} else if (event.is_keyboard()) {
auto& keyboard = event.keyboard();
if (keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED) {
switch (keyboard.hid_usage) {
case HID_USAGE_KEY_SPACE:
ToggleStartStop();
break;
case HID_USAGE_KEY_Q:
Shutdown();
break;
default:
break;
}
}
}
}
void VuMeterView::OnSceneInvalidated(
fuchsia::images::PresentationInfo presentation_info) {
SkCanvas* canvas = AcquireCanvas();
if (canvas) {
DrawContent(canvas);
ReleaseAndSwapCanvas();
}
}
void VuMeterView::DrawContent(SkCanvas* canvas) {
canvas->clear(SK_ColorBLACK);
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(SK_ColorCYAN);
canvas->drawCircle(
logical_size().x / 3.0f, logical_size().y / 2,
(fast_left_.current() * logical_size().x / 2) / kVuFullWidth, paint);
canvas->drawCircle(
2.0f * logical_size().x / 3.0f, logical_size().y / 2,
(fast_right_.current() * logical_size().x / 2) / kVuFullWidth, paint);
paint.setColor(SK_ColorWHITE);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(SkIntToScalar(3));
canvas->drawCircle(
logical_size().x / 3.0f, logical_size().y / 2,
(slow_left_.current() * logical_size().x / 2) / kVuFullWidth, paint);
canvas->drawCircle(
2.0f * logical_size().x / 3.0f, logical_size().y / 2,
(slow_right_.current() * logical_size().x / 2) / kVuFullWidth, paint);
}
void VuMeterView::SendCaptureRequest() {
if (!started_ || request_in_flight_) {
return;
}
// clang-format off
audio_capturer_->CaptureAt(
0, 0, payload_buffer_.size() / kBytesPerFrame,
[this](fuchsia::media::StreamPacket packet) {
OnPacketCaptured(std::move(packet));
});
// clang-format on
request_in_flight_ = true;
}
void VuMeterView::OnDefaultFormatFetched(
fuchsia::media::StreamType default_type) {
// Set the media type, keep the default sample rate but make sure that we
// normalize to stereo 16-bit LPCM.
FXL_DCHECK(default_type.medium_specific.is_audio());
const auto& audio_details = default_type.medium_specific.audio();
audio_capturer_->SetPcmStreamType(
media::CreateAudioStreamType(fuchsia::media::AudioSampleFormat::SIGNED_16,
2, audio_details.frames_per_second));
uint64_t payload_buffer_size =
kBytesPerFrame *
((kCaptureDuration * audio_details.frames_per_second) / ZX_SEC(1));
constexpr zx_rights_t rights =
ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP;
zx_status_t zx_res;
zx::vmo vmo;
zx_res = payload_buffer_.CreateAndMap(payload_buffer_size, ZX_VM_PERM_READ,
nullptr, &vmo, rights);
if (zx_res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create payload buffer (res " << zx_res << ")";
Shutdown();
return;
}
audio_capturer_->AddPayloadBuffer(0, std::move(vmo));
// Start capturing.
ToggleStartStop();
}
void VuMeterView::OnPacketCaptured(fuchsia::media::StreamPacket packet) {
request_in_flight_ = false;
if (!started_) {
return;
}
// TODO(dalesat): Synchronize display and captured audio.
uint32_t frame_count =
static_cast<uint32_t>(payload_buffer_.size() / kBytesPerFrame);
int16_t* samples = reinterpret_cast<int16_t*>(payload_buffer_.start());
for (uint32_t i = 0; i < frame_count; ++i) {
int16_t abs_sample = std::abs(samples[0]);
fast_left_.Process(abs_sample);
slow_left_.Process(abs_sample);
abs_sample = std::abs(samples[1]);
fast_right_.Process(abs_sample);
slow_right_.Process(abs_sample);
samples += 2;
}
InvalidateScene();
SendCaptureRequest();
}
void VuMeterView::Shutdown() {
audio_capturer_.Unbind();
loop_->Quit();
}
} // namespace examples