blob: ac3528983d90a9efa5d39ff8d4fae9bf209300b7 [file] [log] [blame]
// 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 "wav-sink.h"
#include <lib/fdio/io.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <algorithm>
#include <limits>
#include <fbl/algorithm.h>
zx_status_t WAVSink::SetFormat(const AudioStream::Format& format) {
WAVHeader wav_hdr;
if ((fd_ < 0) || format_set_) {
return ZX_ERR_BAD_STATE;
}
if (format.channels > 8)
return ZX_ERR_INVALID_ARGS;
if (format.frame_rate == 0)
return ZX_ERR_INVALID_ARGS;
bool inv_endian = (format.sample_format & AUDIO_SAMPLE_FORMAT_FLAG_INVERT_ENDIAN) != 0;
bool unsigned_fmt = (format.sample_format & AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED) != 0;
auto noflag_format =
static_cast<audio_sample_format_t>((format.sample_format & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK));
// TODO(johngro): deal with endianness. Right now, we just assume that we
// are on a little endian system and demand that the samples given to us be
// in host-endian (aka, little).
if (inv_endian)
return ZX_ERR_NOT_SUPPORTED;
wav_hdr.wave_four_cc = WAVE_FOUR_CC;
wav_hdr.fmt_four_cc = FMT_FOUR_CC;
wav_hdr.fmt_chunk_len = sizeof(wav_hdr) - offsetof(WAVHeader, format);
wav_hdr.channel_count = format.channels;
wav_hdr.frame_rate = format.frame_rate;
// TODO(johngro): Add support for some of these unsupported formats (signed
// 8-bit, 20 or 24 bit in 32, etc...) by converting to the nearest WAV
// compatible format on the fly.
//
// Only 8 bit formats are unsigned.
if ((noflag_format == AUDIO_SAMPLE_FORMAT_8BIT) != unsigned_fmt)
return ZX_ERR_NOT_SUPPORTED;
wav_hdr.format = FORMAT_LPCM;
switch (noflag_format) {
// 8-bit WAV PCM is unsigned.
case AUDIO_SAMPLE_FORMAT_8BIT:
wav_hdr.bits_per_sample = 8;
break;
case AUDIO_SAMPLE_FORMAT_16BIT:
wav_hdr.bits_per_sample = 16;
break;
case AUDIO_SAMPLE_FORMAT_24BIT_PACKED:
wav_hdr.bits_per_sample = 24;
break;
case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT:
wav_hdr.format = FORMAT_IEEE_FLOAT;
__FALLTHROUGH;
case AUDIO_SAMPLE_FORMAT_20BIT_IN32:
case AUDIO_SAMPLE_FORMAT_24BIT_IN32:
case AUDIO_SAMPLE_FORMAT_32BIT:
wav_hdr.bits_per_sample = 32;
break;
case AUDIO_SAMPLE_FORMAT_20BIT_PACKED:
default:
return ZX_ERR_NOT_SUPPORTED;
}
wav_hdr.frame_size =
static_cast<uint16_t>((wav_hdr.bits_per_sample >> 3u) * wav_hdr.channel_count);
wav_hdr.average_byte_rate = static_cast<uint32_t>(wav_hdr.frame_size) * wav_hdr.frame_rate;
zx_status_t res;
RIFFChunkHeader riff_chunk;
// Note: we don't know the length of our RIFF chunk or our DATA chunk yet;
// we will come back and fill these out during finalize, but (for the time
// being) we attempt to get as close as possible to correct.
riff_chunk.four_cc = RIFF_FOUR_CC;
riff_chunk.length = sizeof(RIFFChunkHeader) + sizeof(WAVHeader);
riff_chunk.FixupEndian();
res = Write(&riff_chunk, sizeof(riff_chunk));
if (res != ZX_OK) {
printf("Failed to write top level RIFF header (res = %d)\n", res);
return res;
}
wav_hdr.FixupEndian();
res = Write(&wav_hdr, sizeof(wav_hdr));
if (res != ZX_OK) {
printf("Failed to write WAVE header (res = %d)\n", res);
return res;
}
riff_chunk.four_cc = DATA_FOUR_CC;
riff_chunk.length = 0;
riff_chunk.FixupEndian();
res = Write(&riff_chunk, sizeof(riff_chunk));
if (res != ZX_OK) {
printf("Failed to write DATA header (res = %d)\n", res);
return res;
}
format_set_ = true;
return ZX_OK;
}
zx_status_t WAVSink::PutFrames(const void* buffer, uint32_t amt) {
ZX_DEBUG_ASSERT(buffer != nullptr);
if ((fd_ < 0) || !format_set_) {
return ZX_ERR_BAD_STATE;
}
zx_status_t res = Write(buffer, amt);
if (res != ZX_OK) {
printf("Error writing %u bytes to WAV output (res %d)\n", amt, res);
return res;
}
bytes_written_ += amt;
return res;
}
zx_status_t WAVSink::Finalize() {
if ((fd_ < 0) || !format_set_) {
return ZX_ERR_BAD_STATE;
}
constexpr size_t riff_overhead = sizeof(RIFFChunkHeader) + sizeof(WAVHeader);
auto riff_size =
std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), bytes_written_ + riff_overhead);
auto data_size = std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), bytes_written_);
zx_status_t res;
RIFFChunkHeader riff_chunk;
res = Seek(0);
if (res != 0) {
printf("Failed to seek to RIFF header location during finalize (res = %d)\n", res);
return res;
}
riff_chunk.four_cc = RIFF_FOUR_CC;
riff_chunk.length = static_cast<uint32_t>(riff_size);
riff_chunk.FixupEndian();
res = Write(&riff_chunk, sizeof(riff_chunk));
if (res != 0) {
printf("Failed finalize top level RIFF header (res = %d)\n", res);
return res;
}
res = Seek(riff_overhead);
if (res != 0) {
printf("Failed to seek to DATA header location during finalize (res = %d)\n", res);
return res;
}
riff_chunk.four_cc = DATA_FOUR_CC;
riff_chunk.length = static_cast<uint32_t>(data_size);
riff_chunk.FixupEndian();
res = Write(&riff_chunk, sizeof(riff_chunk));
if (res != 0) {
printf("Failed finalize DATA header (res = %d)\n", res);
return res;
}
Close();
format_set_ = false;
bytes_written_ = 0;
return ZX_OK;
}