summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhubbe@chromium.org <hubbe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 01:45:15 +0000
committerhubbe@chromium.org <hubbe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 01:45:15 +0000
commite432b1f8880d04e8186a0ce6bba691aa4f5a6672 (patch)
tree81cc58da8bcfa0f65140c9a060db8b25ec7d7149 /media
parent3b93ca6e6981c54ac6d3e3eade22ffa30186678f (diff)
downloadchromium_src-e432b1f8880d04e8186a0ce6bba691aa4f5a6672.zip
chromium_src-e432b1f8880d04e8186a0ce6bba691aa4f5a6672.tar.gz
chromium_src-e432b1f8880d04e8186a0ce6bba691aa4f5a6672.tar.bz2
Helper functions and binary for encoding timestamps into audio.
These functions and binaries will be used to test the audio mirroring pipeline. They allow to encode and decode 16 bit numbers into a short blip of audio that should be decodable even when compressed and uncompressed. Review URL: https://codereview.chromium.org/178473021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256728 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/cast/cast.gyp24
-rw-r--r--media/cast/cast_config.gyp29
-rw-r--r--media/cast/test/utility/audio_utility.cc125
-rw-r--r--media/cast/test/utility/audio_utility.h16
-rw-r--r--media/cast/test/utility/audio_utility_unittest.cc45
-rw-r--r--media/cast/test/utility/generate_timecode_audio.cc32
-rw-r--r--media/cast/test/utility/utility.gyp16
7 files changed, 267 insertions, 20 deletions
diff --git a/media/cast/cast.gyp b/media/cast/cast.gyp
index 02b991d..ef67982 100644
--- a/media/cast/cast.gyp
+++ b/media/cast/cast.gyp
@@ -8,23 +8,6 @@
'chromium_code': 1,
},
'targets': [
- {
- 'target_name': 'cast_config',
- 'type': 'static_library',
- 'include_dirs': [
- '<(DEPTH)/',
- ],
- 'dependencies': [
- '<(DEPTH)/base/base.gyp:base',
- ],
- 'sources': [
- 'cast_config.cc',
- 'cast_config.h',
- 'cast_defines.h',
- 'cast_environment.cc',
- 'cast_environment.h',
- ], # source
- },
], # targets,
'conditions': [
['include_tests==1', {
@@ -33,7 +16,7 @@
'target_name': 'cast_unittests',
'type': '<(gtest_target_type)',
'dependencies': [
- 'cast_config',
+ 'cast_config.gyp:cast_config',
'cast_receiver.gyp:cast_receiver',
'cast_sender.gyp:cast_sender',
'logging/logging.gyp:cast_log_analysis',
@@ -89,6 +72,7 @@
'test/fake_single_thread_task_runner.h',
'test/fake_video_encode_accelerator.cc',
'test/fake_video_encode_accelerator.h',
+ 'test/utility/audio_utility_unittest.cc',
'test/utility/barcode_unittest.cc',
'transport/cast_transport_sender_impl_unittest.cc',
'transport/pacing/mock_paced_packet_sender.cc',
@@ -113,7 +97,7 @@
'<(DEPTH)/',
],
'dependencies': [
- 'cast_config',
+ 'cast_config.gyp:cast_config',
'logging/logging.gyp:sender_logging',
'<(DEPTH)/ui/gfx/gfx.gyp:gfx',
'<(DEPTH)/net/net.gyp:net_test_support',
@@ -135,7 +119,7 @@
'<(DEPTH)/',
],
'dependencies': [
- 'cast_config',
+ 'cast_config.gyp:cast_config',
'<(DEPTH)/ui/gfx/gfx.gyp:gfx',
'<(DEPTH)/net/net.gyp:net_test_support',
'<(DEPTH)/media/cast/cast_receiver.gyp:*',
diff --git a/media/cast/cast_config.gyp b/media/cast/cast_config.gyp
new file mode 100644
index 0000000..21d83c9
--- /dev/null
+++ b/media/cast/cast_config.gyp
@@ -0,0 +1,29 @@
+# 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.
+
+{
+ 'variables': {
+ 'include_tests%': 1,
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'cast_config',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '<(DEPTH)/',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'cast_config.cc',
+ 'cast_config.h',
+ 'cast_defines.h',
+ 'cast_environment.cc',
+ 'cast_environment.h',
+ ], # source
+ },
+ ],
+}
diff --git a/media/cast/test/utility/audio_utility.cc b/media/cast/test/utility/audio_utility.cc
index 8477536..f8e4957 100644
--- a/media/cast/test/utility/audio_utility.cc
+++ b/media/cast/test/utility/audio_utility.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <math.h>
+
#include "media/cast/test/utility/audio_utility.h"
#include "base/time/time.h"
@@ -11,6 +13,8 @@
namespace media {
namespace cast {
+const double Pi = 3.14159265358979323846;
+
TestAudioBusFactory::TestAudioBusFactory(int num_channels,
int sample_rate,
float sine_wave_frequency,
@@ -68,5 +72,126 @@ int CountZeroCrossings(const std::vector<int16>& samples) {
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 = 50000.0;
+
+bool EncodeTimestamp(uint16 timestamp,
+ size_t sample_offset,
+ std::vector<int16>* samples) {
+ if (samples->size() < kSamplesToAnalyze) {
+ return false;
+ }
+ // gray-code the number
+ timestamp = (timestamp >> 1) ^ timestamp;
+ std::vector<double> frequencies;
+ for (int 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 < samples->size(); i++) {
+ double ret = 0.0;
+ for (size_t f = 0; f < frequencies.size(); f++) {
+ ret += sin((i + sample_offset) * Pi * 2.0 * frequencies[f] /
+ kSamplingFrequency);
+ }
+ (*samples)[i] = ret * 32766 / (kNumBits + 1);
+ }
+ 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 int16* 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 * Pi * 2 * frequency / kSamplingFrequency);
+ cos_sum += samples[i] * cos(i * Pi * 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 std::vector<int16>& samples, uint16* timestamp) {
+ for (size_t start = 0;
+ start + kSamplesToAnalyze <= samples.size();
+ start += kSamplesToAnalyze / 4) {
+ double sense = DecodeOneFrequency(&samples[start],
+ kSamplesToAnalyze,
+ kSenseFrequency);
+ if (sense < kMinSense) continue;
+ bool success = true;
+ uint16 gray_coded = 0;
+ for (int 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 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
diff --git a/media/cast/test/utility/audio_utility.h b/media/cast/test/utility/audio_utility.h
index ea90e9c..7cc9b7d 100644
--- a/media/cast/test/utility/audio_utility.h
+++ b/media/cast/test/utility/audio_utility.h
@@ -58,6 +58,22 @@ scoped_ptr<PcmAudioFrame> ToPcmAudioFrame(const AudioBus& audio_bus,
// zero.
int CountZeroCrossings(const std::vector<int16>& samples);
+// Encode |timestamp| into the samples pointed to by 'samples' in a way
+// that should be decodable even after compressing/decompressing the audio.
+// Assumes 48Khz sampling rate and needs at least 240 samples. Returns
+// false if |samples| is too small. If more than 240 samples are available,
+// then the timestamp will be repeated. |sample_offset| should contain how
+// many samples has been encoded so far, so that we can make smooth
+// transitions between encoded chunks.
+// See audio_utility.cc for details on how the encoding is done.
+bool EncodeTimestamp(uint16 timestamp,
+ size_t sample_offset,
+ std::vector<int16>* samples);
+
+// Decode a timestamp encoded with EncodeTimestamp. Returns true if a
+// timestamp was found in |samples|.
+bool DecodeTimestamp(const std::vector<int16>& samples, uint16* timestamp);
+
} // namespace cast
} // namespace media
diff --git a/media/cast/test/utility/audio_utility_unittest.cc b/media/cast/test/utility/audio_utility_unittest.cc
new file mode 100644
index 0000000..3e6ea89
--- /dev/null
+++ b/media/cast/test/utility/audio_utility_unittest.cc
@@ -0,0 +1,45 @@
+// 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 "media/base/video_frame.h"
+#include "media/cast/test/utility/audio_utility.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace cast {
+namespace test {
+namespace {
+
+TEST(AudioTimestampTest, Small) {
+ std::vector<int16> samples(480);
+ for (int32 in_timestamp = 0; in_timestamp < 65536; in_timestamp += 177) {
+ EncodeTimestamp(in_timestamp, 0, &samples);
+ uint16 out_timestamp;
+ EXPECT_TRUE(DecodeTimestamp(samples, &out_timestamp));
+ ASSERT_EQ(in_timestamp, out_timestamp);
+ }
+}
+
+TEST(AudioTimestampTest, Negative) {
+ std::vector<int16> samples(480);
+ uint16 out_timestamp;
+ EXPECT_FALSE(DecodeTimestamp(samples, &out_timestamp));
+}
+
+TEST(AudioTimestampTest, CheckPhase) {
+ std::vector<int16> samples(4800);
+ EncodeTimestamp(4711, 0, &samples);
+ while (samples.size() > 240) {
+ uint16 out_timestamp;
+ EXPECT_TRUE(DecodeTimestamp(samples, &out_timestamp));
+ ASSERT_EQ(4711, out_timestamp);
+
+ samples.erase(samples.begin(), samples.begin() + 73);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace cast
+} // namespace media
diff --git a/media/cast/test/utility/generate_timecode_audio.cc b/media/cast/test/utility/generate_timecode_audio.cc
new file mode 100644
index 0000000..27d9a32
--- /dev/null
+++ b/media/cast/test/utility/generate_timecode_audio.cc
@@ -0,0 +1,32 @@
+// 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 <stdio.h>
+#include <stdlib.h>
+#include <vector>
+
+#include "media/cast/test/utility/audio_utility.h"
+
+const size_t kSamplingFrequency = 48000;
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s <fps> <frames> >output.s16le\n", argv[0]);
+ exit(1);
+ }
+ int fps = atoi(argv[1]);
+ int frames = atoi(argv[2]);
+ std::vector<int16> samples(kSamplingFrequency / fps);
+ size_t num_samples = 0;
+ for (uint32 frame_id = 1; frame_id <= frames; frame_id++) {
+ CHECK(media::cast::EncodeTimestamp(frame_id, num_samples, &samples));
+ num_samples += samples.size();
+ for (size_t sample = 0; sample < samples.size(); sample++) {
+ putchar(samples[sample] & 0xff);
+ putchar(samples[sample] >> 8);
+ putchar(samples[sample] & 0xff);
+ putchar(samples[sample] >> 8);
+ }
+ }
+}
diff --git a/media/cast/test/utility/utility.gyp b/media/cast/test/utility/utility.gyp
index cb1c781..035ce2c 100644
--- a/media/cast/test/utility/utility.gyp
+++ b/media/cast/test/utility/utility.gyp
@@ -52,5 +52,21 @@
'<(DEPTH)/media/cast/test/utility/generate_barcode_video.cc',
],
},
+ {
+ 'target_name': 'generate_timecode_audio',
+ 'type': 'executable',
+ 'include_dirs': [
+ '<(DEPTH)/',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/media/cast/cast_config.gyp:cast_config',
+ '<(DEPTH)/media/cast/test/utility/utility.gyp:cast_test_utility',
+ '<(DEPTH)/media/cast/transport/cast_transport.gyp:cast_transport',
+ '<(DEPTH)/media/media.gyp:media',
+ ],
+ 'sources': [
+ '<(DEPTH)/media/cast/test/utility/generate_timecode_audio.cc',
+ ],
+ },
],
}