blob: 37a8df78f57e7ae9e62d18d8389cecabd344a108 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <vector>
#include "base/check_op.h"
#include "base/numerics/math_constants.h"
#include "base/time/time.h"
#include "media/base/audio_bus.h"
#include "media/cast/test/utility/audio_utility.h"
namespace media {
namespace cast {
TestAudioBusFactory::TestAudioBusFactory(int num_channels,
int sample_rate,
float sine_wave_frequency,
float volume)
: num_channels_(num_channels),
sample_rate_(sample_rate),
volume_(volume),
source_(num_channels, sine_wave_frequency, sample_rate) {
CHECK_LT(0, num_channels);
CHECK_LT(0, sample_rate);
CHECK_LE(0.0f, volume_);
CHECK_LE(volume_, 1.0f);
}
TestAudioBusFactory::~TestAudioBusFactory() = default;
std::unique_ptr<AudioBus> TestAudioBusFactory::NextAudioBus(
const base::TimeDelta& duration) {
const int num_samples = (sample_rate_ * duration).InSeconds();
std::unique_ptr<AudioBus> bus(AudioBus::Create(num_channels_, num_samples));
source_.OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get());
bus->Scale(volume_);
return bus;
}
int CountZeroCrossings(const float* samples, int length) {
// The sample values must pass beyond |kAmplitudeThreshold| on the opposite
// side of zero before a crossing will be counted.
const float kAmplitudeThreshold = 0.02f; // 2% of max amplitude.
int count = 0;
int i = 0;
float last = 0.0f;
for (; i < length && fabsf(last) < kAmplitudeThreshold; ++i)
last = samples[i];
for (; i < length; ++i) {
if (fabsf(samples[i]) >= kAmplitudeThreshold &&
(last < 0) != (samples[i] < 0)) {
++count;
last = samples[i];
}
}
return count;
}
// EncodeTimestamp stores a 16-bit number as frequencies in a sample.
// Our internal code tends to work on 10ms chunks of data, and to
// make sure the decoding always work, I wanted to make sure that the
// encoded value can be decoded from 5ms of sample data, assuming a
// sampling rate of 48Khz, this turns out to be 240 samples.
// Each bit of the timestamp is stored as a frequency, where the
// frequency is bit_number * 200 Hz. We also add a 'sense' tone to
// the output, this tone is 17 * 200 = 3400Hz, and when we decode,
// we can use this tone to make sure that we aren't decoding bogus data.
// Also, we use this tone to scale our expectations in case something
// changed changed the volume of the audio.
//
// Normally, we will encode 480 samples (10ms) of data, but when we
// read it will will scan 240 samples at a time until something that
// can be decoded is found.
//
// The intention is to use these routines to encode the frame number
// that goes with each chunk of audio, so if our frame rate is
// 30Hz, we would encode 48000/30 = 1600 samples of "1", then
// 1600 samples of "2", etc. When we decode this, it is possible
// that we get a chunk of data that is spanning two frame numbers,
// so we gray-code the numbers. Since adjacent gray-coded number
// will only differ in one bit, we should never get numbers out
// of sequence when decoding, at least not by more than one.
const double kBaseFrequency = 200;
const int kSamplingFrequency = 48000;
const size_t kNumBits = 16;
const size_t kSamplesToAnalyze = kSamplingFrequency / kBaseFrequency;
const double kSenseFrequency = kBaseFrequency * (kNumBits + 1);
const double kMinSense = 1.5;
bool EncodeTimestamp(uint16_t timestamp,
size_t sample_offset,
size_t length,
float* samples) {
if (length < kSamplesToAnalyze) {
return false;
}
// gray-code the number
timestamp = (timestamp >> 1) ^ timestamp;
std::vector<double> frequencies;
for (size_t i = 0; i < kNumBits; i++) {
if ((timestamp >> i) & 1) {
frequencies.push_back(kBaseFrequency * (i + 1));
}
}
// Carrier sense frequency
frequencies.push_back(kSenseFrequency);
for (size_t i = 0; i < length; i++) {
double mix_of_components = 0.0;
for (size_t f = 0; f < frequencies.size(); f++) {
mix_of_components += sin((i + sample_offset) * base::kPiDouble * 2.0 *
frequencies[f] / kSamplingFrequency);
}
mix_of_components /= kNumBits + 1;
DCHECK_LE(fabs(mix_of_components), 1.0);
samples[i] = mix_of_components;
}
return true;
}
namespace {
// We use a slow DCT here since this code is only used for testing.
// While an FFT would probably be faster, it wouldn't be a LOT
// faster since we only analyze 17 out of 120 frequencies.
// With an FFT we would verify that none of the higher frequencies
// contain a lot of energy, which would be useful in detecting
// bogus data.
double DecodeOneFrequency(const float* samples,
size_t length,
double frequency) {
double sin_sum = 0.0;
double cos_sum = 0.0;
for (size_t i = 0; i < length; i++) {
sin_sum += samples[i] *
sin(i * base::kPiDouble * 2 * frequency / kSamplingFrequency);
cos_sum += samples[i] *
cos(i * base::kPiDouble * 2 * frequency / kSamplingFrequency);
}
return sqrt(sin_sum * sin_sum + cos_sum * cos_sum);
}
} // namespace
// When decoding, we first check for sense frequency, then we decode
// each of the bits. Each frequency must have a strength that is similar to
// the sense frequency or to zero, or the decoding fails. If it fails, we
// move head by 60 samples and try again until we run out of samples.
bool DecodeTimestamp(const float* samples, size_t length, uint16_t* timestamp) {
for (size_t start = 0;
start + kSamplesToAnalyze <= length;
start += kSamplesToAnalyze / 4) {
double sense = DecodeOneFrequency(&samples[start],
kSamplesToAnalyze,
kSenseFrequency);
if (sense < kMinSense) continue;
bool success = true;
uint16_t gray_coded = 0;
for (size_t bit = 0; success && bit < kNumBits; bit++) {
double signal_strength = DecodeOneFrequency(
&samples[start],
kSamplesToAnalyze,
kBaseFrequency * (bit + 1));
if (signal_strength < sense / 4) {
// Zero bit, no action
} else if (signal_strength > sense * 0.75 &&
signal_strength < sense * 1.25) {
// One bit
gray_coded |= 1 << bit;
} else {
success = false;
}
}
if (success) {
// Convert from gray-coded number to binary.
uint16_t mask;
for (mask = gray_coded >> 1; mask != 0; mask = mask >> 1) {
gray_coded = gray_coded ^ mask;
}
*timestamp = gray_coded;
return true;
}
}
return false;
}
} // namespace cast
} // namespace media