Chromium Code Reviews| 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 DLOG(ERROR) << "Data size " << data.size() << " is too short."; | |
|
wzhong
2015/11/18 22:21:35
Why DLOG?
slan
2015/11/19 00:19:44
Done.
| |
| 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 uint32_t total_length = std::min(ReadInt<uint32_t>(wav_data, 4) + 8, | |
| 105 static_cast<uint32_t>(wav_data.size())); | |
| 106 | |
| 107 uint32_t offset = kWavFileHeaderSize; | |
| 108 while (offset < total_length) { | |
| 109 // This is just junk left at the end. Break. | |
| 110 if (total_length - offset < kChunkHeaderSize) | |
| 111 break; | |
| 112 | |
| 113 // We should be at the beginning of a subsection. The next 8 bytes should | |
| 114 // look like: "|f|m|t| |1|2|3|4|" or "|d|a|t|a|1|2|3|4|". | |
| 115 base::StringPiece chunk_header = wav_data.substr(offset, kChunkHeaderSize); | |
| 116 uint32_t chunk_length = ReadInt<uint32_t>(chunk_header, 4); | |
| 117 base::StringPiece chunk_payload = | |
| 118 wav_data.substr(offset + kChunkHeaderSize, chunk_length); | |
| 119 | |
| 120 if (chunk_header.starts_with(kFmtSubchunkId)) { | |
| 121 if (!ParseFmtChunk(chunk_payload, params_out)) | |
| 122 return false; | |
| 123 } else if (chunk_header.starts_with(kDataSubchunkId)) { | |
| 124 *data_out = chunk_payload; | |
| 125 } else { | |
| 126 DVLOG(1) << "Skipping unknown data chunk: " << chunk_header.substr(0, 4) | |
| 127 << "."; | |
| 128 } | |
| 129 | |
| 130 offset += kChunkHeaderSize + chunk_length; | |
| 131 } | |
| 132 | |
| 133 // Check that data format is valid. | |
| 134 if (!ParamsAreValid(*params_out)) { | |
| 135 LOG(ERROR) << "Format is invalid. " | |
| 136 << "num_channels: " << params_out->num_channels << " " | |
| 137 << "sample_rate: " << params_out->sample_rate << " " | |
| 138 << "bits_per_sample: " << params_out->bits_per_sample; | |
| 139 return false; | |
| 140 } | |
| 141 return true; | |
| 142 } | |
| 143 | |
| 53 } // namespace | 144 } // namespace |
| 54 | 145 |
| 55 namespace media { | 146 WavAudioHandler::WavAudioHandler(const base::StringPiece& audio_data, |
| 56 | 147 uint16_t num_channels, |
| 57 WavAudioHandler::WavAudioHandler(const base::StringPiece& wav_data) | 148 uint32_t sample_rate, |
| 58 : num_channels_(0), sample_rate_(0), bits_per_sample_(0), total_frames_(0) { | 149 uint16_t bits_per_sample) |
| 59 CHECK_LE(kWavFileHeaderSize, wav_data.size()) << "wav data is too small"; | 150 : data_(audio_data), |
| 60 CHECK(wav_data.starts_with(kChunkId) && | 151 num_channels_(num_channels), |
| 61 memcmp(wav_data.data() + 8, kFormat, 4) == 0) | 152 sample_rate_(sample_rate), |
| 62 << "incorrect wav header"; | 153 bits_per_sample_(bits_per_sample) { |
| 63 | 154 DCHECK_NE(num_channels_, 0u); |
| 64 uint32 total_length = std::min(ReadInt<uint32>(wav_data, 4), | 155 DCHECK_NE(sample_rate_, 0u); |
| 65 static_cast<uint32>(wav_data.size())); | 156 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_; | 157 total_frames_ = data_.size() * 8 / num_channels_ / bits_per_sample_; |
| 74 } | 158 } |
| 75 | 159 |
| 76 WavAudioHandler::~WavAudioHandler() {} | 160 WavAudioHandler::~WavAudioHandler() {} |
| 77 | 161 |
| 162 // static | |
| 163 scoped_ptr<WavAudioHandler> WavAudioHandler::Create( | |
| 164 const base::StringPiece& wav_data) { | |
| 165 WavAudioParameters params; | |
| 166 base::StringPiece audio_data; | |
| 167 | |
| 168 // Attempt to parse the WAV data. | |
| 169 if (!ParseWavData(wav_data, &audio_data, ¶ms)) | |
| 170 return scoped_ptr<WavAudioHandler>(); | |
| 171 | |
| 172 return make_scoped_ptr(new WavAudioHandler(audio_data, | |
| 173 params.num_channels, | |
| 174 params.sample_rate, | |
| 175 params.bits_per_sample)); | |
| 176 } | |
| 177 | |
| 78 bool WavAudioHandler::AtEnd(size_t cursor) const { | 178 bool WavAudioHandler::AtEnd(size_t cursor) const { |
| 79 return data_.size() <= cursor; | 179 return data_.size() <= cursor; |
| 80 } | 180 } |
| 81 | 181 |
| 82 bool WavAudioHandler::CopyTo(AudioBus* bus, | 182 bool WavAudioHandler::CopyTo(AudioBus* bus, |
| 83 size_t cursor, | 183 size_t cursor, |
| 84 size_t* bytes_written) const { | 184 size_t* bytes_written) const { |
| 85 if (!bus) | 185 if (!bus) |
| 86 return false; | 186 return false; |
| 87 if (bus->channels() != num_channels_) { | 187 if (bus->channels() != num_channels_) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 100 *bytes_written = frames * bytes_per_frame; | 200 *bytes_written = frames * bytes_per_frame; |
| 101 bus->ZeroFramesPartial(frames, bus->frames() - frames); | 201 bus->ZeroFramesPartial(frames, bus->frames() - frames); |
| 102 return true; | 202 return true; |
| 103 } | 203 } |
| 104 | 204 |
| 105 base::TimeDelta WavAudioHandler::GetDuration() const { | 205 base::TimeDelta WavAudioHandler::GetDuration() const { |
| 106 return base::TimeDelta::FromSecondsD(total_frames_ / | 206 return base::TimeDelta::FromSecondsD(total_frames_ / |
| 107 static_cast<double>(sample_rate_)); | 207 static_cast<double>(sample_rate_)); |
| 108 } | 208 } |
| 109 | 209 |
| 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 | 210 } // namespace media |
| OLD | NEW |