OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "media/audio/sounds/wav_audio_handler.h" | 5 #include "media/audio/sounds/wav_audio_handler.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <cstring> | 8 #include <cstring> |
9 | 9 |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/sys_byteorder.h" | 11 #include "base/sys_byteorder.h" |
12 #include "media/base/audio_bus.h" | 12 #include "media/base/audio_bus.h" |
13 | 13 |
14 namespace media { | |
14 namespace { | 15 namespace { |
15 | 16 |
16 const char kChunkId[] = "RIFF"; | 17 const char kChunkId[] = "RIFF"; |
17 const char kFormat[] = "WAVE"; | 18 const char kFormat[] = "WAVE"; |
18 const char kSubchunk1Id[] = "fmt "; | 19 const char kFmtSubchunkId[] = "fmt "; |
19 const char kSubchunk2Id[] = "data"; | 20 const char kDataSubchunkId[] = "data"; |
20 | 21 |
21 // The size of the header of a wav file. The header consists of 'RIFF', 4 bytes | 22 // The size of the header of a wav file. The header consists of 'RIFF', 4 bytes |
22 // of total data length, and 'WAVE'. | 23 // of total data length, and 'WAVE'. |
23 const size_t kWavFileHeaderSize = 12; | 24 const size_t kWavFileHeaderSize = 12; |
24 | 25 |
25 // The size of a chunk header in wav file format. A chunk header consists of a | 26 // The size of a chunk header in wav file format. A chunk header consists of a |
26 // tag ('fmt ' or 'data') and 4 bytes of chunk length. | 27 // tag ('fmt ' or 'data') and 4 bytes of chunk length. |
27 const size_t kChunkHeaderSize = 8; | 28 const size_t kChunkHeaderSize = 8; |
28 | 29 |
29 // The minimum size of 'fmt' chunk. | 30 // The minimum size of 'fmt' chunk. |
30 const size_t kFmtChunkMinimumSize = 16; | 31 const size_t kFmtChunkMinimumSize = 16; |
31 | 32 |
32 // The offsets of 'fmt' fields. | 33 // The offsets of 'fmt' fields. |
33 const size_t kAudioFormatOffset = 0; | 34 const size_t kAudioFormatOffset = 0; |
34 const size_t kChannelOffset = 2; | 35 const size_t kChannelOffset = 2; |
35 const size_t kSampleRateOffset = 4; | 36 const size_t kSampleRateOffset = 4; |
36 const size_t kBitsPerSampleOffset = 14; | 37 const size_t kBitsPerSampleOffset = 14; |
37 | 38 |
38 // Some constants for audio format. | 39 // Some constants for audio format. |
39 const int kAudioFormatPCM = 1; | 40 const int kAudioFormatPCM = 1; |
40 | 41 |
42 // A convenience struct for passing WAV parameters around. AudioParameters is | |
43 // too heavyweight for this. Keep this class internal to this implementation. | |
44 struct WavAudioParameters { | |
45 int audio_format; | |
46 uint16_t num_channels; | |
47 uint32_t sample_rate; | |
48 uint16_t bits_per_sample; | |
49 }; | |
50 | |
51 bool ParamsAreValid(const WavAudioParameters& params) { | |
52 return (params.audio_format == kAudioFormatPCM && params.num_channels != 0u && | |
53 params.sample_rate != 0u && params.bits_per_sample != 0u); | |
54 } | |
55 | |
41 // Reads an integer from |data| with |offset|. | 56 // Reads an integer from |data| with |offset|. |
42 template <typename T> | 57 template <typename T> |
43 T ReadInt(const base::StringPiece& data, size_t offset) { | 58 T ReadInt(const base::StringPiece& data, size_t offset) { |
44 CHECK_LE(offset + sizeof(T), data.size()); | 59 CHECK_LE(offset + sizeof(T), data.size()); |
45 T result; | 60 T result; |
46 memcpy(&result, data.data() + offset, sizeof(T)); | 61 memcpy(&result, data.data() + offset, sizeof(T)); |
47 #if !defined(ARCH_CPU_LITTLE_ENDIAN) | 62 #if !defined(ARCH_CPU_LITTLE_ENDIAN) |
48 result = base::ByteSwap(result); | 63 result = base::ByteSwap(result); |
49 #endif | 64 #endif |
50 return result; | 65 return result; |
51 } | 66 } |
52 | 67 |
68 // Parse a "fmt " chunk from wav data into its parameters. | |
69 bool ParseFmtChunk(const base::StringPiece& data, WavAudioParameters* params) { | |
70 DCHECK(params); | |
71 | |
72 // If the chunk is too small, return false. | |
73 if (data.size() < kFmtChunkMinimumSize) { | |
74 LOG(ERROR) << "Data size " << data.size() << " is too short."; | |
75 return false; | |
76 } | |
77 | |
78 // Read in serialized parameters. | |
79 params->audio_format = ReadInt<uint16>(data, kAudioFormatOffset); | |
80 params->num_channels = ReadInt<uint16>(data, kChannelOffset); | |
81 params->sample_rate = ReadInt<uint32>(data, kSampleRateOffset); | |
82 params->bits_per_sample = ReadInt<uint16>(data, kBitsPerSampleOffset); | |
83 return true; | |
84 } | |
85 | |
86 bool ParseWavData(const base::StringPiece& wav_data, | |
87 base::StringPiece* data_out, | |
88 WavAudioParameters* params_out) { | |
89 DCHECK(data_out); | |
90 DCHECK(params_out); | |
91 | |
92 // The data is not long enough to contain a header. | |
93 if (wav_data.size() < kWavFileHeaderSize) { | |
94 LOG(ERROR) << "wav_data is too small"; | |
95 return false; | |
96 } | |
97 | |
98 // The header should look like: |R|I|F|F|1|2|3|4|W|A|V|E| | |
99 if (!wav_data.starts_with(kChunkId) || | |
100 memcmp(wav_data.data() + 8, kFormat, 4) != 0) { | |
101 LOG(ERROR) << "incorrect wav header"; | |
102 return false; | |
103 } | |
104 | |
105 // Get the total length of the data. This number should reflect the total | |
106 // number of valid bytes in |wav_data|. Read this from the header and add | |
107 // 8 (4 for "RIFF" and 4 for the size itself), and if that is too big, use | |
108 // the length of |wav_data|. We will attempt to parse the data. | |
109 uint32_t total_length = std::min(ReadInt<uint32_t>(wav_data, 4) + 8, | |
110 static_cast<uint32_t>(wav_data.size())); | |
111 | |
112 uint32_t offset = kWavFileHeaderSize; | |
113 bool got_format = false; | |
114 while (offset < total_length) { | |
115 // This is just junk left at the end. Break. | |
116 if (total_length - offset < kChunkHeaderSize) | |
117 break; | |
118 | |
119 // We should be at the beginning of a subsection. The next 8 bytes are the | |
120 // header and should look like: "|f|m|t| |1|2|3|4|" or "|d|a|t|a|1|2|3|4|". | |
121 // Get the |chunk_header| and the |chunk_payload that follows. | |
122 base::StringPiece chunk_header = wav_data.substr(offset, kChunkHeaderSize); | |
123 uint32_t chunk_length = ReadInt<uint32_t>(chunk_header, 4); | |
124 base::StringPiece chunk_payload = | |
125 wav_data.substr(offset + kChunkHeaderSize, chunk_length); | |
126 | |
127 // Parse the subsection header, handling it if it is a "data" or "fmt " | |
128 // chunk. Skip it otherwise. | |
129 if (chunk_header.starts_with(kFmtSubchunkId)) { | |
130 got_format = true; | |
131 if (!ParseFmtChunk(chunk_payload, params_out)) | |
132 return false; | |
133 } else if (chunk_header.starts_with(kDataSubchunkId)) { | |
134 *data_out = chunk_payload; | |
135 } else { | |
136 DVLOG(1) << "Skipping unknown data chunk: " << chunk_header.substr(0, 4) | |
137 << "."; | |
138 } | |
139 | |
140 offset += kChunkHeaderSize + chunk_length; | |
141 } | |
142 | |
143 // Check that data format has been read in and is valid. | |
144 if (!got_format) { | |
145 LOG(ERROR) << "Invalid: No \"" << kFmtSubchunkId << "\" header found!"; | |
146 return false; | |
147 } else if (!ParamsAreValid(*params_out)) { | |
148 LOG(ERROR) << "Format is invalid. " | |
149 << "num_channels: " << params_out->num_channels << " " | |
150 << "sample_rate: " << params_out->sample_rate << " " | |
151 << "bits_per_sample: " << params_out->bits_per_sample; | |
152 return false; | |
153 } | |
154 return true; | |
155 } | |
156 | |
53 } // namespace | 157 } // namespace |
54 | 158 |
55 namespace media { | 159 WavAudioHandler::WavAudioHandler(const base::StringPiece& audio_data, |
tommi (sloooow) - chröme
2015/11/19 09:44:06
FYI - it's preferred to pas StringPiece by value.
slan
2015/11/19 18:57:16
TIL - thank you!
| |
56 | 160 uint16_t num_channels, |
57 WavAudioHandler::WavAudioHandler(const base::StringPiece& wav_data) | 161 uint32_t sample_rate, |
58 : num_channels_(0), sample_rate_(0), bits_per_sample_(0), total_frames_(0) { | 162 uint16_t bits_per_sample) |
59 CHECK_LE(kWavFileHeaderSize, wav_data.size()) << "wav data is too small"; | 163 : data_(audio_data), |
60 CHECK(wav_data.starts_with(kChunkId) && | 164 num_channels_(num_channels), |
61 memcmp(wav_data.data() + 8, kFormat, 4) == 0) | 165 sample_rate_(sample_rate), |
62 << "incorrect wav header"; | 166 bits_per_sample_(bits_per_sample) { |
63 | 167 DCHECK_NE(num_channels_, 0u); |
64 uint32 total_length = std::min(ReadInt<uint32>(wav_data, 4), | 168 DCHECK_NE(sample_rate_, 0u); |
65 static_cast<uint32>(wav_data.size())); | 169 DCHECK_NE(bits_per_sample_, 0u); |
66 uint32 offset = kWavFileHeaderSize; | |
67 while (offset < total_length) { | |
68 const int length = ParseSubChunk(wav_data.substr(offset)); | |
69 CHECK_LE(0, length) << "can't parse wav sub-chunk"; | |
70 offset += length; | |
71 } | |
72 | |
73 total_frames_ = data_.size() * 8 / num_channels_ / bits_per_sample_; | 170 total_frames_ = data_.size() * 8 / num_channels_ / bits_per_sample_; |
74 } | 171 } |
75 | 172 |
76 WavAudioHandler::~WavAudioHandler() {} | 173 WavAudioHandler::~WavAudioHandler() {} |
77 | 174 |
175 // static | |
176 scoped_ptr<WavAudioHandler> WavAudioHandler::Create( | |
177 const base::StringPiece& wav_data) { | |
tommi (sloooow) - chröme
2015/11/19 09:44:06
Since this is a new function, it would be good to
slan
2015/11/19 18:57:16
Done.
| |
178 WavAudioParameters params; | |
179 base::StringPiece audio_data; | |
180 | |
181 // Attempt to parse the WAV data. | |
182 if (!ParseWavData(wav_data, &audio_data, ¶ms)) | |
183 return scoped_ptr<WavAudioHandler>(); | |
184 | |
185 return make_scoped_ptr(new WavAudioHandler(audio_data, | |
186 params.num_channels, | |
187 params.sample_rate, | |
188 params.bits_per_sample)); | |
189 } | |
190 | |
78 bool WavAudioHandler::AtEnd(size_t cursor) const { | 191 bool WavAudioHandler::AtEnd(size_t cursor) const { |
79 return data_.size() <= cursor; | 192 return data_.size() <= cursor; |
80 } | 193 } |
81 | 194 |
82 bool WavAudioHandler::CopyTo(AudioBus* bus, | 195 bool WavAudioHandler::CopyTo(AudioBus* bus, |
83 size_t cursor, | 196 size_t cursor, |
84 size_t* bytes_written) const { | 197 size_t* bytes_written) const { |
85 if (!bus) | 198 if (!bus) |
86 return false; | 199 return false; |
87 if (bus->channels() != num_channels_) { | 200 if (bus->channels() != num_channels_) { |
(...skipping 12 matching lines...) Expand all Loading... | |
100 *bytes_written = frames * bytes_per_frame; | 213 *bytes_written = frames * bytes_per_frame; |
101 bus->ZeroFramesPartial(frames, bus->frames() - frames); | 214 bus->ZeroFramesPartial(frames, bus->frames() - frames); |
102 return true; | 215 return true; |
103 } | 216 } |
104 | 217 |
105 base::TimeDelta WavAudioHandler::GetDuration() const { | 218 base::TimeDelta WavAudioHandler::GetDuration() const { |
106 return base::TimeDelta::FromSecondsD(total_frames_ / | 219 return base::TimeDelta::FromSecondsD(total_frames_ / |
107 static_cast<double>(sample_rate_)); | 220 static_cast<double>(sample_rate_)); |
108 } | 221 } |
109 | 222 |
110 int WavAudioHandler::ParseSubChunk(const base::StringPiece& data) { | |
111 if (data.size() < kChunkHeaderSize) | |
112 return data.size(); | |
113 uint32 chunk_length = ReadInt<uint32>(data, 4); | |
114 if (data.starts_with(kSubchunk1Id)) { | |
115 if (!ParseFmtChunk(data.substr(kChunkHeaderSize, chunk_length))) | |
116 return -1; | |
117 } else if (data.starts_with(kSubchunk2Id)) { | |
118 if (!ParseDataChunk(data.substr(kChunkHeaderSize, chunk_length))) | |
119 return -1; | |
120 } else { | |
121 DVLOG(1) << "Unknown data chunk: " << data.substr(0, 4) << "."; | |
122 } | |
123 return chunk_length + kChunkHeaderSize; | |
124 } | |
125 | |
126 bool WavAudioHandler::ParseFmtChunk(const base::StringPiece& data) { | |
127 if (data.size() < kFmtChunkMinimumSize) { | |
128 DLOG(ERROR) << "Data size " << data.size() << " is too short."; | |
129 return false; | |
130 } | |
131 DCHECK_EQ(ReadInt<uint16>(data, kAudioFormatOffset), kAudioFormatPCM); | |
132 num_channels_ = ReadInt<uint16>(data, kChannelOffset); | |
133 sample_rate_ = ReadInt<uint32>(data, kSampleRateOffset); | |
134 bits_per_sample_ = ReadInt<uint16>(data, kBitsPerSampleOffset); | |
135 return true; | |
136 } | |
137 | |
138 bool WavAudioHandler::ParseDataChunk(const base::StringPiece& data) { | |
139 data_ = data; | |
140 return true; | |
141 } | |
142 | |
143 } // namespace media | 223 } // namespace media |
OLD | NEW |