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* audio_data_out, |
| 88 WavAudioParameters* params_out) { |
| 89 DCHECK(audio_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 *audio_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(base::StringPiece audio_data, |
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) { |
| 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, params.num_channels, |
| 186 params.sample_rate, |
| 187 params.bits_per_sample)); |
| 188 } |
| 189 |
78 bool WavAudioHandler::AtEnd(size_t cursor) const { | 190 bool WavAudioHandler::AtEnd(size_t cursor) const { |
79 return data_.size() <= cursor; | 191 return data_.size() <= cursor; |
80 } | 192 } |
81 | 193 |
82 bool WavAudioHandler::CopyTo(AudioBus* bus, | 194 bool WavAudioHandler::CopyTo(AudioBus* bus, |
83 size_t cursor, | 195 size_t cursor, |
84 size_t* bytes_written) const { | 196 size_t* bytes_written) const { |
85 if (!bus) | 197 if (!bus) |
86 return false; | 198 return false; |
87 if (bus->channels() != num_channels_) { | 199 if (bus->channels() != num_channels_) { |
(...skipping 12 matching lines...) Expand all Loading... |
100 *bytes_written = frames * bytes_per_frame; | 212 *bytes_written = frames * bytes_per_frame; |
101 bus->ZeroFramesPartial(frames, bus->frames() - frames); | 213 bus->ZeroFramesPartial(frames, bus->frames() - frames); |
102 return true; | 214 return true; |
103 } | 215 } |
104 | 216 |
105 base::TimeDelta WavAudioHandler::GetDuration() const { | 217 base::TimeDelta WavAudioHandler::GetDuration() const { |
106 return base::TimeDelta::FromSecondsD(total_frames_ / | 218 return base::TimeDelta::FromSecondsD(total_frames_ / |
107 static_cast<double>(sample_rate_)); | 219 static_cast<double>(sample_rate_)); |
108 } | 220 } |
109 | 221 |
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 | 222 } // namespace media |
OLD | NEW |