diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.cc index aedf067278..6421d0ce1a 100644 --- a/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.cc @@ -11,10 +11,13 @@ #include "webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h" #include +#include +#include +#include #include #include -#include +#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 send_time_s; + std::vector arrival_delay_ms; + std::vector corrected_arrival_delay_ms; + std::vector> playout_delay_ms; + std::vector> 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 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(*ssrc_it++); + while (ssrc_it != ssrcs_.end()) { + output << ", 0x" << std::hex << static_cast(*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 diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h b/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h index b7b5dfe245..87bed2fc4e 100644 --- a/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.h @@ -12,6 +12,8 @@ #define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_DELAY_ANALYZER_H_ #include +#include +#include #include #include "webrtc/base/optional.h" @@ -41,6 +43,11 @@ class NetEqDelayAnalyzer : public test::NetEqPostInsertPacket, std::vector>* playout_delay_ms, std::vector>* 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 ssrcs_; + std::set payload_types_; }; } // namespace test diff --git a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc index e998fa9204..d468c6f549 100644 --- a/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc +++ b/webrtc/modules/audio_coding/neteq/tools/neteq_rtpplay.cc @@ -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 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(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(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(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(48000); if (payload_type == FLAGS_red) - return 0; - return -1; + return rtc::Optional(0); + return rtc::Optional(); } // 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(packet.header.payloadType) << ")" + << std::endl; + } + last_ssrc_ = rtc::Optional(packet.header.ssrc); + if (other_callback_) { + other_callback_->AfterInsertPacket(packet, neteq); + } + } + + private: + NetEqPostInsertPacket* other_callback_; + rtc::Optional 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 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 sample_rate_hz; + std::set> discarded_pt_and_ssrc; + while (input->NextHeader()) { + rtc::Optional 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(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(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 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",