blob: 4edc13fcf8edc73c171b76ee8d5396f475aa2e8c [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/audio/audio_file_reader_wav.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "media/base/endian_util.h"
namespace cobalt {
namespace audio {
using media::endian_util::load_uint16_little_endian;
using media::endian_util::load_uint32_big_endian;
using media::endian_util::load_uint32_little_endian;
namespace {
const uint32 kWAVChunkID_RIFF = 0x52494646; // 'RIFF'
const uint32 kWAVWaveID_WAVE = 0x57415645; // 'WAVE'
const uint32 kWAVChunkID_fmt = 0x666d7420; // 'fmt '
const uint32 kWAVChunkID_data = 0x64617461; // 'data'
const uint16 kWAVFormatCodePCM = 0x0001;
const uint16 kWAVFormatCodeFloat = 0x0003;
// 4 bytes "RIFF" Chunk id, 4 bytes "RIFF" Chunk size, 4 bytes "WAVE" id,
// at least 24 bytes "fmt" Chunk and at least 8 bytes "data" Chunk.
uint32 kWAVChunkSize = 44;
uint32 kWAVRIFFChunkHeaderSize = 12;
uint32 kWAVfmtChunkHeaderSize = 16;
} // namespace
// static
scoped_ptr<AudioFileReader> AudioFileReaderWAV::TryCreate(
const uint8* data, size_t size, SampleType sample_type) {
// Need at least the |kWAVChunkSize| bytes for this to be a WAV.
if (size < kWAVChunkSize) {
return scoped_ptr<AudioFileReader>();
}
scoped_ptr<AudioFileReaderWAV> audio_file_reader_wav(
new AudioFileReaderWAV(data, size, sample_type));
if (!audio_file_reader_wav->is_valid()) {
return scoped_ptr<AudioFileReader>();
}
return make_scoped_ptr<AudioFileReader>(audio_file_reader_wav.release());
}
AudioFileReaderWAV::AudioFileReaderWAV(const uint8* data, size_t size,
SampleType sample_type)
: sample_rate_(0.f),
number_of_frames_(0),
number_of_channels_(0),
sample_type_(sample_type) {
DCHECK_GE(size, kWAVRIFFChunkHeaderSize);
if (ParseRIFFHeader(data, size)) {
ParseChunks(data, size);
}
}
bool AudioFileReaderWAV::ParseRIFFHeader(const uint8* data, size_t size) {
// First 4 bytes need to be the RIFF chunkID.
if (load_uint32_big_endian(data) != kWAVChunkID_RIFF) {
return false;
}
// Sanity-check the size. It should be large enough to hold at least the chunk
// header. Next 4 bytes need to be chunk size.
uint32 riff_chunk_size = load_uint32_little_endian(data + 4);
if (riff_chunk_size > size - 8 || riff_chunk_size < 4) {
return false;
}
// Next 4 bytes need to be the WAVE id, check for that.
if (load_uint32_big_endian(data + 8) != kWAVWaveID_WAVE) {
return false;
}
return true;
}
void AudioFileReaderWAV::ParseChunks(const uint8* data, size_t size) {
uint32 offset = kWAVRIFFChunkHeaderSize;
bool is_sample_in_float = false;
// If the WAV file is PCM format, it has two sub-chunks: first one is "fmt"
// and the second one is "data".
// TODO: support the cases that the WAV file is non-PCM format and the
// WAV file is extensible format.
for (int i = 0; i < 2; ++i) {
// Sub chunk id.
uint32 chunk_id = load_uint32_big_endian(data + offset);
offset += 4;
// Sub chunk size.
uint32 chunk_size = load_uint32_little_endian(data + offset);
offset += 4;
if (offset + chunk_size > size) {
DLOG(WARNING) << "Audio data is not enough.";
break;
}
if (chunk_id == kWAVChunkID_fmt && i == 0) {
if (!ParseWAV_fmt(data, offset, chunk_size, &is_sample_in_float)) {
DLOG(WARNING) << "Parse fmt chunk failed.";
break;
}
} else if (chunk_id == kWAVChunkID_data && i == 1) {
if (!ParseWAV_data(data, offset, chunk_size, is_sample_in_float)) {
DLOG(WARNING) << "Parse data chunk failed.";
break;
}
} else {
DLOG(WARNING) << "Malformated audio chunk.";
break;
}
offset += chunk_size;
}
}
bool AudioFileReaderWAV::ParseWAV_fmt(const uint8* data, size_t offset,
size_t size, bool* is_sample_in_float) {
// Check size for complete header.
if (size < kWAVfmtChunkHeaderSize) {
return false;
}
// AudioFormat.
uint16 format_code = load_uint16_little_endian(data + offset);
if (format_code != kWAVFormatCodeFloat && format_code != kWAVFormatCodePCM) {
DLOG(ERROR) << "Bad format on WAV, we only support uncompressed samples!"
<< "Format code: " << format_code;
return false;
}
*is_sample_in_float = format_code == kWAVFormatCodeFloat;
// Load channel count.
number_of_channels_ = load_uint16_little_endian(data + offset + 2);
// Load sample rate.
sample_rate_ =
static_cast<float>(load_uint32_little_endian(data + offset + 4));
// Skip over:
// uint32 average byterate
// uint16 block alignment
// Check sample size, we only support 32 bit floats or 16 bit PCM.
uint16 bits_per_sample = load_uint16_little_endian(data + offset + 14);
if ((*is_sample_in_float && bits_per_sample != 32) ||
(!*is_sample_in_float && bits_per_sample != 16)) {
DLOG(ERROR) << "Bad bits per sample on WAV. "
<< "Bits per sample: " << bits_per_sample;
return false;
}
return true;
}
bool AudioFileReaderWAV::ParseWAV_data(const uint8* data, size_t offset,
size_t size, bool is_sample_in_float) {
const uint8* data_samples = data + offset;
// Set number of frames based on size of data chunk.
const int32 bytes_per_src_sample =
static_cast<int32>(is_sample_in_float ? sizeof(float) : sizeof(int16));
number_of_frames_ =
static_cast<int32>(size / (bytes_per_src_sample * number_of_channels_));
const int32 bytes_per_dest_sample =
static_cast<int32>(GetSampleTypeSize(sample_type_));
const bool is_dest_float = sample_type_ == kSampleTypeFloat32;
// We store audio samples in the current platform's preferred format.
sample_data_.reset(new uint8[static_cast<size_t>(
number_of_frames_ * number_of_channels_ * bytes_per_dest_sample)]);
// Here we handle all 4 possible conversion cases. Also note that the
// source data is stored interleaved, and that need to convert it to planar.
uint8* dest_sample = sample_data_.get();
for (int32 i = 0; i < number_of_channels_; ++i) {
const uint8* src_samples = data_samples + i * bytes_per_src_sample;
for (int32 j = 0; j < number_of_frames_; ++j) {
if (is_dest_float) {
float sample;
if (is_sample_in_float) {
uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
sample = bit_cast<float>(sample_as_uint32);
} else {
uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples);
int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned);
sample = ConvertSample<int16, float>(sample_pcm);
}
reinterpret_cast<float*>(dest_sample)[i * number_of_frames_ + j] =
sample;
src_samples += bytes_per_src_sample * number_of_channels_;
} else {
int16 sample;
if (is_sample_in_float) {
uint32 sample_as_uint32 = load_uint32_little_endian(src_samples);
float value = bit_cast<float>(sample_as_uint32);
sample = ConvertSample<float, int16>(value);
} else {
sample = bit_cast<int16>(load_uint16_little_endian(src_samples));
}
reinterpret_cast<int16*>(dest_sample)[i * number_of_frames_ + j] =
sample;
src_samples += bytes_per_src_sample * number_of_channels_;
}
}
}
return true;
}
} // namespace audio
} // namespace cobalt