Add Matlab plotting script generator to neteq_rtpplay

This change adds an option to have neteq_rtpplay generate a Matlab
script. When executed in Matlab, the script will generate graphs with
the timing information from the test run.

The script is generated when the flag --matlabplot is passed to
neteq_rtpplay.

The CL also adds better checking and reporting about packets discarded
in the process of finding out the initial sampling rate.

Bug: webrtc:2692, webrtc:7467
Change-Id: I805e7c83b82533142b6e74bf065506e3d60a8170
Reviewed-on: https://chromium-review.googlesource.com/541276
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#18680}
This commit is contained in:
Henrik Lundin 2017-06-20 14:48:50 +02:00 committed by Commit Bot
parent 4b15afe038
commit 0bc0ccdc43
3 changed files with 180 additions and 14 deletions

View File

@ -11,10 +11,13 @@
#include "webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
#include <algorithm>
#include <fstream>
#include <ios>
#include <iterator>
#include <limits>
#include <utility>
#include <webrtc/base/checks.h>
#include "webrtc/base/checks.h"
namespace webrtc {
namespace test {
@ -57,6 +60,8 @@ void NetEqDelayAnalyzer::AfterInsertPacket(
NetEq* neteq) {
data_.insert(
std::make_pair(packet.header.timestamp, TimingData(packet.time_ms)));
ssrcs_.insert(packet.header.ssrc);
payload_types_.insert(packet.header.payloadType);
}
void NetEqDelayAnalyzer::BeforeGetAudio(NetEq* neteq) {
@ -172,5 +177,81 @@ void NetEqDelayAnalyzer::CreateGraphs(
RTC_DCHECK_EQ(send_time_s->size(), target_delay_ms->size());
}
void NetEqDelayAnalyzer::CreateMatlabScript(
const std::string& script_name) const {
std::vector<float> send_time_s;
std::vector<float> arrival_delay_ms;
std::vector<float> corrected_arrival_delay_ms;
std::vector<rtc::Optional<float>> playout_delay_ms;
std::vector<rtc::Optional<float>> target_delay_ms;
CreateGraphs(&send_time_s, &arrival_delay_ms, &corrected_arrival_delay_ms,
&playout_delay_ms, &target_delay_ms);
// Create an output file stream to Matlab script file.
std::ofstream output(script_name);
// The iterator is used to batch-output comma-separated values from vectors.
std::ostream_iterator<float> output_iterator(output, ",");
output << "send_time_s = [ ";
std::copy(send_time_s.begin(), send_time_s.end(), output_iterator);
output << "];" << std::endl;
output << "arrival_delay_ms = [ ";
std::copy(arrival_delay_ms.begin(), arrival_delay_ms.end(), output_iterator);
output << "];" << std::endl;
output << "corrected_arrival_delay_ms = [ ";
std::copy(corrected_arrival_delay_ms.begin(),
corrected_arrival_delay_ms.end(), output_iterator);
output << "];" << std::endl;
output << "playout_delay_ms = [ ";
for (const auto& v : playout_delay_ms) {
if (!v) {
output << "nan, ";
} else {
output << *v << ", ";
}
}
output << "];" << std::endl;
output << "target_delay_ms = [ ";
for (const auto& v : target_delay_ms) {
if (!v) {
output << "nan, ";
} else {
output << *v << ", ";
}
}
output << "];" << std::endl;
output << "h=plot(send_time_s, arrival_delay_ms, "
<< "send_time_s, target_delay_ms, 'g.', "
<< "send_time_s, playout_delay_ms);" << std::endl;
output << "set(h(1),'color',0.75*[1 1 1]);" << std::endl;
output << "set(h(2),'markersize',6);" << std::endl;
output << "set(h(3),'linew',1.5);" << std::endl;
output << "ax1=axis;" << std::endl;
output << "axis tight" << std::endl;
output << "ax2=axis;" << std::endl;
output << "axis([ax2(1:3) ax1(4)])" << std::endl;
output << "xlabel('send time [s]');" << std::endl;
output << "ylabel('relative delay [ms]');" << std::endl;
if (!ssrcs_.empty()) {
auto ssrc_it = ssrcs_.cbegin();
output << "title('SSRC: 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
while (ssrc_it != ssrcs_.end()) {
output << ", 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
}
output << std::dec;
auto pt_it = payload_types_.cbegin();
output << "; Payload Types: " << *pt_it++;
while (pt_it != payload_types_.end()) {
output << ", " << *pt_it++;
}
output << "');" << std::endl;
}
}
} // namespace test
} // namespace webrtc

View File

@ -12,6 +12,8 @@
#define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_DELAY_ANALYZER_H_
#include <map>
#include <set>
#include <string>
#include <vector>
#include "webrtc/base/optional.h"
@ -41,6 +43,11 @@ class NetEqDelayAnalyzer : public test::NetEqPostInsertPacket,
std::vector<rtc::Optional<float>>* playout_delay_ms,
std::vector<rtc::Optional<float>>* target_delay_ms) const;
// Creates a matlab script with file name script_name. When executed in
// Matlab, the script will generate graphs with the same timing information
// as provided by CreateGraphs.
void CreateMatlabScript(const std::string& script_name) const;
private:
struct TimingData {
explicit TimingData(double at) : arrival_time_ms(at) {}
@ -55,6 +62,8 @@ class NetEqDelayAnalyzer : public test::NetEqPostInsertPacket,
size_t get_audio_count_ = 0;
size_t last_sync_buffer_ms_ = 0;
int last_sample_rate_hz_ = 0;
std::set<uint32_t> ssrcs_;
std::set<int> payload_types_;
};
} // namespace test

