| // 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 <stdio.h> |
| #include <string.h> |
| #include <zircon/device/audio.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include <audio-utils/audio-output.h> |
| #include <audio-utils/audio-stream.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| |
| namespace audio { |
| namespace utils { |
| |
| std::unique_ptr<AudioOutput> AudioOutput::Create(uint32_t dev_id) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_id)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| std::unique_ptr<AudioOutput> AudioOutput::Create(const char* dev_path) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_path)); |
| if (!ac.check()) |
| return nullptr; |
| return res; |
| } |
| |
| zx_status_t AudioOutput::Play(AudioSource& source) { |
| zx_status_t res; |
| |
| if (source.finished()) |
| return ZX_OK; |
| |
| AudioSource::Format format; |
| res = source.GetFormat(&format); |
| if (res != ZX_OK) { |
| printf("Failed to get source's format (res %d)\n", res); |
| return res; |
| } |
| |
| res = SetFormat(format.frame_rate, format.channels, format.sample_format); |
| if (res != ZX_OK) { |
| printf("Failed to set source format [%u Hz, %hu Chan, %08x fmt] (res %d)\n", format.frame_rate, |
| format.channels, format.sample_format, res); |
| return res; |
| } |
| |
| // ALSA under QEMU required huge buffers. |
| // |
| // TODO(johngro) : Add the ability to determine what type of read-ahead the |
| // HW is going to require so we can adjust our buffer size to what the HW |
| // requires, not what ALSA under QEMU requires. |
| res = GetBuffer(480 * 20 * 3, 3); |
| if (res != ZX_OK) { |
| printf("Failed to set output format (res %d)\n", res); |
| return res; |
| } |
| |
| memset(rb_virt_, 0, rb_sz_); |
| |
| auto buf = reinterpret_cast<uint8_t*>(rb_virt_); |
| uint32_t rd, wr; |
| uint32_t playout_rd, playout_amt; |
| bool started = false; |
| rd = wr = 0; |
| playout_rd = playout_amt = 0; |
| |
| while (true) { |
| uint32_t bytes_read, junk; |
| audio_rb_position_notify_t pos_notif; |
| zx_signals_t sigs; |
| |
| // Top up the buffer. In theory, we should only need to loop 2 times in |
| // order to handle a ring discontinuity |
| for (uint32_t i = 0; i < 2; ++i) { |
| uint32_t space = (rb_sz_ + rd - wr - 1) % rb_sz_; |
| uint32_t todo = std::min(space, rb_sz_ - wr); |
| ZX_DEBUG_ASSERT(space < rb_sz_); |
| |
| if (!todo) |
| break; |
| |
| if (source.finished()) { |
| memset(buf + wr, 0, todo); |
| zx_cache_flush(buf + wr, todo, ZX_CACHE_FLUSH_DATA); |
| |
| wr += todo; |
| } else { |
| uint32_t done; |
| res = source.GetFrames(buf + wr, std::min(space, rb_sz_ - wr), &done); |
| if (res != ZX_OK) { |
| printf("Error packing frames (res %d)\n", res); |
| break; |
| } |
| zx_cache_flush(buf + wr, done, ZX_CACHE_FLUSH_DATA); |
| wr += done; |
| |
| if (source.finished()) { |
| playout_rd = rd; |
| playout_amt = (rb_sz_ + wr - rd) % rb_sz_; |
| |
| // We have just become finished. Reset the loop counter and |
| // start over, this time filling with as much silence as we |
| // can. |
| i = 0; |
| } |
| } |
| |
| if (wr < rb_sz_) |
| break; |
| |
| ZX_DEBUG_ASSERT(wr == rb_sz_); |
| wr = 0; |
| } |
| |
| if (res != ZX_OK) |
| break; |
| |
| // If we have not started yet, do so. |
| if (!started) { |
| res = StartRingBuffer(); |
| if (res != ZX_OK) { |
| printf("Failed to start ring buffer!\n"); |
| break; |
| } |
| started = true; |
| } |
| |
| res = |
| rb_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &sigs); |
| |
| 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 playback!\n"); |
| break; |
| } |
| |
| 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; |
| } |
| |
| rd = pos_notif.ring_buffer_pos; |
| |
| // rd has moved. If the source has finished and rd has moved at least |
| // the playout distance, we are finsihed. |
| if (source.finished()) { |
| uint32_t dist = (rb_sz_ + rd - playout_rd) % rb_sz_; |
| |
| if (dist >= playout_amt) |
| break; |
| |
| playout_amt -= dist; |
| playout_rd = rd; |
| } |
| } |
| |
| if (res == ZX_OK) { |
| // We have already let the DMA engine catch up, but we still need to |
| // wait for the fifo to play out. For now, just hard code this as |
| // 30uSec. |
| // |
| // TODO: base this on the start time and the number of frames queued |
| // instead of just making a number up. |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(30))); |
| } |
| |
| zx_status_t stop_res = StopRingBuffer(); |
| if (res == ZX_OK) |
| res = stop_res; |
| |
| return res; |
| } |
| |
| } // namespace utils |
| } // namespace audio |