| // 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 |