View File

@ -25,6 +25,7 @@
#include "webrtc/modules/audio_coding/neteq/include/neteq.h"
#include "webrtc/modules/audio_coding/neteq/tools/fake_decode_from_file.h"
#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_packet_source_input.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_replacement_input.h"
#include "webrtc/modules/audio_coding/neteq/tools/neteq_test.h"
@ -159,6 +160,9 @@ const bool audio_level_dummy =
DEFINE_int32(abs_send_time, 3, "Extension ID for absolute sender time");
const bool abs_send_time_dummy =
google::RegisterFlagValidator(&FLAGS_abs_send_time, &ValidateExtensionId);
DEFINE_bool(matlabplot,
false,
"Generates a matlab script for plotting the delay profile");
// Maps a codec type to a printable name string.
std::string CodecName(NetEqDecoder codec) {
@ -238,24 +242,24 @@ void PrintCodecMapping() {
PrintCodecMappingEntry(NetEqDecoder::kDecoderCNGswb48kHz, FLAGS_cn_swb48);
}
int CodecSampleRate(uint8_t payload_type) {
rtc::Optional<int> CodecSampleRate(uint8_t payload_type) {
if (payload_type == FLAGS_pcmu || payload_type == FLAGS_pcma ||
payload_type == FLAGS_ilbc || payload_type == FLAGS_pcm16b ||
payload_type == FLAGS_cn_nb || payload_type == FLAGS_avt)
return 8000;
return rtc::Optional<int>(8000);
if (payload_type == FLAGS_isac || payload_type == FLAGS_pcm16b_wb ||
payload_type == FLAGS_g722 || payload_type == FLAGS_cn_wb ||
payload_type == FLAGS_avt_16)
return 16000;
return rtc::Optional<int>(16000);
if (payload_type == FLAGS_isac_swb || payload_type == FLAGS_pcm16b_swb32 ||
payload_type == FLAGS_cn_swb32 || payload_type == FLAGS_avt_32)
return 32000;
return rtc::Optional<int>(32000);
if (payload_type == FLAGS_opus || payload_type == FLAGS_pcm16b_swb48 ||
payload_type == FLAGS_cn_swb48 || payload_type == FLAGS_avt_48)
return 48000;
return rtc::Optional<int>(48000);
if (payload_type == FLAGS_red)
return 0;
return -1;
return rtc::Optional<int>(0);
return rtc::Optional<int>();
}
// Class to let through only the packets with a given SSRC. Should be used as an
@ -308,6 +312,35 @@ class FilterSsrcInput : public NetEqInput {
uint32_t ssrc_;
};
// A callback class which prints whenver the inserted packet stream changes
// the SSRC.
class SsrcSwitchDetector : public NetEqPostInsertPacket {
public:
// Takes a pointer to another callback object, which will be invoked after
// this object finishes. This does not transfer ownership, and null is a
// valid value.
SsrcSwitchDetector(NetEqPostInsertPacket* other_callback)
: other_callback_(other_callback) {}
void AfterInsertPacket(const NetEqInput::PacketData& packet, NetEq* neteq) {
if (last_ssrc_ && packet.header.ssrc != *last_ssrc_) {
std::cout << "Changing streams from 0x" << std::hex << *last_ssrc_
<< " to 0x" << std::hex << packet.header.ssrc
<< std::dec << " (payload type "
<< static_cast<int>(packet.header.payloadType) << ")"
<< std::endl;
}
last_ssrc_ = rtc::Optional<uint32_t>(packet.header.ssrc);
if (other_callback_) {
other_callback_->AfterInsertPacket(packet, neteq);
}
}
private:
NetEqPostInsertPacket* other_callback_;
rtc::Optional<uint32_t> last_ssrc_;
};
int RunTest(int argc, char* argv[]) {
std::string program_name = argv[0];
std::string usage = "Tool for decoding an RTP dump file using NetEq.\n"
@ -357,10 +390,39 @@ int RunTest(int argc, char* argv[]) {
}
// Check the sample rate.
rtc::Optional<RTPHeader> first_rtp_header = input->NextHeader();
RTC_CHECK(first_rtp_header);
const int sample_rate_hz = CodecSampleRate(first_rtp_header->payloadType);
RTC_CHECK_GT(sample_rate_hz, 0);
rtc::Optional<int> sample_rate_hz;
std::set<std::pair<int, uint32_t>> discarded_pt_and_ssrc;
while (input->NextHeader()) {
rtc::Optional<RTPHeader> first_rtp_header = input->NextHeader();
RTC_DCHECK(first_rtp_header);
sample_rate_hz = CodecSampleRate(first_rtp_header->payloadType);
if (sample_rate_hz) {
std::cout << "Found valid packet with payload type "
<< static_cast<int>(first_rtp_header->payloadType)
<< " and SSRC 0x" << std::hex << first_rtp_header->ssrc
<< std::dec << std::endl;
break;
}
// Discard this packet and move to the next. Keep track of discarded payload
// types and SSRCs.
discarded_pt_and_ssrc.emplace(first_rtp_header->payloadType,
first_rtp_header->ssrc);
input->PopPacket();
}
if (!discarded_pt_and_ssrc.empty()) {
std::cout << "Discarded initial packets with the following payload types "
"and SSRCs:"
<< std::endl;
for (const auto& d : discarded_pt_and_ssrc) {
std::cout << "PT " << d.first << "; SSRC 0x" << std::hex
<< static_cast<int>(d.second) << std::dec << std::endl;
}
}
if (!sample_rate_hz) {
std::cout << "Cannot find any packets with known payload types"
<< std::endl;
RTC_NOTREACHED();
}
// Open the output file now that we know the sample rate. (Rate is only needed
// for wav files.)
@ -369,7 +431,7 @@ int RunTest(int argc, char* argv[]) {
if (output_file_name.size() >= 4 &&
output_file_name.substr(output_file_name.size() - 4) == ".wav") {
// Open a wav file.
output.reset(new OutputWavFile(output_file_name, sample_rate_hz));
output.reset(new OutputWavFile(output_file_name, *sample_rate_hz));
} else {
// Open a pcm file.
output.reset(new OutputAudioFile(output_file_name));
@ -446,14 +508,28 @@ int RunTest(int argc, char* argv[]) {
}
NetEqTest::Callbacks callbacks;
std::unique_ptr<NetEqDelayAnalyzer> delay_analyzer;
if (FLAGS_matlabplot) {
delay_analyzer.reset(new NetEqDelayAnalyzer);
}
SsrcSwitchDetector ssrc_switch_detector(delay_analyzer.get());
callbacks.post_insert_packet = &ssrc_switch_detector;
callbacks.get_audio_callback = delay_analyzer.get();
NetEq::Config config;
config.sample_rate_hz = sample_rate_hz;
config.sample_rate_hz = *sample_rate_hz;
NetEqTest test(config, codecs, ext_codecs, std::move(input),
std::move(output), callbacks);
int64_t test_duration_ms = test.Run();
NetEqNetworkStatistics stats = test.SimulationStats();
if (FLAGS_matlabplot) {
std::cout << "Creating Matlab plot script " << output_file_name + ".m"
<< std::endl;
delay_analyzer->CreateMatlabScript(output_file_name + ".m");
}
printf("Simulation statistics:\n");
printf(" output duration: %" PRId64 " ms\n", test_duration_ms);
printf(" packet_loss_rate: %f %%\n",