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 "wav-source.h"
6 
7 #include <fcntl.h>
8 #include <stdio.h>
9 
10 #include <zircon/assert.h>
11 #include <fbl/auto_call.h>
12 #include <fbl/algorithm.h>
13 #include <lib/fdio/io.h>
14 
Initialize(const char * filename)15 zx_status_t WAVSource::Initialize(const char* filename) {
16     zx_status_t res = WAVCommon::Initialize(filename, InitMode::SOURCE);
17     if (res != ZX_OK) return res;
18 
19     RIFFChunkHeader riff_hdr;
20     WAVHeader wav_info;
21 
22     auto cleanup = fbl::MakeAutoCall([&]() {
23             Close();
24             payload_len_ = 0;
25         });
26 
27     // Read and sanity check the top level RIFF header
28     res = Read(&riff_hdr, sizeof(riff_hdr));
29     if (res != ZX_OK) {
30         printf("Failed to read top level RIFF header!\n");
31         return res;
32     }
33     riff_hdr.FixupEndian();
34 
35     if (riff_hdr.four_cc != RIFF_FOUR_CC) {
36         printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n",
37                RIFF_FOUR_CC, riff_hdr.four_cc);
38         return ZX_ERR_INVALID_ARGS;
39     }
40 
41     // Read the WAVE header along with its required format chunk.
42     res = Read(&wav_info, sizeof(wav_info));
43     if (res != ZX_OK) {
44         printf("Failed to read top level WAVE header!\n");
45         return res;
46     }
47     wav_info.FixupEndian();
48 
49     if (wav_info.wave_four_cc != WAVE_FOUR_CC) {
50         printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n",
51                WAVE_FOUR_CC, wav_info.wave_four_cc);
52         return ZX_ERR_INVALID_ARGS;
53     }
54 
55     if (wav_info.fmt_four_cc != FMT_FOUR_CC) {
56         printf("Missing expected 'RIFF' 4CC (expected 0x%08x got 0x%08x)\n",
57                FMT_FOUR_CC, wav_info.fmt_four_cc);
58         return ZX_ERR_INVALID_ARGS;
59     }
60 
61     if (!wav_info.frame_size) {
62         printf("Bad frame size (%hu)\n", wav_info.frame_size);
63         return ZX_ERR_INVALID_ARGS;
64     }
65 
66     // Sanity check the format of the wave file.  This test app only supports a
67     // limited subset of the possible formats.
68     if (wav_info.format != FORMAT_LPCM) {
69         printf("Unsupported format (0x%08hx) must be LPCM (0x%08hx)\n",
70                 wav_info.format, FORMAT_LPCM);
71         return ZX_ERR_INVALID_ARGS;
72     }
73 
74     switch (wav_info.bits_per_sample) {
75         case 8:  audio_format_.sample_format = AUDIO_SAMPLE_FORMAT_8BIT; break;
76         case 16: audio_format_.sample_format = AUDIO_SAMPLE_FORMAT_16BIT; break;
77         default:
78             printf("Unsupported bits per sample (%hu)\n", wav_info.bits_per_sample);
79             return ZX_ERR_INVALID_ARGS;
80     };
81 
82     audio_format_.frame_rate = wav_info.frame_rate;
83     audio_format_.channels   = wav_info.channel_count;
84 
85     // Skip any extra data in the format chunk
86     size_t total_wav_hdr_size = wav_info.fmt_chunk_len + offsetof(WAVHeader, format);
87     if (total_wav_hdr_size < sizeof(WAVHeader)) {
88         printf("Bad format chunk length in WAV header (%u)\n", wav_info.fmt_chunk_len);
89         return ZX_ERR_INVALID_ARGS;
90     }
91 
92     if (total_wav_hdr_size > sizeof(WAVHeader)) {
93         off_t delta = total_wav_hdr_size - sizeof(WAVHeader);
94         if (::lseek(fd_, delta, SEEK_CUR) < 0) {
95             printf("Error while attempt to skip %zu bytes of extra WAV header\n",
96                     static_cast<size_t>(delta));
97             return ZX_ERR_INVALID_ARGS;
98         }
99     }
100 
101     // Read and skip chunks until we find the data chunk.
102     RIFFChunkHeader data_hdr;
103     while (true) {
104         res = Read(&data_hdr, sizeof(data_hdr));
105         if (res != ZX_OK) {
106             printf("Failed to find DATA chunk header\n");
107             return res;
108         }
109         data_hdr.FixupEndian();
110 
111         if (data_hdr.four_cc == DATA_FOUR_CC)
112             break;
113 
114         if (::lseek(fd_, data_hdr.length, SEEK_CUR) < 0) {
115             printf("Error while attempt to skip %u bytes of 0x%08x chunk\n",
116                     data_hdr.length, data_hdr.four_cc);
117             return ZX_ERR_INVALID_ARGS;
118         }
119     }
120 
121     // If the length of the data chunk is not a multiple of the frame size, log a
122     // warning and truncate the length.
123     uint16_t leftover;
124     payload_len_ = data_hdr.length;
125     leftover     = static_cast<uint16_t>(payload_len_ % wav_info.frame_size);
126     if (leftover) {
127         printf("WARNING: Data chunk length (%u) not a multiple of frame size (%hu)\n",
128                 payload_len_, wav_info.frame_size);
129         payload_len_ -= leftover;
130     }
131 
132     cleanup.cancel();
133     return ZX_OK;
134 }
135 
GetFormat(Format * out_format)136 zx_status_t WAVSource::GetFormat(Format* out_format) {
137     if (fd_ < 0)
138         return ZX_ERR_BAD_STATE;
139 
140     *out_format = audio_format_;
141     return ZX_OK;
142 }
143 
GetFrames(void * buffer,uint32_t buf_space,uint32_t * out_packed)144 zx_status_t WAVSource::GetFrames(void* buffer, uint32_t buf_space, uint32_t* out_packed) {
145     if ((buffer == nullptr) || (out_packed == nullptr))
146         return ZX_ERR_INVALID_ARGS;
147 
148     if ((fd_ < 0) || finished())
149         return ZX_ERR_BAD_STATE;
150 
151     ZX_DEBUG_ASSERT(payload_played_ < payload_len_);
152     uint32_t todo = fbl::min(buf_space, payload_len_ - payload_played_);
153     zx_status_t res = Read(buffer, todo);
154     if (res == ZX_OK) {
155         payload_played_ += todo;
156         *out_packed = todo;
157     }
158 
159     return res;
160 }
161