1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <audio-utils/audio-output.h> 6#include <audio-utils/audio-stream.h> 7#include <zircon/device/audio.h> 8#include <fbl/algorithm.h> 9#include <fbl/alloc_checker.h> 10#include <fbl/limits.h> 11#include <stdio.h> 12#include <string.h> 13 14namespace audio { 15namespace utils { 16 17fbl::unique_ptr<AudioOutput> AudioOutput::Create(uint32_t dev_id) { 18 fbl::AllocChecker ac; 19 fbl::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_id)); 20 if (!ac.check()) 21 return nullptr; 22 return res; 23} 24 25fbl::unique_ptr<AudioOutput> AudioOutput::Create(const char* dev_path) { 26 fbl::AllocChecker ac; 27 fbl::unique_ptr<AudioOutput> res(new (&ac) AudioOutput(dev_path)); 28 if (!ac.check()) 29 return nullptr; 30 return res; 31} 32 33zx_status_t AudioOutput::Play(AudioSource& source) { 34 zx_status_t res; 35 36 if (source.finished()) 37 return ZX_OK; 38 39 AudioSource::Format format; 40 res = source.GetFormat(&format); 41 if (res != ZX_OK) { 42 printf("Failed to get source's format (res %d)\n", res); 43 return res; 44 } 45 46 res = SetFormat(format.frame_rate, format.channels, format.sample_format); 47 if (res != ZX_OK) { 48 printf("Failed to set source format [%u Hz, %hu Chan, %08x fmt] (res %d)\n", 49 format.frame_rate, format.channels, format.sample_format, res); 50 return res; 51 } 52 53 // ALSA under QEMU required huge buffers. 54 // 55 // TODO(johngro) : Add the ability to determine what type of read-ahead the 56 // HW is going to require so we can adjust our buffer size to what the HW 57 // requires, not what ALSA under QEMU requires. 58 res = GetBuffer(480 * 20 * 3, 3); 59 if (res != ZX_OK) { 60 printf("Failed to set output format (res %d)\n", res); 61 return res; 62 } 63 64 memset(rb_virt_, 0, rb_sz_); 65 66 auto buf = reinterpret_cast<uint8_t*>(rb_virt_); 67 uint32_t rd, wr; 68 uint32_t playout_rd, playout_amt; 69 bool started = false; 70 rd = wr = 0; 71 playout_rd = playout_amt = 0; 72 73 while (true) { 74 uint32_t bytes_read, junk; 75 audio_rb_position_notify_t pos_notif; 76 zx_signals_t sigs; 77 78 // Top up the buffer. In theory, we should only need to loop 2 times in 79 // order to handle a ring discontinuity 80 for (uint32_t i = 0; i < 2; ++i) { 81 uint32_t space = (rb_sz_ + rd - wr - 1) % rb_sz_; 82 uint32_t todo = fbl::min(space, rb_sz_ - wr); 83 ZX_DEBUG_ASSERT(space < rb_sz_); 84 85 if (!todo) 86 break; 87 88 if (source.finished()) { 89 memset(buf + wr, 0, todo); 90 zx_cache_flush(buf + wr, todo, ZX_CACHE_FLUSH_DATA); 91 92 wr += todo; 93 } else { 94 uint32_t done; 95 res = source.GetFrames(buf + wr, fbl::min(space, rb_sz_ - wr), &done); 96 if (res != ZX_OK) { 97 printf("Error packing frames (res %d)\n", res); 98 break; 99 } 100 zx_cache_flush(buf + wr, done, ZX_CACHE_FLUSH_DATA); 101 wr += done; 102 103 if (source.finished()) { 104 playout_rd = rd; 105 playout_amt = (rb_sz_ + wr - rd) % rb_sz_; 106 107 // We have just become finished. Reset the loop counter and 108 // start over, this time filling with as much silence as we 109 // can. 110 i = 0; 111 } 112 } 113 114 if (wr < rb_sz_) 115 break; 116 117 ZX_DEBUG_ASSERT(wr == rb_sz_); 118 wr = 0; 119 } 120 121 if (res != ZX_OK) 122 break; 123 124 // If we have not started yet, do so. 125 if (!started) { 126 res = StartRingBuffer(); 127 if (res != ZX_OK) { 128 printf("Failed to start ring buffer!\n"); 129 break; 130 } 131 started = true; 132 } 133 134 res = rb_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 135 zx::time::infinite(), &sigs); 136 137 if (res != ZX_OK) { 138 printf("Failed to wait for notificiation (res %d)\n", res); 139 break; 140 } 141 142 if (sigs & ZX_CHANNEL_PEER_CLOSED) { 143 printf("Peer closed connection during playback!\n"); 144 break; 145 } 146 147 res = rb_ch_.read(0, 148 &pos_notif, sizeof(pos_notif), &bytes_read, 149 nullptr, 0, &junk); 150 if (res != ZX_OK) { 151 printf("Failed to read notification from ring buffer channel (res %d)\n", res); 152 break; 153 } 154 155 if (bytes_read != sizeof(pos_notif)) { 156 printf("Bad size when reading notification from ring buffer channel (%u != %zu)\n", 157 bytes_read, sizeof(pos_notif)); 158 res = ZX_ERR_INTERNAL; 159 break; 160 } 161 162 if (pos_notif.hdr.cmd != AUDIO_RB_POSITION_NOTIFY) { 163 printf("Unexpected command type when reading notification from ring " 164 "buffer channel (cmd %04x)\n", pos_notif.hdr.cmd); 165 res = ZX_ERR_INTERNAL; 166 break; 167 } 168 169 rd = pos_notif.ring_buffer_pos; 170 171 // rd has moved. If the source has finished and rd has moved at least 172 // the playout distance, we are finsihed. 173 if (source.finished()) { 174 uint32_t dist = (rb_sz_ + rd - playout_rd) % rb_sz_; 175 176 if (dist >= playout_amt) 177 break; 178 179 playout_amt -= dist; 180 playout_rd = rd; 181 } 182 } 183 184 if (res == ZX_OK) { 185 // We have already let the DMA engine catch up, but we still need to 186 // wait for the fifo to play out. For now, just hard code this as 187 // 30uSec. 188 // 189 // TODO: base this on the start time and the number of frames queued 190 // instead of just making a number up. 191 zx_nanosleep(zx_deadline_after(ZX_MSEC(30))); 192 } 193 194 zx_status_t stop_res = StopRingBuffer(); 195 if (res == ZX_OK) 196 res = stop_res; 197 198 return res; 199} 200 201} // namespace utils 202} // namespace audio 203