| // 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 "src/media/audio/audio_core/ring_buffer.h" |
| |
| #include <memory> |
| |
| #include <trace/event.h> |
| |
| #include "src/lib/syslog/cpp/logger.h" |
| |
| namespace media::audio { |
| namespace { |
| |
| template <class RingBufferT> |
| struct BufferTraits { |
| static std::optional<typename RingBufferT::Buffer> MakeBuffer(int64_t start, uint32_t length, |
| void* payload); |
| }; |
| |
| template <> |
| struct BufferTraits<ReadableRingBuffer> { |
| static std::optional<ReadableStream::Buffer> MakeBuffer(int64_t start, uint32_t length, |
| void* payload) { |
| return std::make_optional<ReadableStream::Buffer>(start, length, payload, true); |
| } |
| }; |
| |
| template <> |
| struct BufferTraits<WritableRingBuffer> { |
| // TODO(50442): Technically the destructor should flush cache for the memory range that was locked |
| // when is_hardware_buffer == true. |
| static std::optional<WritableStream::Buffer> MakeBuffer(int64_t start, uint32_t length, |
| void* payload) { |
| return std::make_optional<WritableStream::Buffer>(start, length, payload); |
| } |
| }; |
| |
| template <class RingBufferT> |
| std::optional<typename RingBufferT::Buffer> LockBuffer(RingBufferT* b, zx::time now, int64_t frame, |
| uint32_t frame_count, bool is_read_lock, |
| bool is_hardware_buffer) { |
| frame += b->offset_frames(); |
| auto [reference_clock_to_fractional_frame, _] = b->ReferenceClockToFractionalFrames(); |
| if (!reference_clock_to_fractional_frame.invertible()) { |
| return std::nullopt; |
| } |
| |
| int64_t ring_position_now = |
| FractionalFrames<int64_t>::FromRaw(reference_clock_to_fractional_frame.Apply(now.get())) |
| .Floor() + |
| b->offset_frames(); |
| |
| int64_t first_valid_frame; |
| int64_t last_valid_frame; |
| if (is_read_lock) { |
| last_valid_frame = ring_position_now; |
| first_valid_frame = last_valid_frame - b->frames(); |
| } else { |
| first_valid_frame = ring_position_now; |
| last_valid_frame = first_valid_frame + b->frames(); |
| } |
| if (frame >= last_valid_frame || (frame + frame_count) <= first_valid_frame) { |
| return std::nullopt; |
| } |
| |
| int64_t last_requested_frame = frame + frame_count; |
| |
| // 'absolute' here means the frame number not adjusted for the ring size. 'local' is the frame |
| // number modulo ring size. |
| int64_t first_absolute_frame = std::max(frame, first_valid_frame); |
| |
| int64_t first_frame_local = first_absolute_frame % b->frames(); |
| if (first_frame_local < 0) { |
| first_frame_local += b->frames(); |
| } |
| int64_t last_frame_local = std::min(last_requested_frame, last_valid_frame) % b->frames(); |
| if (last_frame_local <= first_frame_local) { |
| last_frame_local = b->frames(); |
| } |
| |
| void* payload = b->virt() + (first_frame_local * b->format().bytes_per_frame()); |
| uint32_t payload_frames = last_frame_local - first_frame_local; |
| size_t payload_bytes = payload_frames * b->format().bytes_per_frame(); |
| |
| // Software buffers are entirely within-process and we assume that higher-level readers and |
| // writers are synchronized appropriately. |
| // |
| // Hardware buffers are shared with hardware, so we need to flush cache to ensure we read the |
| // latest data. |
| if (is_read_lock && is_hardware_buffer) { |
| zx_cache_flush(payload, payload_bytes, ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE); |
| } |
| |
| return BufferTraits<RingBufferT>::MakeBuffer(first_absolute_frame - b->offset_frames(), |
| payload_frames, payload); |
| } |
| |
| fbl::RefPtr<RefCountedVmoMapper> MapVmo(const Format& format, zx::vmo vmo, uint32_t frame_count, |
| bool writable) { |
| if (!vmo.is_valid()) { |
| FX_LOGS(ERROR) << "Invalid VMO!"; |
| return nullptr; |
| } |
| |
| if (!format.bytes_per_frame()) { |
| FX_LOGS(ERROR) << "Frame size may not be zero!"; |
| return nullptr; |
| } |
| |
| uint64_t vmo_size; |
| zx_status_t status = vmo.get_size(&vmo_size); |
| |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to get ring buffer VMO size"; |
| return nullptr; |
| } |
| |
| uint64_t size = static_cast<uint64_t>(format.bytes_per_frame()) * frame_count; |
| if (size > vmo_size) { |
| FX_LOGS(ERROR) << "Driver-reported ring buffer size (" << size << ") is greater than VMO size (" |
| << vmo_size << ")"; |
| return nullptr; |
| } |
| |
| // Map the VMO into our address space. |
| // TODO(35022): How do I specify the cache policy for this mapping? |
| zx_vm_option_t flags = ZX_VM_PERM_READ | (writable ? ZX_VM_PERM_WRITE : 0); |
| auto vmo_mapper = fbl::MakeRefCounted<RefCountedVmoMapper>(); |
| status = vmo_mapper->Map(vmo, 0u, size, flags); |
| |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to map ring buffer VMO"; |
| return nullptr; |
| } |
| |
| return vmo_mapper; |
| } |
| |
| } // namespace |
| |
| BaseRingBuffer::BaseRingBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frames, |
| fbl::RefPtr<RefCountedVmoMapper> vmo_mapper, uint32_t frame_count, uint32_t offset_frames, |
| bool is_hardware_buffer) |
| : vmo_mapper_(std::move(vmo_mapper)), |
| frames_(frame_count), |
| reference_clock_to_fractional_frame_(std::move(reference_clock_to_fractional_frames)), |
| offset_frames_(offset_frames), |
| is_hardware_buffer_(is_hardware_buffer) { |
| FX_CHECK(vmo_mapper_->start() != nullptr); |
| FX_CHECK(vmo_mapper_->size() >= (format.bytes_per_frame() * frame_count)); |
| } |
| |
| ReadableRingBuffer::ReadableRingBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frames, |
| fbl::RefPtr<RefCountedVmoMapper> vmo_mapper, uint32_t frame_count, uint32_t offset_frames, |
| bool is_hardware_buffer) |
| : ReadableStream(format), |
| BaseRingBuffer(format, reference_clock_to_fractional_frames, vmo_mapper, frame_count, |
| offset_frames, is_hardware_buffer) {} |
| |
| WritableRingBuffer::WritableRingBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frames, |
| fbl::RefPtr<RefCountedVmoMapper> vmo_mapper, uint32_t frame_count, uint32_t offset_frames, |
| bool is_hardware_buffer) |
| : WritableStream(format), |
| BaseRingBuffer(format, reference_clock_to_fractional_frames, vmo_mapper, frame_count, |
| offset_frames, is_hardware_buffer) {} |
| |
| // static |
| BaseRingBuffer::Endpoints BaseRingBuffer::AllocateSoftwareBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frame, |
| uint32_t frame_count, uint32_t frame_offset) { |
| TRACE_DURATION("audio", "RingBuffer::AllocateSoftwareBuffer"); |
| |
| size_t vmo_size = frame_count * format.bytes_per_frame(); |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(vmo_size, 0, &vmo); |
| FX_CHECK(status == ZX_OK); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to allocate ring buffer VMO with size " << vmo_size; |
| FX_CHECK(false); |
| } |
| |
| auto vmo_mapper = MapVmo(format, std::move(vmo), frame_count, true); |
| FX_DCHECK(vmo_mapper); |
| |
| return Endpoints{ |
| .reader = std::make_shared<ReadableRingBuffer>(format, reference_clock_to_fractional_frame, |
| vmo_mapper, frame_count, frame_offset, false), |
| .writer = std::make_shared<WritableRingBuffer>(format, reference_clock_to_fractional_frame, |
| vmo_mapper, frame_count, frame_offset, false), |
| }; |
| } |
| |
| // static |
| std::shared_ptr<ReadableRingBuffer> BaseRingBuffer::CreateReadableHardwareBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frame, zx::vmo vmo, |
| uint32_t frame_count, uint32_t offset_frames) { |
| TRACE_DURATION("audio", "RingBuffer::CreateReadableHardwareBuffer"); |
| |
| auto vmo_mapper = MapVmo(format, std::move(vmo), frame_count, false); |
| FX_DCHECK(vmo_mapper); |
| |
| return std::make_shared<ReadableRingBuffer>( |
| format, std::move(reference_clock_to_fractional_frame), std::move(vmo_mapper), frame_count, |
| offset_frames, true); |
| } |
| |
| // static |
| std::shared_ptr<WritableRingBuffer> BaseRingBuffer::CreateWritableHardwareBuffer( |
| const Format& format, |
| fbl::RefPtr<VersionedTimelineFunction> reference_clock_to_fractional_frame, zx::vmo vmo, |
| uint32_t frame_count, uint32_t offset_frames) { |
| TRACE_DURATION("audio", "RingBuffer::CreateWritableHardwareBuffer"); |
| |
| auto vmo_mapper = MapVmo(format, std::move(vmo), frame_count, true); |
| FX_DCHECK(vmo_mapper); |
| |
| return std::make_shared<WritableRingBuffer>( |
| format, std::move(reference_clock_to_fractional_frame), std::move(vmo_mapper), frame_count, |
| offset_frames, true); |
| } |
| |
| std::optional<ReadableStream::Buffer> ReadableRingBuffer::ReadLock(zx::time now, int64_t frame, |
| uint32_t frame_count) { |
| return LockBuffer<ReadableRingBuffer>(this, now, frame, frame_count, true, is_hardware_buffer_); |
| } |
| |
| std::optional<WritableStream::Buffer> WritableRingBuffer::WriteLock(zx::time now, int64_t frame, |
| uint32_t frame_count) { |
| return LockBuffer<WritableRingBuffer>(this, now, frame, frame_count, false, is_hardware_buffer_); |
| } |
| |
| BaseStream::TimelineFunctionSnapshot BaseRingBuffer::ReferenceClockToFractionalFramesImpl() const { |
| if (!reference_clock_to_fractional_frame_) { |
| return { |
| .timeline_function = TimelineFunction(), |
| .generation = kInvalidGenerationId, |
| }; |
| } |
| auto [timeline_function, generation] = reference_clock_to_fractional_frame_->get(); |
| return { |
| .timeline_function = timeline_function, |
| .generation = generation, |
| }; |
| } |
| |
| BaseStream::TimelineFunctionSnapshot ReadableRingBuffer::ReferenceClockToFractionalFrames() const { |
| return BaseRingBuffer::ReferenceClockToFractionalFramesImpl(); |
| } |
| |
| BaseStream::TimelineFunctionSnapshot WritableRingBuffer::ReferenceClockToFractionalFrames() const { |
| return BaseRingBuffer::ReferenceClockToFractionalFramesImpl(); |
| } |
| |
| } // namespace media::audio |