Chromium Code Reviews| Index: media/audio/sounds/wav_audio_handler.cc |
| diff --git a/media/audio/sounds/wav_audio_handler.cc b/media/audio/sounds/wav_audio_handler.cc |
| index c9808394aea0c12c2906b64b2e18f7c9d7769b99..2a051c50ab578db45ca8eefd6f34486ee536eb3a 100644 |
| --- a/media/audio/sounds/wav_audio_handler.cc |
| +++ b/media/audio/sounds/wav_audio_handler.cc |
| @@ -11,12 +11,13 @@ |
| #include "base/sys_byteorder.h" |
| #include "media/base/audio_bus.h" |
| +namespace media { |
| namespace { |
| const char kChunkId[] = "RIFF"; |
| const char kFormat[] = "WAVE"; |
| -const char kSubchunk1Id[] = "fmt "; |
| -const char kSubchunk2Id[] = "data"; |
| +const char kFmtSubchunkId[] = "fmt "; |
| +const char kDataSubchunkId[] = "data"; |
| // The size of the header of a wav file. The header consists of 'RIFF', 4 bytes |
| // of total data length, and 'WAVE'. |
| @@ -38,6 +39,20 @@ const size_t kBitsPerSampleOffset = 14; |
| // Some constants for audio format. |
| const int kAudioFormatPCM = 1; |
| +// A convenience struct for passing WAV parameters around. AudioParameters is |
| +// too heavyweight for this. Keep this class internal to this implementation. |
| +struct WavAudioParameters { |
| + int audio_format; |
| + uint16_t num_channels; |
| + uint32_t sample_rate; |
| + uint16_t bits_per_sample; |
| +}; |
| + |
| +bool ParamsAreValid(const WavAudioParameters& params) { |
| + return (params.audio_format == kAudioFormatPCM && params.num_channels != 0u && |
| + params.sample_rate != 0u && params.bits_per_sample != 0u); |
| +} |
| + |
| // Reads an integer from |data| with |offset|. |
| template <typename T> |
| T ReadInt(const base::StringPiece& data, size_t offset) { |
| @@ -50,31 +65,116 @@ T ReadInt(const base::StringPiece& data, size_t offset) { |
| return result; |
| } |
| -} // namespace |
| +// Parse a "fmt " chunk from wav data into its parameters. |
| +bool ParseFmtChunk(const base::StringPiece& data, WavAudioParameters* params) { |
| + DCHECK(params); |
| -namespace media { |
| + // If the chunk is too small, return false. |
| + if (data.size() < kFmtChunkMinimumSize) { |
| + 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.
|
| + return false; |
| + } |
| -WavAudioHandler::WavAudioHandler(const base::StringPiece& wav_data) |
| - : num_channels_(0), sample_rate_(0), bits_per_sample_(0), total_frames_(0) { |
| - CHECK_LE(kWavFileHeaderSize, wav_data.size()) << "wav data is too small"; |
| - CHECK(wav_data.starts_with(kChunkId) && |
| - memcmp(wav_data.data() + 8, kFormat, 4) == 0) |
| - << "incorrect wav header"; |
| + // Read in serialized parameters. |
| + params->audio_format = ReadInt<uint16>(data, kAudioFormatOffset); |
| + params->num_channels = ReadInt<uint16>(data, kChannelOffset); |
| + params->sample_rate = ReadInt<uint32>(data, kSampleRateOffset); |
| + params->bits_per_sample = ReadInt<uint16>(data, kBitsPerSampleOffset); |
| + return true; |
| +} |
| - uint32 total_length = std::min(ReadInt<uint32>(wav_data, 4), |
| - static_cast<uint32>(wav_data.size())); |
| - uint32 offset = kWavFileHeaderSize; |
| +bool ParseWavData(const base::StringPiece& wav_data, |
| + base::StringPiece* data_out, |
| + WavAudioParameters* params_out) { |
| + DCHECK(data_out); |
| + DCHECK(params_out); |
| + |
| + // The data is not long enough to contain a header. |
| + if (wav_data.size() < kWavFileHeaderSize) { |
| + LOG(ERROR) << "wav_data is too small"; |
| + return false; |
| + } |
| + |
| + // The header should look like: |R|I|F|F|1|2|3|4|W|A|V|E| |
| + if (!wav_data.starts_with(kChunkId) || |
| + memcmp(wav_data.data() + 8, kFormat, 4) != 0) { |
| + LOG(ERROR) << "incorrect wav header"; |
| + return false; |
| + } |
| + uint32_t total_length = std::min(ReadInt<uint32_t>(wav_data, 4) + 8, |
| + static_cast<uint32_t>(wav_data.size())); |
| + |
| + uint32_t offset = kWavFileHeaderSize; |
| while (offset < total_length) { |
| - const int length = ParseSubChunk(wav_data.substr(offset)); |
| - CHECK_LE(0, length) << "can't parse wav sub-chunk"; |
| - offset += length; |
| + // This is just junk left at the end. Break. |
| + if (total_length - offset < kChunkHeaderSize) |
| + break; |
| + |
| + // We should be at the beginning of a subsection. The next 8 bytes should |
| + // look like: "|f|m|t| |1|2|3|4|" or "|d|a|t|a|1|2|3|4|". |
| + base::StringPiece chunk_header = wav_data.substr(offset, kChunkHeaderSize); |
| + uint32_t chunk_length = ReadInt<uint32_t>(chunk_header, 4); |
| + base::StringPiece chunk_payload = |
| + wav_data.substr(offset + kChunkHeaderSize, chunk_length); |
| + |
| + if (chunk_header.starts_with(kFmtSubchunkId)) { |
| + if (!ParseFmtChunk(chunk_payload, params_out)) |
| + return false; |
| + } else if (chunk_header.starts_with(kDataSubchunkId)) { |
| + *data_out = chunk_payload; |
| + } else { |
| + DVLOG(1) << "Skipping unknown data chunk: " << chunk_header.substr(0, 4) |
| + << "."; |
| + } |
| + |
| + offset += kChunkHeaderSize + chunk_length; |
| + } |
| + |
| + // Check that data format is valid. |
| + if (!ParamsAreValid(*params_out)) { |
| + LOG(ERROR) << "Format is invalid. " |
| + << "num_channels: " << params_out->num_channels << " " |
| + << "sample_rate: " << params_out->sample_rate << " " |
| + << "bits_per_sample: " << params_out->bits_per_sample; |
| + return false; |
| } |
| + return true; |
| +} |
| + |
| +} // namespace |
| +WavAudioHandler::WavAudioHandler(const base::StringPiece& audio_data, |
| + uint16_t num_channels, |
| + uint32_t sample_rate, |
| + uint16_t bits_per_sample) |
| + : data_(audio_data), |
| + num_channels_(num_channels), |
| + sample_rate_(sample_rate), |
| + bits_per_sample_(bits_per_sample) { |
| + DCHECK_NE(num_channels_, 0u); |
| + DCHECK_NE(sample_rate_, 0u); |
| + DCHECK_NE(bits_per_sample_, 0u); |
| total_frames_ = data_.size() * 8 / num_channels_ / bits_per_sample_; |
| } |
| WavAudioHandler::~WavAudioHandler() {} |
| +// static |
| +scoped_ptr<WavAudioHandler> WavAudioHandler::Create( |
| + const base::StringPiece& wav_data) { |
| + WavAudioParameters params; |
| + base::StringPiece audio_data; |
| + |
| + // Attempt to parse the WAV data. |
| + if (!ParseWavData(wav_data, &audio_data, ¶ms)) |
| + return scoped_ptr<WavAudioHandler>(); |
| + |
| + return make_scoped_ptr(new WavAudioHandler(audio_data, |
| + params.num_channels, |
| + params.sample_rate, |
| + params.bits_per_sample)); |
| +} |
| + |
| bool WavAudioHandler::AtEnd(size_t cursor) const { |
| return data_.size() <= cursor; |
| } |
| @@ -107,37 +207,4 @@ base::TimeDelta WavAudioHandler::GetDuration() const { |
| static_cast<double>(sample_rate_)); |
| } |
| -int WavAudioHandler::ParseSubChunk(const base::StringPiece& data) { |
| - if (data.size() < kChunkHeaderSize) |
| - return data.size(); |
| - uint32 chunk_length = ReadInt<uint32>(data, 4); |
| - if (data.starts_with(kSubchunk1Id)) { |
| - if (!ParseFmtChunk(data.substr(kChunkHeaderSize, chunk_length))) |
| - return -1; |
| - } else if (data.starts_with(kSubchunk2Id)) { |
| - if (!ParseDataChunk(data.substr(kChunkHeaderSize, chunk_length))) |
| - return -1; |
| - } else { |
| - DVLOG(1) << "Unknown data chunk: " << data.substr(0, 4) << "."; |
| - } |
| - return chunk_length + kChunkHeaderSize; |
| -} |
| - |
| -bool WavAudioHandler::ParseFmtChunk(const base::StringPiece& data) { |
| - if (data.size() < kFmtChunkMinimumSize) { |
| - DLOG(ERROR) << "Data size " << data.size() << " is too short."; |
| - return false; |
| - } |
| - DCHECK_EQ(ReadInt<uint16>(data, kAudioFormatOffset), kAudioFormatPCM); |
| - num_channels_ = ReadInt<uint16>(data, kChannelOffset); |
| - sample_rate_ = ReadInt<uint32>(data, kSampleRateOffset); |
| - bits_per_sample_ = ReadInt<uint16>(data, kBitsPerSampleOffset); |
| - return true; |
| -} |
| - |
| -bool WavAudioHandler::ParseDataChunk(const base::StringPiece& data) { |
| - data_ = data; |
| - return true; |
| -} |
| - |
| } // namespace media |