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