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