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:
parent
4b15afe038
commit
0bc0ccdc43
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user