| // 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-source.h" |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| |
| #include <zircon/assert.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/algorithm.h> |
| #include <lib/fdio/io.h> |
| |
| zx_status_t WAVSource::Initialize(const char* filename) { |
| zx_status_t res = WAVCommon::Initialize(filename, InitMode::SOURCE); |
| if (res != ZX_OK) return res; |
| |
| RIFFChunkHeader riff_hdr; |
| WAVHeader wav_info; |
| |
| auto cleanup = fbl::MakeAutoCall([&]() { |
| Close(); |
| payload_len_ = 0; |
| }); |
| |
| // Read and sanity check the top level RIFF header |
| res = Read(&riff_hdr, sizeof(riff_hdr)); |
| if (res != ZX_OK) { |
| printf("Failed to read top level RIFF header!\n"); |
| return res; |
| } |
| riff_hdr.FixupEndian(); |
| |
| if (riff_hdr.four_cc != RIFF_FOUR_CC) { |
| printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n", |
| RIFF_FOUR_CC, riff_hdr.four_cc); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Read the WAVE header along with its required format chunk. |
| res = Read(&wav_info, sizeof(wav_info)); |
| if (res != ZX_OK) { |
| printf("Failed to read top level WAVE header!\n"); |
| return res; |
| } |
| wav_info.FixupEndian(); |
| |
| if (wav_info.wave_four_cc != WAVE_FOUR_CC) { |
| printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n", |
| WAVE_FOUR_CC, wav_info.wave_four_cc); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (wav_info.fmt_four_cc != FMT_FOUR_CC) { |
| printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n", |
| FMT_FOUR_CC, wav_info.fmt_four_cc); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!wav_info.frame_size) { |
| printf("Bad frame size (%hu)\n", wav_info.frame_size); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Sanity check the format of the wave file. This test app only supports a |
| // limited subset of the possible formats. |
| if (wav_info.format != FORMAT_LPCM) { |
| printf("Unsupported format (0x%08hx) must be LPCM (0x%08hx)\n", |
| wav_info.format, FORMAT_LPCM); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| switch (wav_info.bits_per_sample) { |
| case 8: audio_format_.sample_format = AUDIO_SAMPLE_FORMAT_8BIT; break; |
| case 16: audio_format_.sample_format = AUDIO_SAMPLE_FORMAT_16BIT; break; |
| default: |
| printf("Unsupported bits per sample (%hu)\n", wav_info.bits_per_sample); |
| return ZX_ERR_INVALID_ARGS; |
| }; |
| |
| audio_format_.frame_rate = wav_info.frame_rate; |
| audio_format_.channels = wav_info.channel_count; |
| |
| // Skip any extra data in the format chunk |
| size_t total_wav_hdr_size = wav_info.fmt_chunk_len + offsetof(WAVHeader, format); |
| if (total_wav_hdr_size < sizeof(WAVHeader)) { |
| printf("Bad format chunk length in WAV header (%u)\n", wav_info.fmt_chunk_len); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (total_wav_hdr_size > sizeof(WAVHeader)) { |
| off_t delta = total_wav_hdr_size - sizeof(WAVHeader); |
| if (::lseek(fd_, delta, SEEK_CUR) < 0) { |
| printf("Error while attempt to skip %zu bytes of extra WAV header\n", |
| static_cast<size_t>(delta)); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| // Read and skip chunks until we find the data chunk. |
| RIFFChunkHeader data_hdr; |
| while (true) { |
| res = Read(&data_hdr, sizeof(data_hdr)); |
| if (res != ZX_OK) { |
| printf("Failed to find DATA chunk header\n"); |
| return res; |
| } |
| data_hdr.FixupEndian(); |
| |
| if (data_hdr.four_cc == DATA_FOUR_CC) |
| break; |
| |
| if (::lseek(fd_, data_hdr.length, SEEK_CUR) < 0) { |
| printf("Error while attempt to skip %u bytes of 0x%08x chunk\n", |
| data_hdr.length, data_hdr.four_cc); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| // If the length of the data chunk is not a multiple of the frame size, log a |
| // warning and truncate the length. |
| uint16_t leftover; |
| payload_len_ = data_hdr.length; |
| leftover = static_cast<uint16_t>(payload_len_ % wav_info.frame_size); |
| if (leftover) { |
| printf("WARNING: Data chunk length (%u) not a multiple of frame size (%hu)\n", |
| payload_len_, wav_info.frame_size); |
| payload_len_ -= leftover; |
| } |
| |
| cleanup.cancel(); |
| return ZX_OK; |
| } |
| |
| zx_status_t WAVSource::GetFormat(Format* out_format) { |
| if (fd_ < 0) |
| return ZX_ERR_BAD_STATE; |
| |
| *out_format = audio_format_; |
| return ZX_OK; |
| } |
| |
| zx_status_t WAVSource::GetFrames(void* buffer, uint32_t buf_space, uint32_t* out_packed) { |
| if ((buffer == nullptr) || (out_packed == nullptr)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| if ((fd_ < 0) || finished()) |
| return ZX_ERR_BAD_STATE; |
| |
| ZX_DEBUG_ASSERT(payload_played_ < payload_len_); |
| uint32_t todo = fbl::min(buf_space, payload_len_ - payload_played_); |
| zx_status_t res = Read(buffer, todo); |
| if (res == ZX_OK) { |
| payload_played_ += todo; |
| *out_packed = todo; |
| } |
| |
| return res; |
| } |