blob: f4197a10b13e1ec67addc01bb638e846d0163ed2 [file] [log] [blame]
/*
* Copyright 2012 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 "media/audio/shell_wav_test_probe.h"
#include <string>
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/platform_file.h"
#include "media/base/endian_util.h"
#include "media/filters/shell_demuxer.h"
// don't include me in release builds please
#if !defined(__LB_SHELL__FOR_RELEASE__)
static const uint16 kWavFormatCodePCM = 0x0001;
static const uint16 kWavFormatCodeIEEEFloat = 0x0003;
// "RIFF" in ASCII (big-endian)
static const uint32 kWav_RIFF = 0x52494646;
// "WAVE" in ASCII (big-endian)
static const uint32 kWav_WAVE = 0x57415645;
// "fmt " in ASCII (big-endian)
static const uint32 kWav_fmt = 0x666d7420;
// "data" in ASCII (big-endian)
static const uint32 kWav_data = 0x64617461;
namespace media {
ShellWavTestProbe::ShellWavTestProbe()
: wav_file_(NULL),
form_wav_length_bytes_(kWavTotalHeaderLength - 8),
format_code_(0),
channels_(0),
samples_per_second_(0),
bits_per_sample_(0),
bytes_per_frame_(0),
closed_(true),
close_after_ms_(0) {}
void ShellWavTestProbe::Initialize(const char* file_name,
int channel_count,
int samples_per_second,
int bits_per_sample,
bool use_floats) {
// try to open file first
FilePath base_path;
bool path_ok = PathService::Get(base::DIR_EXE, &base_path);
DCHECK(path_ok);
base_path = base_path.Append(file_name);
wav_file_ = base::CreatePlatformFile(
base_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
NULL, NULL);
DCHECK_NE(wav_file_, base::kInvalidPlatformFileValue);
closed_ = false;
if (use_floats)
format_code_ = kWavFormatCodeIEEEFloat;
else
format_code_ = kWavFormatCodePCM;
channels_ = channel_count;
bits_per_sample_ = (uint16)bits_per_sample;
samples_per_second_ = samples_per_second;
bytes_per_frame_ = (bits_per_sample_ / 8) * channels_;
// write temporary header, it's incomplete until we know the whole length
// of the sample stream, but this will advance file pointer to start of
// audio data
WriteHeader();
}
// see: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
void ShellWavTestProbe::WriteHeader() {
// first four bytes are FORM RIFF header
endian_util::store_uint32_big_endian(kWav_RIFF, wav_header_buffer_);
// next for are length of file - FORM header, uint32 little-endian
endian_util::store_uint32_little_endian(form_wav_length_bytes_,
wav_header_buffer_ + 4);
// then WAVE header
endian_util::store_uint32_big_endian(kWav_WAVE, wav_header_buffer_ + 8);
// start common chunk with format "fmt " header
endian_util::store_uint32_big_endian(kWav_fmt, wav_header_buffer_ + 12);
// length of format chunk, uint32 little-endian
endian_util::store_uint32_little_endian(kWavFormatChunkLength - 8,
wav_header_buffer_ + 16);
// format code, uint16 little-endian
endian_util::store_uint16_little_endian(format_code_,
wav_header_buffer_ + 20);
// number of channels, uint16 little-endian
endian_util::store_uint16_little_endian(channels_, wav_header_buffer_ + 22);
// sample rate, uint32 little-endian
endian_util::store_uint32_little_endian(samples_per_second_,
wav_header_buffer_ + 24);
// average bytes per second, uint32 little-endian, derived
uint32 bytes_per_second = samples_per_second_ * bytes_per_frame_;
endian_util::store_uint32_little_endian(bytes_per_second,
wav_header_buffer_ + 28);
// "block align", reading as bytes per frame, uint16 little-endian
endian_util::store_uint16_little_endian(bytes_per_frame_,
wav_header_buffer_ + 32);
// bits per sample, uint16 little-endian
endian_util::store_uint16_little_endian(bits_per_sample_,
wav_header_buffer_ + 34);
// size of extension format chunk header, uint16 little-endian
// always 22 bytes for us so we can do > 2 channels audio
endian_util::store_uint16_little_endian(22, wav_header_buffer_ + 36);
// valid bits per sample, always same as bits per sample
endian_util::store_uint16_little_endian(bits_per_sample_,
wav_header_buffer_ + 38);
// channel mask, 4 bytes, set to all zeroes to keep default channel layout
endian_util::store_uint32_little_endian(0, wav_header_buffer_ + 40);
// subformat guid, 16 bytes, first two bytes are format code again, rest
// are a magic number 00 00 00 00 10 00 80 00 00 aa 00 38 9b 71
uint64 magic_msd = ((uint64)format_code_ << 48) | 0x0000000000001000;
endian_util::store_uint64_big_endian(magic_msd, wav_header_buffer_ + 44);
endian_util::store_uint64_big_endian(0x800000aa00389b71,
wav_header_buffer_ + 52);
// start the data chunk with "data" header
endian_util::store_uint32_big_endian(kWav_data, wav_header_buffer_ + 60);
// data chunk size is form wav length minus the rest of the header bytes
// uint32 little-endian
uint32 data_chunk_size = form_wav_length_bytes_ - (kWavTotalHeaderLength - 8);
endian_util::store_uint32_little_endian(data_chunk_size,
wav_header_buffer_ + 64);
// alright, aiff header buffer is current, now we can write it into the file
// jump to start of file
int result =
base::SeekPlatformFile(wav_file_, base::PLATFORM_FILE_FROM_BEGIN, 0);
// write buffer
result = base::WritePlatformFileAtCurrentPos(
wav_file_, reinterpret_cast<const char*>(wav_header_buffer_),
kWavTotalHeaderLength);
DCHECK_EQ(result, kWavTotalHeaderLength);
}
void ShellWavTestProbe::CloseAfter(uint64 milliseconds) {
close_after_ms_ = milliseconds;
}
void ShellWavTestProbe::AddData(const uint8* data,
uint32 length,
uint64 timestamp) {
#if defined(__LB_SHELL__BIG_ENDIAN__) || \
(defined(OS_STARBOARD) && defined(SB_IS_BIG_ENDIAN) && SB_IS_BIG_ENDIAN)
uint8* reverse_buffer = (uint8*)malloc(length);
uint16 bytes_per_sample = bits_per_sample_ / 8;
int num_words = length / bytes_per_sample;
for (int i = 0; i < num_words; i++) {
uint8* out = reverse_buffer + (i * bytes_per_sample);
if (bytes_per_sample == 2) {
endian_util::store_uint16_little_endian(((uint16*)data)[i], out);
} else if (bytes_per_sample == 4) {
endian_util::store_uint32_little_endian(((uint32*)data)[i], out);
} else {
DLOG(ERROR) << "Failed to add data";
}
}
AddDataLittleEndian(reverse_buffer, length, timestamp);
free(reverse_buffer);
#else
AddDataLittleEndian(data, length, timestamp);
#endif
}
void ShellWavTestProbe::AddDataLittleEndian(const uint8* data,
uint32 length,
uint64 timestamp) {
if (closed_)
return;
if (!length)
return;
int result = base::WritePlatformFileAtCurrentPos(
wav_file_, reinterpret_cast<const char*>(data), length);
DCHECK_EQ(result, length);
base::FlushPlatformFile(wav_file_);
// update our counters
form_wav_length_bytes_ += length;
if (close_after_ms_ > 0) {
if (timestamp == 0) {
// guess at timestamp based on total file size
timestamp = (((uint64)form_wav_length_bytes_ -
(uint64)(kWavTotalHeaderLength - 8)) *
1000ULL) /
(uint64)(samples_per_second_ * bytes_per_frame_);
}
if (timestamp > close_after_ms_) {
Close();
}
}
}
void ShellWavTestProbe::AddData(const scoped_refptr<Buffer>& buffer) {
uint64 timestamp = 0;
if (buffer->GetTimestamp() != kNoTimestamp()) {
timestamp = buffer->GetTimestamp().InMilliseconds();
}
AddData(buffer->GetData(), buffer->GetDataSize(), timestamp);
}
void ShellWavTestProbe::Close() {
if (closed_)
return;
closed_ = true;
// write the header again now that we know the lengths
WriteHeader();
// close the file
base::ClosePlatformFile(wav_file_);
wav_file_ = NULL;
}
} // namespace media
#endif // __LB_SHELL__FOR_RELEASE__