Add aecdump support to audioproc_f
Originally landed here: https://codereview.webrtc.org/1409943002/ The transient suppression fix landed here: https://codereview.webrtc.org/1411423010/ TBR=mflodman Review URL: https://codereview.webrtc.org/1432843002 Cr-Commit-Position: refs/heads/master@{#10722}
This commit is contained in:
@ -13,6 +13,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "webrtc/base/checks.h"
|
#include "webrtc/base/checks.h"
|
||||||
#include "webrtc/base/safe_conversions.h"
|
#include "webrtc/base/safe_conversions.h"
|
||||||
@ -37,9 +38,17 @@ class ReadableWavFile : public ReadableWav {
|
|||||||
FILE* file_;
|
FILE* file_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string WavFile::FormatAsString() const {
|
||||||
|
std::ostringstream s;
|
||||||
|
s << "Sample rate: " << sample_rate() << " Hz, Channels: " << num_channels()
|
||||||
|
<< ", Duration: "
|
||||||
|
<< (1.f * num_samples()) / (num_channels() * sample_rate()) << " s";
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
WavReader::WavReader(const std::string& filename)
|
WavReader::WavReader(const std::string& filename)
|
||||||
: file_handle_(fopen(filename.c_str(), "rb")) {
|
: file_handle_(fopen(filename.c_str(), "rb")) {
|
||||||
RTC_CHECK(file_handle_ && "Could not open wav file for reading.");
|
RTC_CHECK(file_handle_) << "Could not open wav file for reading.";
|
||||||
|
|
||||||
ReadableWavFile readable(file_handle_);
|
ReadableWavFile readable(file_handle_);
|
||||||
WavFormat format;
|
WavFormat format;
|
||||||
@ -96,7 +105,7 @@ WavWriter::WavWriter(const std::string& filename, int sample_rate,
|
|||||||
num_channels_(num_channels),
|
num_channels_(num_channels),
|
||||||
num_samples_(0),
|
num_samples_(0),
|
||||||
file_handle_(fopen(filename.c_str(), "wb")) {
|
file_handle_(fopen(filename.c_str(), "wb")) {
|
||||||
RTC_CHECK(file_handle_ && "Could not open wav file for writing.");
|
RTC_CHECK(file_handle_) << "Could not open wav file for writing.";
|
||||||
RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat,
|
RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat,
|
||||||
kBytesPerSample, num_samples_));
|
kBytesPerSample, num_samples_));
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ class WavFile {
|
|||||||
virtual int sample_rate() const = 0;
|
virtual int sample_rate() const = 0;
|
||||||
virtual int num_channels() const = 0;
|
virtual int num_channels() const = 0;
|
||||||
virtual uint32_t num_samples() const = 0;
|
virtual uint32_t num_samples() const = 0;
|
||||||
|
|
||||||
|
// Returns a human-readable string containing the audio format.
|
||||||
|
std::string FormatAsString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simple C++ class for writing 16-bit PCM WAV files. All error handling is
|
// Simple C++ class for writing 16-bit PCM WAV files. All error handling is
|
||||||
|
@ -128,7 +128,11 @@
|
|||||||
'<(webrtc_root)/test/test.gyp:test_support',
|
'<(webrtc_root)/test/test.gyp:test_support',
|
||||||
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
|
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
|
||||||
],
|
],
|
||||||
'sources': [ 'test/audioproc_float.cc', ],
|
'sources': [
|
||||||
|
'test/audio_file_processor.cc',
|
||||||
|
'test/audio_file_processor.h',
|
||||||
|
'test/audioproc_float.cc',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'target_name': 'unpack_aecdump',
|
'target_name': 'unpack_aecdump',
|
||||||
|
177
webrtc/modules/audio_processing/test/audio_file_processor.cc
Normal file
177
webrtc/modules/audio_processing/test/audio_file_processor.cc
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "webrtc/modules/audio_processing/test/audio_file_processor.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "webrtc/base/checks.h"
|
||||||
|
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
||||||
|
|
||||||
|
using rtc::scoped_ptr;
|
||||||
|
using rtc::CheckedDivExact;
|
||||||
|
using std::vector;
|
||||||
|
using webrtc::audioproc::Event;
|
||||||
|
using webrtc::audioproc::Init;
|
||||||
|
using webrtc::audioproc::ReverseStream;
|
||||||
|
using webrtc::audioproc::Stream;
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Returns a StreamConfig corresponding to file.
|
||||||
|
StreamConfig GetStreamConfig(const WavFile& file) {
|
||||||
|
return StreamConfig(file.sample_rate(), file.num_channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a ChannelBuffer corresponding to file.
|
||||||
|
ChannelBuffer<float> GetChannelBuffer(const WavFile& file) {
|
||||||
|
return ChannelBuffer<float>(
|
||||||
|
CheckedDivExact(file.sample_rate(), AudioFileProcessor::kChunksPerSecond),
|
||||||
|
file.num_channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
WavFileProcessor::WavFileProcessor(scoped_ptr<AudioProcessing> ap,
|
||||||
|
scoped_ptr<WavReader> in_file,
|
||||||
|
scoped_ptr<WavWriter> out_file)
|
||||||
|
: ap_(ap.Pass()),
|
||||||
|
in_buf_(GetChannelBuffer(*in_file)),
|
||||||
|
out_buf_(GetChannelBuffer(*out_file)),
|
||||||
|
input_config_(GetStreamConfig(*in_file)),
|
||||||
|
output_config_(GetStreamConfig(*out_file)),
|
||||||
|
buffer_reader_(in_file.Pass()),
|
||||||
|
buffer_writer_(out_file.Pass()) {}
|
||||||
|
|
||||||
|
bool WavFileProcessor::ProcessChunk() {
|
||||||
|
if (!buffer_reader_.Read(&in_buf_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto st = ScopedTimer(mutable_proc_time());
|
||||||
|
RTC_CHECK_EQ(kNoErr,
|
||||||
|
ap_->ProcessStream(in_buf_.channels(), input_config_,
|
||||||
|
output_config_, out_buf_.channels()));
|
||||||
|
}
|
||||||
|
buffer_writer_.Write(out_buf_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AecDumpFileProcessor::AecDumpFileProcessor(scoped_ptr<AudioProcessing> ap,
|
||||||
|
FILE* dump_file,
|
||||||
|
scoped_ptr<WavWriter> out_file)
|
||||||
|
: ap_(ap.Pass()),
|
||||||
|
dump_file_(dump_file),
|
||||||
|
out_buf_(GetChannelBuffer(*out_file)),
|
||||||
|
output_config_(GetStreamConfig(*out_file)),
|
||||||
|
buffer_writer_(out_file.Pass()) {
|
||||||
|
RTC_CHECK(dump_file_) << "Could not open dump file for reading.";
|
||||||
|
}
|
||||||
|
|
||||||
|
AecDumpFileProcessor::~AecDumpFileProcessor() {
|
||||||
|
fclose(dump_file_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AecDumpFileProcessor::ProcessChunk() {
|
||||||
|
Event event_msg;
|
||||||
|
|
||||||
|
// Continue until we process our first Stream message.
|
||||||
|
do {
|
||||||
|
if (!ReadMessageFromFile(dump_file_, &event_msg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event_msg.type() == Event::INIT) {
|
||||||
|
RTC_CHECK(event_msg.has_init());
|
||||||
|
HandleMessage(event_msg.init());
|
||||||
|
|
||||||
|
} else if (event_msg.type() == Event::STREAM) {
|
||||||
|
RTC_CHECK(event_msg.has_stream());
|
||||||
|
HandleMessage(event_msg.stream());
|
||||||
|
|
||||||
|
} else if (event_msg.type() == Event::REVERSE_STREAM) {
|
||||||
|
RTC_CHECK(event_msg.has_reverse_stream());
|
||||||
|
HandleMessage(event_msg.reverse_stream());
|
||||||
|
}
|
||||||
|
} while (event_msg.type() != Event::STREAM);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AecDumpFileProcessor::HandleMessage(const Init& msg) {
|
||||||
|
RTC_CHECK(msg.has_sample_rate());
|
||||||
|
RTC_CHECK(msg.has_num_input_channels());
|
||||||
|
RTC_CHECK(msg.has_num_reverse_channels());
|
||||||
|
|
||||||
|
in_buf_.reset(new ChannelBuffer<float>(
|
||||||
|
CheckedDivExact(msg.sample_rate(), kChunksPerSecond),
|
||||||
|
msg.num_input_channels()));
|
||||||
|
const int reverse_sample_rate = msg.has_reverse_sample_rate()
|
||||||
|
? msg.reverse_sample_rate()
|
||||||
|
: msg.sample_rate();
|
||||||
|
reverse_buf_.reset(new ChannelBuffer<float>(
|
||||||
|
CheckedDivExact(reverse_sample_rate, kChunksPerSecond),
|
||||||
|
msg.num_reverse_channels()));
|
||||||
|
input_config_ = StreamConfig(msg.sample_rate(), msg.num_input_channels());
|
||||||
|
reverse_config_ =
|
||||||
|
StreamConfig(reverse_sample_rate, msg.num_reverse_channels());
|
||||||
|
|
||||||
|
const ProcessingConfig config = {
|
||||||
|
{input_config_, output_config_, reverse_config_, reverse_config_}};
|
||||||
|
RTC_CHECK_EQ(kNoErr, ap_->Initialize(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AecDumpFileProcessor::HandleMessage(const Stream& msg) {
|
||||||
|
RTC_CHECK(!msg.has_input_data());
|
||||||
|
RTC_CHECK_EQ(in_buf_->num_channels(), msg.input_channel_size());
|
||||||
|
|
||||||
|
for (int i = 0; i < msg.input_channel_size(); ++i) {
|
||||||
|
RTC_CHECK_EQ(in_buf_->num_frames() * sizeof(*in_buf_->channels()[i]),
|
||||||
|
msg.input_channel(i).size());
|
||||||
|
std::memcpy(in_buf_->channels()[i], msg.input_channel(i).data(),
|
||||||
|
msg.input_channel(i).size());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto st = ScopedTimer(mutable_proc_time());
|
||||||
|
RTC_CHECK_EQ(kNoErr, ap_->set_stream_delay_ms(msg.delay()));
|
||||||
|
ap_->echo_cancellation()->set_stream_drift_samples(msg.drift());
|
||||||
|
if (msg.has_keypress()) {
|
||||||
|
ap_->set_stream_key_pressed(msg.keypress());
|
||||||
|
}
|
||||||
|
RTC_CHECK_EQ(kNoErr,
|
||||||
|
ap_->ProcessStream(in_buf_->channels(), input_config_,
|
||||||
|
output_config_, out_buf_.channels()));
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_writer_.Write(out_buf_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AecDumpFileProcessor::HandleMessage(const ReverseStream& msg) {
|
||||||
|
RTC_CHECK(!msg.has_data());
|
||||||
|
RTC_CHECK_EQ(reverse_buf_->num_channels(), msg.channel_size());
|
||||||
|
|
||||||
|
for (int i = 0; i < msg.channel_size(); ++i) {
|
||||||
|
RTC_CHECK_EQ(reverse_buf_->num_frames() * sizeof(*in_buf_->channels()[i]),
|
||||||
|
msg.channel(i).size());
|
||||||
|
std::memcpy(reverse_buf_->channels()[i], msg.channel(i).data(),
|
||||||
|
msg.channel(i).size());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto st = ScopedTimer(mutable_proc_time());
|
||||||
|
// TODO(ajm): This currently discards the processed output, which is needed
|
||||||
|
// for e.g. intelligibility enhancement.
|
||||||
|
RTC_CHECK_EQ(kNoErr, ap_->ProcessReverseStream(
|
||||||
|
reverse_buf_->channels(), reverse_config_,
|
||||||
|
reverse_config_, reverse_buf_->channels()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
139
webrtc/modules/audio_processing/test/audio_file_processor.h
Normal file
139
webrtc/modules/audio_processing/test/audio_file_processor.h
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
||||||
|
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "webrtc/base/scoped_ptr.h"
|
||||||
|
#include "webrtc/common_audio/channel_buffer.h"
|
||||||
|
#include "webrtc/common_audio/wav_file.h"
|
||||||
|
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||||
|
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||||
|
#include "webrtc/system_wrappers/include/tick_util.h"
|
||||||
|
|
||||||
|
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
|
||||||
|
#include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h"
|
||||||
|
#else
|
||||||
|
#include "webrtc/audio_processing/debug.pb.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// Holds a few statistics about a series of TickIntervals.
|
||||||
|
struct TickIntervalStats {
|
||||||
|
TickIntervalStats() : min(std::numeric_limits<int64_t>::max()) {}
|
||||||
|
TickInterval sum;
|
||||||
|
TickInterval max;
|
||||||
|
TickInterval min;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface for processing an input file with an AudioProcessing instance and
|
||||||
|
// dumping the results to an output file.
|
||||||
|
class AudioFileProcessor {
|
||||||
|
public:
|
||||||
|
static const int kChunksPerSecond = 1000 / AudioProcessing::kChunkSizeMs;
|
||||||
|
|
||||||
|
virtual ~AudioFileProcessor() {}
|
||||||
|
|
||||||
|
// Processes one AudioProcessing::kChunkSizeMs of data from the input file and
|
||||||
|
// writes to the output file.
|
||||||
|
virtual bool ProcessChunk() = 0;
|
||||||
|
|
||||||
|
// Returns the execution time of all AudioProcessing calls.
|
||||||
|
const TickIntervalStats& proc_time() const { return proc_time_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// RAII class for execution time measurement. Updates the provided
|
||||||
|
// TickIntervalStats based on the time between ScopedTimer creation and
|
||||||
|
// leaving the enclosing scope.
|
||||||
|
class ScopedTimer {
|
||||||
|
public:
|
||||||
|
explicit ScopedTimer(TickIntervalStats* proc_time)
|
||||||
|
: proc_time_(proc_time), start_time_(TickTime::Now()) {}
|
||||||
|
|
||||||
|
~ScopedTimer() {
|
||||||
|
TickInterval interval = TickTime::Now() - start_time_;
|
||||||
|
proc_time_->sum += interval;
|
||||||
|
proc_time_->max = std::max(proc_time_->max, interval);
|
||||||
|
proc_time_->min = std::min(proc_time_->min, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TickIntervalStats* const proc_time_;
|
||||||
|
TickTime start_time_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TickIntervalStats* mutable_proc_time() { return &proc_time_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TickIntervalStats proc_time_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to read from and write to WavFile objects.
|
||||||
|
class WavFileProcessor final : public AudioFileProcessor {
|
||||||
|
public:
|
||||||
|
// Takes ownership of all parameters.
|
||||||
|
WavFileProcessor(rtc::scoped_ptr<AudioProcessing> ap,
|
||||||
|
rtc::scoped_ptr<WavReader> in_file,
|
||||||
|
rtc::scoped_ptr<WavWriter> out_file);
|
||||||
|
virtual ~WavFileProcessor() {}
|
||||||
|
|
||||||
|
// Processes one chunk from the WAV input and writes to the WAV output.
|
||||||
|
bool ProcessChunk() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtc::scoped_ptr<AudioProcessing> ap_;
|
||||||
|
|
||||||
|
ChannelBuffer<float> in_buf_;
|
||||||
|
ChannelBuffer<float> out_buf_;
|
||||||
|
const StreamConfig input_config_;
|
||||||
|
const StreamConfig output_config_;
|
||||||
|
ChannelBufferWavReader buffer_reader_;
|
||||||
|
ChannelBufferWavWriter buffer_writer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to read from an aecdump file and write to a WavWriter.
|
||||||
|
class AecDumpFileProcessor final : public AudioFileProcessor {
|
||||||
|
public:
|
||||||
|
// Takes ownership of all parameters.
|
||||||
|
AecDumpFileProcessor(rtc::scoped_ptr<AudioProcessing> ap,
|
||||||
|
FILE* dump_file,
|
||||||
|
rtc::scoped_ptr<WavWriter> out_file);
|
||||||
|
|
||||||
|
virtual ~AecDumpFileProcessor();
|
||||||
|
|
||||||
|
// Processes messages from the aecdump file until the first Stream message is
|
||||||
|
// completed. Passes other data from the aecdump messages as appropriate.
|
||||||
|
bool ProcessChunk() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleMessage(const webrtc::audioproc::Init& msg);
|
||||||
|
void HandleMessage(const webrtc::audioproc::Stream& msg);
|
||||||
|
void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
|
||||||
|
|
||||||
|
rtc::scoped_ptr<AudioProcessing> ap_;
|
||||||
|
FILE* dump_file_;
|
||||||
|
|
||||||
|
rtc::scoped_ptr<ChannelBuffer<float>> in_buf_;
|
||||||
|
rtc::scoped_ptr<ChannelBuffer<float>> reverse_buf_;
|
||||||
|
ChannelBuffer<float> out_buf_;
|
||||||
|
StreamConfig input_config_;
|
||||||
|
StreamConfig reverse_config_;
|
||||||
|
const StreamConfig output_config_;
|
||||||
|
ChannelBufferWavWriter buffer_writer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -18,26 +19,28 @@
|
|||||||
#include "webrtc/common_audio/channel_buffer.h"
|
#include "webrtc/common_audio/channel_buffer.h"
|
||||||
#include "webrtc/common_audio/wav_file.h"
|
#include "webrtc/common_audio/wav_file.h"
|
||||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||||
|
#include "webrtc/modules/audio_processing/test/audio_file_processor.h"
|
||||||
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
||||||
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||||
#include "webrtc/system_wrappers/include/tick_util.h"
|
#include "webrtc/system_wrappers/include/tick_util.h"
|
||||||
#include "webrtc/test/testsupport/trace_to_stderr.h"
|
#include "webrtc/test/testsupport/trace_to_stderr.h"
|
||||||
|
|
||||||
DEFINE_string(dump, "", "The name of the debug dump file to read from.");
|
DEFINE_string(dump, "", "Name of the aecdump debug file to read from.");
|
||||||
DEFINE_string(i, "", "The name of the input file to read from.");
|
DEFINE_string(i, "", "Name of the capture input stream file to read from.");
|
||||||
DEFINE_string(i_rev, "", "The name of the reverse input file to read from.");
|
DEFINE_string(
|
||||||
DEFINE_string(o, "out.wav", "Name of the output file to write to.");
|
o,
|
||||||
DEFINE_string(o_rev,
|
"out.wav",
|
||||||
"out_rev.wav",
|
"Name of the output file to write the processed capture stream to.");
|
||||||
"Name of the reverse output file to write to.");
|
DEFINE_int32(out_channels, 1, "Number of output channels.");
|
||||||
DEFINE_int32(out_channels, 0, "Number of output channels. Defaults to input.");
|
DEFINE_int32(out_sample_rate, 48000, "Output sample rate in Hz.");
|
||||||
DEFINE_int32(out_sample_rate, 0,
|
|
||||||
"Output sample rate in Hz. Defaults to input.");
|
|
||||||
DEFINE_string(mic_positions, "",
|
DEFINE_string(mic_positions, "",
|
||||||
"Space delimited cartesian coordinates of microphones in meters. "
|
"Space delimited cartesian coordinates of microphones in meters. "
|
||||||
"The coordinates of each point are contiguous. "
|
"The coordinates of each point are contiguous. "
|
||||||
"For a two element array: \"x1 y1 z1 x2 y2 z2\"");
|
"For a two element array: \"x1 y1 z1 x2 y2 z2\"");
|
||||||
DEFINE_double(target_angle_degrees, 90, "The azimuth of the target in radians");
|
DEFINE_double(
|
||||||
|
target_angle_degrees,
|
||||||
|
90,
|
||||||
|
"The azimuth of the target in degrees. Only applies to beamforming.");
|
||||||
|
|
||||||
DEFINE_bool(aec, false, "Enable echo cancellation.");
|
DEFINE_bool(aec, false, "Enable echo cancellation.");
|
||||||
DEFINE_bool(agc, false, "Enable automatic gain control.");
|
DEFINE_bool(agc, false, "Enable automatic gain control.");
|
||||||
@ -64,15 +67,6 @@ const char kUsage[] =
|
|||||||
"All components are disabled by default. If any bi-directional components\n"
|
"All components are disabled by default. If any bi-directional components\n"
|
||||||
"are enabled, only debug dump files are permitted.";
|
"are enabled, only debug dump files are permitted.";
|
||||||
|
|
||||||
// Returns a StreamConfig corresponding to wav_file if it's non-nullptr.
|
|
||||||
// Otherwise returns a default initialized StreamConfig.
|
|
||||||
StreamConfig MakeStreamConfig(const WavFile* wav_file) {
|
|
||||||
if (wav_file) {
|
|
||||||
return {wav_file->sample_rate(), wav_file->num_channels()};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
@ -84,48 +78,34 @@ int main(int argc, char* argv[]) {
|
|||||||
"An input file must be specified with either -i or -dump.\n");
|
"An input file must be specified with either -i or -dump.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!FLAGS_dump.empty()) {
|
if (FLAGS_dump.empty() && (FLAGS_aec || FLAGS_ie)) {
|
||||||
fprintf(stderr, "FIXME: the -dump option is not yet implemented.\n");
|
fprintf(stderr, "-aec and -ie require a -dump file.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (FLAGS_ie) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"FIXME(ajm): The intelligibility enhancer output is not dumped.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
test::TraceToStderr trace_to_stderr(true);
|
test::TraceToStderr trace_to_stderr(true);
|
||||||
WavReader in_file(FLAGS_i);
|
|
||||||
// If the output format is uninitialized, use the input format.
|
|
||||||
const int out_channels =
|
|
||||||
FLAGS_out_channels ? FLAGS_out_channels : in_file.num_channels();
|
|
||||||
const int out_sample_rate =
|
|
||||||
FLAGS_out_sample_rate ? FLAGS_out_sample_rate : in_file.sample_rate();
|
|
||||||
WavWriter out_file(FLAGS_o, out_sample_rate, out_channels);
|
|
||||||
|
|
||||||
Config config;
|
Config config;
|
||||||
config.Set<ExperimentalNs>(new ExperimentalNs(FLAGS_ts || FLAGS_all));
|
|
||||||
config.Set<Intelligibility>(new Intelligibility(FLAGS_ie || FLAGS_all));
|
|
||||||
|
|
||||||
if (FLAGS_bf || FLAGS_all) {
|
if (FLAGS_bf || FLAGS_all) {
|
||||||
const size_t num_mics = in_file.num_channels();
|
if (FLAGS_mic_positions.empty()) {
|
||||||
const std::vector<Point> array_geometry =
|
fprintf(stderr, "-mic_positions must be specified when -bf is used.\n");
|
||||||
ParseArrayGeometry(FLAGS_mic_positions, num_mics);
|
return 1;
|
||||||
RTC_CHECK_EQ(array_geometry.size(), num_mics);
|
}
|
||||||
|
|
||||||
config.Set<Beamforming>(new Beamforming(
|
config.Set<Beamforming>(new Beamforming(
|
||||||
true, array_geometry,
|
true, ParseArrayGeometry(FLAGS_mic_positions),
|
||||||
SphericalPointf(DegreesToRadians(FLAGS_target_angle_degrees), 0.f,
|
SphericalPointf(DegreesToRadians(FLAGS_target_angle_degrees), 0.f,
|
||||||
1.f)));
|
1.f)));
|
||||||
}
|
}
|
||||||
|
config.Set<ExperimentalNs>(new ExperimentalNs(FLAGS_ts || FLAGS_all));
|
||||||
|
config.Set<Intelligibility>(new Intelligibility(FLAGS_ie || FLAGS_all));
|
||||||
|
|
||||||
rtc::scoped_ptr<AudioProcessing> ap(AudioProcessing::Create(config));
|
rtc::scoped_ptr<AudioProcessing> ap(AudioProcessing::Create(config));
|
||||||
if (!FLAGS_dump.empty()) {
|
RTC_CHECK_EQ(kNoErr, ap->echo_cancellation()->Enable(FLAGS_aec || FLAGS_all));
|
||||||
RTC_CHECK_EQ(kNoErr,
|
|
||||||
ap->echo_cancellation()->Enable(FLAGS_aec || FLAGS_all));
|
|
||||||
} else if (FLAGS_aec) {
|
|
||||||
fprintf(stderr, "-aec requires a -dump file.\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
bool process_reverse = !FLAGS_i_rev.empty();
|
|
||||||
RTC_CHECK_EQ(kNoErr, ap->gain_control()->Enable(FLAGS_agc || FLAGS_all));
|
RTC_CHECK_EQ(kNoErr, ap->gain_control()->Enable(FLAGS_agc || FLAGS_all));
|
||||||
RTC_CHECK_EQ(kNoErr,
|
|
||||||
ap->gain_control()->set_mode(GainControl::kFixedDigital));
|
|
||||||
RTC_CHECK_EQ(kNoErr, ap->high_pass_filter()->Enable(FLAGS_hpf || FLAGS_all));
|
RTC_CHECK_EQ(kNoErr, ap->high_pass_filter()->Enable(FLAGS_hpf || FLAGS_all));
|
||||||
RTC_CHECK_EQ(kNoErr, ap->noise_suppression()->Enable(FLAGS_ns || FLAGS_all));
|
RTC_CHECK_EQ(kNoErr, ap->noise_suppression()->Enable(FLAGS_ns || FLAGS_all));
|
||||||
if (FLAGS_ns_level != -1) {
|
if (FLAGS_ns_level != -1) {
|
||||||
@ -135,109 +115,38 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
ap->set_stream_key_pressed(FLAGS_ts);
|
ap->set_stream_key_pressed(FLAGS_ts);
|
||||||
|
|
||||||
printf("Input file: %s\nChannels: %d, Sample rate: %d Hz\n\n",
|
rtc::scoped_ptr<AudioFileProcessor> processor;
|
||||||
FLAGS_i.c_str(), in_file.num_channels(), in_file.sample_rate());
|
auto out_file = rtc_make_scoped_ptr(
|
||||||
printf("Output file: %s\nChannels: %d, Sample rate: %d Hz\n\n",
|
new WavWriter(FLAGS_o, FLAGS_out_sample_rate, FLAGS_out_channels));
|
||||||
FLAGS_o.c_str(), out_file.num_channels(), out_file.sample_rate());
|
std::cout << FLAGS_o << ": " << out_file->FormatAsString() << std::endl;
|
||||||
|
if (FLAGS_dump.empty()) {
|
||||||
|
auto in_file = rtc_make_scoped_ptr(new WavReader(FLAGS_i));
|
||||||
|
std::cout << FLAGS_i << ": " << in_file->FormatAsString() << std::endl;
|
||||||
|
processor.reset(
|
||||||
|
new WavFileProcessor(ap.Pass(), in_file.Pass(), out_file.Pass()));
|
||||||
|
|
||||||
ChannelBuffer<float> in_buf(
|
} else {
|
||||||
rtc::CheckedDivExact(in_file.sample_rate(), kChunksPerSecond),
|
processor.reset(new AecDumpFileProcessor(
|
||||||
in_file.num_channels());
|
ap.Pass(), fopen(FLAGS_dump.c_str(), "rb"), out_file.Pass()));
|
||||||
ChannelBuffer<float> out_buf(
|
|
||||||
rtc::CheckedDivExact(out_file.sample_rate(), kChunksPerSecond),
|
|
||||||
out_file.num_channels());
|
|
||||||
|
|
||||||
std::vector<float> in_interleaved(in_buf.size());
|
|
||||||
std::vector<float> out_interleaved(out_buf.size());
|
|
||||||
|
|
||||||
rtc::scoped_ptr<WavReader> in_rev_file;
|
|
||||||
rtc::scoped_ptr<WavWriter> out_rev_file;
|
|
||||||
rtc::scoped_ptr<ChannelBuffer<float>> in_rev_buf;
|
|
||||||
rtc::scoped_ptr<ChannelBuffer<float>> out_rev_buf;
|
|
||||||
std::vector<float> in_rev_interleaved;
|
|
||||||
std::vector<float> out_rev_interleaved;
|
|
||||||
if (process_reverse) {
|
|
||||||
in_rev_file.reset(new WavReader(FLAGS_i_rev));
|
|
||||||
out_rev_file.reset(new WavWriter(FLAGS_o_rev, in_rev_file->sample_rate(),
|
|
||||||
in_rev_file->num_channels()));
|
|
||||||
printf("In rev file: %s\nChannels: %d, Sample rate: %d Hz\n\n",
|
|
||||||
FLAGS_i_rev.c_str(), in_rev_file->num_channels(),
|
|
||||||
in_rev_file->sample_rate());
|
|
||||||
printf("Out rev file: %s\nChannels: %d, Sample rate: %d Hz\n\n",
|
|
||||||
FLAGS_o_rev.c_str(), out_rev_file->num_channels(),
|
|
||||||
out_rev_file->sample_rate());
|
|
||||||
in_rev_buf.reset(new ChannelBuffer<float>(
|
|
||||||
rtc::CheckedDivExact(in_rev_file->sample_rate(), kChunksPerSecond),
|
|
||||||
in_rev_file->num_channels()));
|
|
||||||
in_rev_interleaved.resize(in_rev_buf->size());
|
|
||||||
out_rev_buf.reset(new ChannelBuffer<float>(
|
|
||||||
rtc::CheckedDivExact(out_rev_file->sample_rate(), kChunksPerSecond),
|
|
||||||
out_rev_file->num_channels()));
|
|
||||||
out_rev_interleaved.resize(out_rev_buf->size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TickTime processing_start_time;
|
|
||||||
TickInterval accumulated_time;
|
|
||||||
int num_chunks = 0;
|
int num_chunks = 0;
|
||||||
|
while (processor->ProcessChunk()) {
|
||||||
const auto input_config = MakeStreamConfig(&in_file);
|
|
||||||
const auto output_config = MakeStreamConfig(&out_file);
|
|
||||||
const auto reverse_input_config = MakeStreamConfig(in_rev_file.get());
|
|
||||||
const auto reverse_output_config = MakeStreamConfig(out_rev_file.get());
|
|
||||||
|
|
||||||
while (in_file.ReadSamples(in_interleaved.size(),
|
|
||||||
&in_interleaved[0]) == in_interleaved.size()) {
|
|
||||||
// Have logs display the file time rather than wallclock time.
|
|
||||||
trace_to_stderr.SetTimeSeconds(num_chunks * 1.f / kChunksPerSecond);
|
trace_to_stderr.SetTimeSeconds(num_chunks * 1.f / kChunksPerSecond);
|
||||||
FloatS16ToFloat(&in_interleaved[0], in_interleaved.size(),
|
++num_chunks;
|
||||||
&in_interleaved[0]);
|
|
||||||
Deinterleave(&in_interleaved[0], in_buf.num_frames(),
|
|
||||||
in_buf.num_channels(), in_buf.channels());
|
|
||||||
if (process_reverse) {
|
|
||||||
in_rev_file->ReadSamples(in_rev_interleaved.size(),
|
|
||||||
in_rev_interleaved.data());
|
|
||||||
FloatS16ToFloat(in_rev_interleaved.data(), in_rev_interleaved.size(),
|
|
||||||
in_rev_interleaved.data());
|
|
||||||
Deinterleave(in_rev_interleaved.data(), in_rev_buf->num_frames(),
|
|
||||||
in_rev_buf->num_channels(), in_rev_buf->channels());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FLAGS_perf) {
|
if (FLAGS_perf) {
|
||||||
processing_start_time = TickTime::Now();
|
const auto& proc_time = processor->proc_time();
|
||||||
}
|
int64_t exec_time_us = proc_time.sum.Microseconds();
|
||||||
RTC_CHECK_EQ(kNoErr, ap->ProcessStream(in_buf.channels(), input_config,
|
printf(
|
||||||
output_config, out_buf.channels()));
|
"\nExecution time: %.3f s, File time: %.2f s\n"
|
||||||
if (process_reverse) {
|
"Time per chunk (mean, max, min):\n%.0f us, %.0f us, %.0f us\n",
|
||||||
RTC_CHECK_EQ(kNoErr, ap->ProcessReverseStream(
|
exec_time_us * 1e-6, num_chunks * 1.f / kChunksPerSecond,
|
||||||
in_rev_buf->channels(), reverse_input_config,
|
exec_time_us * 1.f / num_chunks, 1.f * proc_time.max.Microseconds(),
|
||||||
reverse_output_config, out_rev_buf->channels()));
|
1.f * proc_time.min.Microseconds());
|
||||||
}
|
|
||||||
if (FLAGS_perf) {
|
|
||||||
accumulated_time += TickTime::Now() - processing_start_time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Interleave(out_buf.channels(), out_buf.num_frames(),
|
|
||||||
out_buf.num_channels(), &out_interleaved[0]);
|
|
||||||
FloatToFloatS16(&out_interleaved[0], out_interleaved.size(),
|
|
||||||
&out_interleaved[0]);
|
|
||||||
out_file.WriteSamples(&out_interleaved[0], out_interleaved.size());
|
|
||||||
if (process_reverse) {
|
|
||||||
Interleave(out_rev_buf->channels(), out_rev_buf->num_frames(),
|
|
||||||
out_rev_buf->num_channels(), out_rev_interleaved.data());
|
|
||||||
FloatToFloatS16(out_rev_interleaved.data(), out_rev_interleaved.size(),
|
|
||||||
out_rev_interleaved.data());
|
|
||||||
out_rev_file->WriteSamples(out_rev_interleaved.data(),
|
|
||||||
out_rev_interleaved.size());
|
|
||||||
}
|
|
||||||
num_chunks++;
|
|
||||||
}
|
|
||||||
if (FLAGS_perf) {
|
|
||||||
int64_t execution_time_ms = accumulated_time.Milliseconds();
|
|
||||||
printf("\nExecution time: %.3f s\nFile time: %.2f s\n"
|
|
||||||
"Time per chunk: %.3f ms\n",
|
|
||||||
execution_time_ms * 0.001f, num_chunks * 1.f / kChunksPerSecond,
|
|
||||||
execution_time_ms * 1.f / num_chunks);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,8 +636,8 @@ void void_main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!raw_output) {
|
if (!raw_output) {
|
||||||
// The WAV file needs to be reset every time, because it cant change
|
// The WAV file needs to be reset every time, because it can't change
|
||||||
// it's sample rate or number of channels.
|
// its sample rate or number of channels.
|
||||||
output_wav_file.reset(new WavWriter(out_filename + ".wav",
|
output_wav_file.reset(new WavWriter(out_filename + ".wav",
|
||||||
output_sample_rate,
|
output_sample_rate,
|
||||||
msg.num_output_channels()));
|
msg.num_output_channels()));
|
||||||
|
@ -31,6 +31,35 @@ void RawFile::WriteSamples(const float* samples, size_t num_samples) {
|
|||||||
fwrite(samples, sizeof(*samples), num_samples, file_handle_);
|
fwrite(samples, sizeof(*samples), num_samples, file_handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelBufferWavReader::ChannelBufferWavReader(rtc::scoped_ptr<WavReader> file)
|
||||||
|
: file_(file.Pass()) {}
|
||||||
|
|
||||||
|
bool ChannelBufferWavReader::Read(ChannelBuffer<float>* buffer) {
|
||||||
|
RTC_CHECK_EQ(file_->num_channels(), buffer->num_channels());
|
||||||
|
interleaved_.resize(buffer->size());
|
||||||
|
if (file_->ReadSamples(interleaved_.size(), &interleaved_[0]) !=
|
||||||
|
interleaved_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatS16ToFloat(&interleaved_[0], interleaved_.size(), &interleaved_[0]);
|
||||||
|
Deinterleave(&interleaved_[0], buffer->num_frames(), buffer->num_channels(),
|
||||||
|
buffer->channels());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelBufferWavWriter::ChannelBufferWavWriter(rtc::scoped_ptr<WavWriter> file)
|
||||||
|
: file_(file.Pass()) {}
|
||||||
|
|
||||||
|
void ChannelBufferWavWriter::Write(const ChannelBuffer<float>& buffer) {
|
||||||
|
RTC_CHECK_EQ(file_->num_channels(), buffer.num_channels());
|
||||||
|
interleaved_.resize(buffer.size());
|
||||||
|
Interleave(buffer.channels(), buffer.num_frames(), buffer.num_channels(),
|
||||||
|
&interleaved_[0]);
|
||||||
|
FloatToFloatS16(&interleaved_[0], interleaved_.size(), &interleaved_[0]);
|
||||||
|
file_->WriteSamples(&interleaved_[0], interleaved_.size());
|
||||||
|
}
|
||||||
|
|
||||||
void WriteIntData(const int16_t* data,
|
void WriteIntData(const int16_t* data,
|
||||||
size_t length,
|
size_t length,
|
||||||
WavWriter* wav_file,
|
WavWriter* wav_file,
|
||||||
@ -92,28 +121,32 @@ AudioProcessing::ChannelLayout LayoutFromChannels(int num_channels) {
|
|||||||
case 2:
|
case 2:
|
||||||
return AudioProcessing::kStereo;
|
return AudioProcessing::kStereo;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
RTC_CHECK(false);
|
||||||
return AudioProcessing::kMono;
|
return AudioProcessing::kMono;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions,
|
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions) {
|
||||||
size_t num_mics) {
|
|
||||||
const std::vector<float> values = ParseList<float>(mic_positions);
|
const std::vector<float> values = ParseList<float>(mic_positions);
|
||||||
RTC_CHECK_EQ(values.size(), 3 * num_mics)
|
const size_t num_mics =
|
||||||
<< "Could not parse mic_positions or incorrect number of points.";
|
rtc::CheckedDivExact(values.size(), static_cast<size_t>(3));
|
||||||
|
RTC_CHECK_GT(num_mics, 0u) << "mic_positions is not large enough.";
|
||||||
|
|
||||||
std::vector<Point> result;
|
std::vector<Point> result;
|
||||||
result.reserve(num_mics);
|
result.reserve(num_mics);
|
||||||
for (size_t i = 0; i < values.size(); i += 3) {
|
for (size_t i = 0; i < values.size(); i += 3) {
|
||||||
double x = values[i + 0];
|
result.push_back(Point(values[i + 0], values[i + 1], values[i + 2]));
|
||||||
double y = values[i + 1];
|
|
||||||
double z = values[i + 2];
|
|
||||||
result.push_back(Point(x, y, z));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions,
|
||||||
|
size_t num_mics) {
|
||||||
|
std::vector<Point> result = ParseArrayGeometry(mic_positions);
|
||||||
|
RTC_CHECK_EQ(result.size(), num_mics)
|
||||||
|
<< "Could not parse mic_positions or incorrect number of points.";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -43,6 +43,35 @@ class RawFile final {
|
|||||||
RTC_DISALLOW_COPY_AND_ASSIGN(RawFile);
|
RTC_DISALLOW_COPY_AND_ASSIGN(RawFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reads ChannelBuffers from a provided WavReader.
|
||||||
|
class ChannelBufferWavReader final {
|
||||||
|
public:
|
||||||
|
explicit ChannelBufferWavReader(rtc::scoped_ptr<WavReader> file);
|
||||||
|
|
||||||
|
// Reads data from the file according to the |buffer| format. Returns false if
|
||||||
|
// a full buffer can't be read from the file.
|
||||||
|
bool Read(ChannelBuffer<float>* buffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtc::scoped_ptr<WavReader> file_;
|
||||||
|
std::vector<float> interleaved_;
|
||||||
|
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavReader);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Writes ChannelBuffers to a provided WavWriter.
|
||||||
|
class ChannelBufferWavWriter final {
|
||||||
|
public:
|
||||||
|
explicit ChannelBufferWavWriter(rtc::scoped_ptr<WavWriter> file);
|
||||||
|
void Write(const ChannelBuffer<float>& buffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtc::scoped_ptr<WavWriter> file_;
|
||||||
|
std::vector<float> interleaved_;
|
||||||
|
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavWriter);
|
||||||
|
};
|
||||||
|
|
||||||
void WriteIntData(const int16_t* data,
|
void WriteIntData(const int16_t* data,
|
||||||
size_t length,
|
size_t length,
|
||||||
WavWriter* wav_file,
|
WavWriter* wav_file,
|
||||||
@ -118,6 +147,9 @@ std::vector<T> ParseList(const std::string& to_parse) {
|
|||||||
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions,
|
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions,
|
||||||
size_t num_mics);
|
size_t num_mics);
|
||||||
|
|
||||||
|
// Same as above, but without the num_mics check for when it isn't available.
|
||||||
|
std::vector<Point> ParseArrayGeometry(const std::string& mic_positions);
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
||||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_TEST_UTILS_H_
|
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_TEST_UTILS_H_
|
||||||
|
@ -85,6 +85,7 @@ class TickTime {
|
|||||||
class TickInterval {
|
class TickInterval {
|
||||||
public:
|
public:
|
||||||
TickInterval();
|
TickInterval();
|
||||||
|
explicit TickInterval(int64_t interval);
|
||||||
|
|
||||||
int64_t Milliseconds() const;
|
int64_t Milliseconds() const;
|
||||||
int64_t Microseconds() const;
|
int64_t Microseconds() const;
|
||||||
@ -105,8 +106,6 @@ class TickInterval {
|
|||||||
friend bool operator>=(const TickInterval& lhs, const TickInterval& rhs);
|
friend bool operator>=(const TickInterval& lhs, const TickInterval& rhs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit TickInterval(int64_t interval);
|
|
||||||
|
|
||||||
friend class TickTime;
|
friend class TickTime;
|
||||||
friend TickInterval operator-(const TickTime& lhs, const TickTime& rhs);
|
friend TickInterval operator-(const TickTime& lhs, const TickTime& rhs);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user