| // 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 <fbl/algorithm.h> |
| #include <fbl/auto_call.h> |
| #include <limits> |
| #include <lib/fdio/io.h> |
| #include <stdio.h> |
| #include <zircon/assert.h> |
| |
| #include "wav-sink.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 = |
| fbl::min<uint64_t>(std::numeric_limits<uint32_t>::max(), bytes_written_ + riff_overhead); |
| auto data_size = fbl::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; |
| } |