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 <zircon/assert.h> 6#include <fbl/auto_call.h> 7#include <fbl/algorithm.h> 8#include <fbl/limits.h> 9#include <lib/fdio/io.h> 10#include <stdio.h> 11 12#include "wav-sink.h" 13 14zx_status_t WAVSink::SetFormat(const AudioStream::Format& format) { 15 WAVHeader wav_hdr; 16 17 if ((fd_ < 0) || format_set_) { 18 return ZX_ERR_BAD_STATE; 19 } 20 21 if (format.channels > 8) return ZX_ERR_INVALID_ARGS; 22 if (format.frame_rate == 0) return ZX_ERR_INVALID_ARGS; 23 24 bool inv_endian = (format.sample_format & AUDIO_SAMPLE_FORMAT_FLAG_INVERT_ENDIAN) != 0; 25 bool unsigned_fmt = (format.sample_format & AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED) != 0; 26 auto noflag_format = static_cast<audio_sample_format_t>( 27 (format.sample_format & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK)); 28 29 // TODO(johngro): deal with endianness. Right now, we just assume that we 30 // are on a little endian system and demand that the samples given to us be 31 // in host-endian (aka, little). 32 if (inv_endian) return ZX_ERR_NOT_SUPPORTED; 33 34 wav_hdr.wave_four_cc = WAVE_FOUR_CC; 35 wav_hdr.fmt_four_cc = FMT_FOUR_CC; 36 wav_hdr.fmt_chunk_len = sizeof(wav_hdr) - offsetof(WAVHeader, format); 37 wav_hdr.channel_count = format.channels; 38 wav_hdr.frame_rate = format.frame_rate; 39 40 // TODO(johngro): Add support for some of these unsupported formats (signed 41 // 8-bit, 20 or 24 bit in 32, etc...) by converting to the nearest WAV 42 // compatible format on the fly. 43 // 44 // Only 8 bit formats are unsigned. 45 if ((noflag_format == AUDIO_SAMPLE_FORMAT_8BIT) != unsigned_fmt) 46 return ZX_ERR_NOT_SUPPORTED; 47 48 wav_hdr.format = FORMAT_LPCM; 49 50 switch (noflag_format) { 51 // 8-bit WAV PCM is unsigned. 52 case AUDIO_SAMPLE_FORMAT_8BIT: wav_hdr.bits_per_sample = 8; break; 53 case AUDIO_SAMPLE_FORMAT_16BIT: wav_hdr.bits_per_sample = 16; break; 54 case AUDIO_SAMPLE_FORMAT_24BIT_PACKED: wav_hdr.bits_per_sample = 24; break; 55 56 case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT: 57 wav_hdr.format = FORMAT_IEEE_FLOAT; 58 __FALLTHROUGH; 59 case AUDIO_SAMPLE_FORMAT_20BIT_IN32: 60 case AUDIO_SAMPLE_FORMAT_24BIT_IN32: 61 case AUDIO_SAMPLE_FORMAT_32BIT: wav_hdr.bits_per_sample = 32; break; 62 63 case AUDIO_SAMPLE_FORMAT_20BIT_PACKED: 64 default: 65 return ZX_ERR_NOT_SUPPORTED; 66 } 67 68 wav_hdr.frame_size = static_cast<uint16_t>((wav_hdr.bits_per_sample >> 3u) * 69 wav_hdr.channel_count); 70 wav_hdr.average_byte_rate = static_cast<uint32_t>(wav_hdr.frame_size) * wav_hdr.frame_rate; 71 72 zx_status_t res; 73 RIFFChunkHeader riff_chunk; 74 75 // Note: we don't know the length of our RIFF chunk or our DATA chunk yet; 76 // we will come back and fill these out during finalize, but (for the time 77 // being) we attempt to get as close as possible to correct. 78 riff_chunk.four_cc = RIFF_FOUR_CC; 79 riff_chunk.length = sizeof(RIFFChunkHeader) + sizeof(WAVHeader); 80 riff_chunk.FixupEndian(); 81 res = Write(&riff_chunk, sizeof(riff_chunk)); 82 if (res != ZX_OK) { 83 printf("Failed to write top level RIFF header (res = %d)\n", res); 84 return res; 85 } 86 87 wav_hdr.FixupEndian(); 88 res = Write(&wav_hdr, sizeof(wav_hdr)); 89 if (res != ZX_OK) { 90 printf("Failed to write WAVE header (res = %d)\n", res); 91 return res; 92 } 93 94 riff_chunk.four_cc = DATA_FOUR_CC; 95 riff_chunk.length = 0; 96 riff_chunk.FixupEndian(); 97 res = Write(&riff_chunk, sizeof(riff_chunk)); 98 if (res != ZX_OK) { 99 printf("Failed to write DATA header (res = %d)\n", res); 100 return res; 101 } 102 103 format_set_ = true; 104 return ZX_OK; 105} 106 107zx_status_t WAVSink::PutFrames(const void* buffer, uint32_t amt) { 108 ZX_DEBUG_ASSERT(buffer != nullptr); 109 110 if ((fd_ < 0) || !format_set_) { 111 return ZX_ERR_BAD_STATE; 112 } 113 114 zx_status_t res = Write(buffer, amt); 115 if (res != ZX_OK) { 116 printf("Error writing %u bytes to WAV output (res %d)\n", amt, res); 117 return res; 118 } 119 120 bytes_written_ += amt; 121 return res; 122} 123 124zx_status_t WAVSink::Finalize() { 125 if ((fd_ < 0) || !format_set_) { 126 return ZX_ERR_BAD_STATE; 127 } 128 129 constexpr size_t riff_overhead = sizeof(RIFFChunkHeader) + sizeof(WAVHeader); 130 auto riff_size = fbl::min<uint64_t>(fbl::numeric_limits<uint32_t>::max(), 131 bytes_written_ + riff_overhead); 132 auto data_size = fbl::min<uint64_t>(fbl::numeric_limits<uint32_t>::max(), 133 bytes_written_); 134 135 zx_status_t res; 136 RIFFChunkHeader riff_chunk; 137 138 res = Seek(0); 139 if (res != 0) { 140 printf("Failed to seek to RIFF header location during finalize (res = %d)\n", res); 141 return res; 142 } 143 144 riff_chunk.four_cc = RIFF_FOUR_CC; 145 riff_chunk.length = static_cast<uint32_t>(riff_size); 146 riff_chunk.FixupEndian(); 147 res = Write(&riff_chunk, sizeof(riff_chunk)); 148 if (res != 0) { 149 printf("Failed finalize top level RIFF header (res = %d)\n", res); 150 return res; 151 } 152 153 res = Seek(riff_overhead); 154 if (res != 0) { 155 printf("Failed to seek to DATA header location during finalize (res = %d)\n", res); 156 return res; 157 } 158 159 riff_chunk.four_cc = DATA_FOUR_CC; 160 riff_chunk.length = static_cast<uint32_t>(data_size); 161 riff_chunk.FixupEndian(); 162 res = Write(&riff_chunk, sizeof(riff_chunk)); 163 if (res != 0) { 164 printf("Failed finalize DATA header (res = %d)\n", res); 165 return res; 166 } 167 168 Close(); 169 format_set_ = false; 170 bytes_written_ = 0; 171 172 return ZX_OK; 173} 174