aboutsummaryrefslogtreecommitdiffstats
path: root/ui/qt
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt')
-rw-r--r--ui/qt/CMakeLists.txt8
-rw-r--r--ui/qt/Makefile.am2
-rw-r--r--ui/qt/Makefile.common10
-rw-r--r--ui/qt/Wireshark.pro11
-rw-r--r--ui/qt/packet_dialog.cpp3
-rw-r--r--ui/qt/rtp_audio_stream.cpp396
-rw-r--r--ui/qt/rtp_audio_stream.h151
-rw-r--r--ui/qt/rtp_player_dialog.cpp656
-rw-r--r--ui/qt/rtp_player_dialog.h136
-rw-r--r--ui/qt/rtp_player_dialog.ui429
-rw-r--r--ui/qt/voip_calls_dialog.cpp116
-rw-r--r--ui/qt/voip_calls_dialog.h1
-rw-r--r--ui/qt/voip_calls_dialog.ui2
13 files changed, 1886 insertions, 35 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt
index efb028110b..d214b4826f 100644
--- a/ui/qt/CMakeLists.txt
+++ b/ui/qt/CMakeLists.txt
@@ -114,6 +114,8 @@ set(WIRESHARK_QT_HEADERS
response_time_delay_dialog.h
rpc_service_response_time_dialog.h
rtp_analysis_dialog.h
+ rtp_audio_stream.h
+ rtp_player_dialog.h
rtp_stream_dialog.h
sctp_all_assocs_dialog.h
sctp_assoc_analyse_dialog.h
@@ -256,6 +258,11 @@ set(WIRESHARK_QT_SRC
response_time_delay_dialog.cpp
rpc_service_response_time_dialog.cpp
rtp_analysis_dialog.cpp
+ rtp_audio_stream.cpp
+ rtp_player_dialog.cpp
+ rtp_stream_dialog.cpp
+ sctp_all_assocs_dialog.cpp
+ sctp_assoc_analyse_dialog.cpp
sctp_chunk_statistics_dialog.cpp
sctp_graph_dialog.cpp
sctp_graph_arwnd_dialog.cpp
@@ -376,6 +383,7 @@ set(WIRESHARK_QT_UI
protocol_hierarchy_dialog.ui
resolved_addresses_dialog.ui
rtp_analysis_dialog.ui
+ rtp_player_dialog.ui
rtp_stream_dialog.ui
sctp_all_assocs_dialog.ui
sctp_assoc_analyse_dialog.ui
diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am
index e4198c630a..13bd0d2db7 100644
--- a/ui/qt/Makefile.am
+++ b/ui/qt/Makefile.am
@@ -244,6 +244,8 @@ resolved_addresses_dialog.$(OBJEXT): ui_resolved_addresses_dialog.h
rtp_analysis_dialog.$(OBJEXT): ui_rtp_analysis_dialog.h
+rtp_player_dialog.$(OBJEXT): ui_rtp_player_dialog.h
+
rtp_stream_dialog.$(OBJEXT): ui_rtp_stream_dialog.h
search_frame.$(OBJEXT): ui_search_frame.h
diff --git a/ui/qt/Makefile.common b/ui/qt/Makefile.common
index ab8c3c9086..1af30ecbfb 100644
--- a/ui/qt/Makefile.common
+++ b/ui/qt/Makefile.common
@@ -87,6 +87,7 @@ NODIST_GENERATED_HEADER_FILES = \
ui_remote_settings_dialog.h \
ui_resolved_addresses_dialog.h \
ui_rtp_analysis_dialog.h \
+ ui_rtp_player_dialog.h \
ui_rtp_stream_dialog.h \
ui_sctp_all_assocs_dialog.h \
ui_sctp_assoc_analyse_dialog.h \
@@ -235,9 +236,12 @@ MOC_HDRS = \
response_time_delay_dialog.h \
rpc_service_response_time_dialog.h \
rtp_analysis_dialog.h \
+ rtp_audio_stream.h \
+ rtp_player_dialog.h \
rtp_stream_dialog.h \
sctp_all_assocs_dialog.h \
sctp_assoc_analyse_dialog.h \
+ search_frame.h \
sctp_chunk_statistics_dialog.h \
sctp_graph_dialog.h \
sctp_graph_arwnd_dialog.h \
@@ -326,6 +330,7 @@ UI_FILES = \
remote_settings_dialog.ui \
resolved_addresses_dialog.ui \
rtp_analysis_dialog.ui \
+ rtp_player_dialog.ui \
rtp_stream_dialog.ui \
sctp_all_assocs_dialog.ui \
sctp_assoc_analyse_dialog.ui \
@@ -486,6 +491,11 @@ WIRESHARK_QT_SRC = \
response_time_delay_dialog.cpp \
rpc_service_response_time_dialog.cpp \
rtp_analysis_dialog.cpp \
+ rtp_audio_stream.cpp \
+ rtp_player_dialog.cpp \
+ rtp_stream_dialog.cpp \
+ sctp_all_assocs_dialog.cpp \
+ sctp_assoc_analyse_dialog.cpp \
sctp_chunk_statistics_dialog.cpp \
sctp_graph_dialog.cpp \
sctp_graph_arwnd_dialog.cpp \
diff --git a/ui/qt/Wireshark.pro b/ui/qt/Wireshark.pro
index 43f470c013..a056d5b6b9 100644
--- a/ui/qt/Wireshark.pro
+++ b/ui/qt/Wireshark.pro
@@ -26,7 +26,7 @@
isEqual(QT_MAJOR_VERSION, 4) {
QT += core gui
} else {
- QT += core widgets printsupport multimediawidgets
+ QT += core widgets printsupport multimedia
}
isEqual(QT_MAJOR_VERSION, 5): greaterThan(QT_MINOR_VERSION, 1): win32 {
@@ -265,6 +265,7 @@ FORMS += \
remote_settings_dialog.ui \
resolved_addresses_dialog.ui \
rtp_analysis_dialog.ui \
+ rtp_player_dialog.ui \
rtp_stream_dialog.ui \
sctp_all_assocs_dialog.ui \
sctp_assoc_analyse_dialog.ui \
@@ -350,6 +351,8 @@ HEADERS += $$HEADERS_WS_C \
remote_settings_dialog.h \
resolved_addresses_dialog.h \
rtp_analysis_dialog.h \
+ rtp_audio_stream.h \
+ rtp_player_dialog.h \
rtp_stream_dialog.h \
sctp_all_assocs_dialog.h \
sctp_assoc_analyse_dialog.h \
@@ -490,7 +493,7 @@ win32 {
EXTRA_DLLS = QtCored4 QtGuid4
} else: lessThan(QT_MINOR_VERSION, 3) {
# The QT lib parts are copied by windeployqt post 5.3
- EXTRA_DLLS = Qt5Cored Qt5Guid Qt5Widgetsd Qt5PrintSupportd Qt5MultimediaWidgetsd
+ EXTRA_DLLS = Qt5Cored Qt5Guid Qt5Widgetsd Qt5PrintSupportd Qt5Multimediad
EXTRA_PLATFORM_DLLS = qwindowsd
QMAKE_POST_LINK +=$$quote($(CHK_DIR_EXISTS) $${PLATFORM_DLL_DIR} $(MKDIR) $${PLATFORM_DLL_DIR}$$escape_expand(\\n\\t))
}
@@ -500,7 +503,7 @@ win32 {
EXTRA_DLLS = QtCore4 QtGui4
} else: lessThan(QT_MINOR_VERSION, 3) {
# The QT lib parts are copied by windeployqt post 5.3
- EXTRA_DLLS = Qt5Core Qt5Gui Qt5Widgets Qt5PrintSupport Qt5MultimediaWidgets
+ EXTRA_DLLS = Qt5Core Qt5Gui Qt5Widgets Qt5PrintSupport Qt5Multimedia
EXTRA_PLATFORM_DLLS = qwindows
QMAKE_POST_LINK +=$$quote($(CHK_DIR_EXISTS) $${PLATFORM_DLL_DIR} $(MKDIR) $${PLATFORM_DLL_DIR}$$escape_expand(\\n\\t))
}
@@ -756,6 +759,8 @@ SOURCES += \
resolved_addresses_dialog.cpp \
rpc_service_response_time_dialog.cpp \
rtp_analysis_dialog.cpp \
+ rtp_audio_stream.cpp \
+ rtp_player_dialog.cpp \
rtp_stream_dialog.cpp \
sctp_all_assocs_dialog.cpp \
sctp_assoc_analyse_dialog.cpp \
diff --git a/ui/qt/packet_dialog.cpp b/ui/qt/packet_dialog.cpp
index 05f8878903..1ecff0370c 100644
--- a/ui/qt/packet_dialog.cpp
+++ b/ui/qt/packet_dialog.cpp
@@ -89,8 +89,7 @@ PacketDialog::PacketDialog(QWidget &parent, CaptureFile &cf, frame_data *fdata)
}
byte_view_tab_->setCurrentIndex(0);
- ui->packetSplitter->setStretchFactor(0, 5);
- ui->packetSplitter->setStretchFactor(1, 1);
+ ui->packetSplitter->setStretchFactor(1, 0);
QStringList col_parts;
for (int i = 0; i < cap_file_.capFile()->cinfo.num_cols; ++i) {
diff --git a/ui/qt/rtp_audio_stream.cpp b/ui/qt/rtp_audio_stream.cpp
new file mode 100644
index 0000000000..1b6bb44b80
--- /dev/null
+++ b/ui/qt/rtp_audio_stream.cpp
@@ -0,0 +1,396 @@
+/* rtp_audio_frame.h
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include "rtp_audio_stream.h"
+
+#ifdef QT_MULTIMEDIA_LIB
+
+#include <codecs/speex/speex_resampler.h>
+
+#include <epan/rtp_pt.h>
+
+#include <epan/dissectors/packet-rtp.h>
+
+#include <ui/rtp_media.h>
+#include <ui/rtp_stream.h>
+
+#include <wsutil/nstime.h>
+
+#include <QAudioFormat>
+#include <QAudioOutput>
+#include <QDir>
+#include <QTemporaryFile>
+
+static spx_int16_t default_audio_sample_rate_ = 8000;
+static const spx_int16_t visual_sample_rate_ = 1000;
+
+RtpAudioStream::RtpAudioStream(QObject *parent, _rtp_stream_info *rtp_stream) :
+ QObject(parent),
+ decoders_hash_(rtp_decoder_hash_table_new()),
+ global_start_rel_time_(0.0),
+ start_abs_offset_(0.0),
+ start_rel_time_(0.0),
+ stop_rel_time_(0.0),
+ audio_out_rate_(0),
+ audio_resampler_(0),
+ audio_output_(0),
+ max_sample_val_(1)
+{
+ copy_address(&src_addr_, &rtp_stream->src_addr);
+ src_port_ = rtp_stream->src_port;
+ copy_address(&dst_addr_, &rtp_stream->dest_addr);
+ dst_port_ = rtp_stream->dest_port;
+ ssrc_ = rtp_stream->ssrc;
+
+ // We keep visual samples in memory. Make fewer of them.
+ visual_resampler_ = ws_codec_resampler_init(1, default_audio_sample_rate_,
+ visual_sample_rate_, SPEEX_RESAMPLER_QUALITY_MIN, NULL);
+ ws_codec_resampler_skip_zeros(visual_resampler_);
+
+ QString tempname = QString("%1/wireshark_rtp_stream").arg(QDir::tempPath());
+ tempfile_ = new QTemporaryFile(tempname, this);
+ tempfile_->open();
+
+ // RTP_STREAM_DEBUG("Writing to %s", tempname.toUtf8().constData());
+}
+
+RtpAudioStream::~RtpAudioStream()
+{
+ g_hash_table_destroy(decoders_hash_);
+ if (audio_resampler_) ws_codec_resampler_destroy (audio_resampler_);
+ ws_codec_resampler_destroy (visual_resampler_);
+}
+
+bool RtpAudioStream::isMatch(const _rtp_stream_info *rtp_stream) const
+{
+ if (rtp_stream
+ && addresses_equal(&rtp_stream->src_addr, &src_addr_)
+ && rtp_stream->src_port == src_port_
+ && addresses_equal(&rtp_stream->dest_addr, &dst_addr_)
+ && rtp_stream->dest_port == dst_port_
+ && rtp_stream->ssrc == ssrc_)
+ return true;
+ return false;
+}
+
+bool RtpAudioStream::isMatch(const _packet_info *pinfo, const _rtp_info *rtp_info) const
+{
+ if (pinfo && rtp_info
+ && addresses_equal(&pinfo->src, &src_addr_)
+ && pinfo->srcport == src_port_
+ && addresses_equal(&pinfo->dst, &dst_addr_)
+ && pinfo->destport == dst_port_
+ && rtp_info->info_sync_src == ssrc_)
+ return true;
+ return false;
+}
+
+// XXX We add multiple RTP streams here because that's what the GTK+ UI does.
+// Should we make these distinct, with their own waveforms? It seems like
+// that would simplify a lot of things.
+void RtpAudioStream::addRtpStream(const _rtp_stream_info *rtp_stream)
+{
+ if (!rtp_stream) return;
+
+ // RTP_STREAM_DEBUG("added %d:%u packets", g_list_length(rtp_stream->rtp_packet_list), rtp_stream->packet_count);
+ rtp_streams_ << rtp_stream;
+
+ double stream_srt = nstime_to_sec(&rtp_stream->start_rel_time);
+ if (rtp_streams_.length() < 2 || stream_srt > start_rel_time_) {
+ start_rel_time_ = stop_rel_time_ = stream_srt;
+ start_abs_offset_ = nstime_to_sec(&rtp_stream->start_fd->abs_ts) - start_rel_time_;
+ }
+}
+
+static const int max_payload_len_ = 1000; // Arbitrary.
+static const int sample_bytes_ = sizeof(SAMPLE) / sizeof(char);
+void RtpAudioStream::addRtpPacket(const struct _packet_info *pinfo, const _rtp_info *rtp_info)
+{
+ if (!rtp_info) return;
+
+ // Combination of gtk/rtp_player.c:decode_rtp_stream + decode_rtp_packet
+ // XXX This is more messy than it should be.
+
+ size_t decoded_bytes;
+ SAMPLE *decode_buff = NULL;
+ SAMPLE *resample_buff = NULL;
+ spx_uint32_t cur_in_rate, visual_out_rate;
+ char *write_buff;
+ qint64 write_bytes;
+ unsigned channels;
+ unsigned sample_rate;
+ rtp_packet_t rtp_packet;
+
+ stop_rel_time_ = nstime_to_sec(&pinfo->rel_ts);
+ ws_codec_resampler_get_rate(visual_resampler_, &cur_in_rate, &visual_out_rate);
+
+ QString payload_name;
+ if (rtp_info->info_payload_type_str) {
+ payload_name = rtp_info->info_payload_type_str;
+ } else {
+ payload_name = try_val_to_str_ext(rtp_info->info_payload_type, &rtp_payload_type_short_vals_ext);
+ }
+ if (!payload_name.isEmpty()) {
+ payload_names_ << payload_name;
+ }
+
+ // First, decode the payload.
+ rtp_packet.info = (_rtp_info *) g_memdup(rtp_info, sizeof(struct _rtp_info));
+ rtp_packet.arrive_offset = start_rel_time_;
+ if (rtp_info->info_all_data_present && (rtp_info->info_payload_len != 0)) {
+ rtp_packet.payload_data = (guint8 *)g_malloc(rtp_info->info_payload_len);
+ memcpy(rtp_packet.payload_data, rtp_info->info_data + rtp_info->info_payload_offset, rtp_info->info_payload_len);
+ } else {
+ rtp_packet.payload_data = NULL;
+ }
+
+ decoded_bytes = decode_rtp_packet(&rtp_packet, &decode_buff, decoders_hash_, &channels, &sample_rate);
+ write_buff = (char *) decode_buff;
+ write_bytes = rtp_info->info_payload_len * sample_bytes_;
+
+ if (tempfile_->pos() == 0) {
+ // First packet. Let it determine our sample rate.
+ audio_out_rate_ = sample_rate;
+
+ last_sequence_ = rtp_info->info_seq_num - 1;
+
+ // Prepend silence to match our sibling streams.
+ int prepend_samples = (start_rel_time_ - global_start_rel_time_) * audio_out_rate_;
+ if (prepend_samples > 0) {
+ int prepend_bytes = prepend_samples * sample_bytes_;
+ char *prepend_buff = (char *) g_malloc(prepend_bytes);
+ SAMPLE silence = 0;
+ memccpy(prepend_buff, &silence, prepend_samples, sample_bytes_);
+ tempfile_->write(prepend_buff, prepend_bytes);
+ }
+ } else if (audio_out_rate_ != sample_rate) {
+ // Resample the audio to match our previous output rate.
+ if (!audio_resampler_) {
+ audio_resampler_ = ws_codec_resampler_init(1, sample_rate, audio_out_rate_, 10, NULL);
+ ws_codec_resampler_skip_zeros(audio_resampler_);
+ // RTP_STREAM_DEBUG("Started resampling from %u to (out) %u Hz.", sample_rate, audio_out_rate_);
+ } else {
+ spx_uint32_t audio_out_rate;
+ ws_codec_resampler_get_rate(audio_resampler_, &cur_in_rate, &audio_out_rate);
+
+ // Adjust rates if needed.
+ if (sample_rate != cur_in_rate) {
+ ws_codec_resampler_set_rate(audio_resampler_, sample_rate, audio_out_rate);
+ ws_codec_resampler_set_rate(visual_resampler_, sample_rate, visual_out_rate);
+ // RTP_STREAM_DEBUG("Changed input rate from %u to %u Hz. Out is %u.", cur_in_rate, sample_rate, audio_out_rate_);
+ }
+ }
+ spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
+ spx_uint32_t out_len = (audio_out_rate_ * (spx_uint32_t)rtp_info->info_payload_len / sample_rate) + (audio_out_rate_ % sample_rate != 0);
+ resample_buff = (SAMPLE *) g_malloc(out_len * sample_bytes_);
+
+ ws_codec_resampler_process_int(audio_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
+ write_buff = (char *) decode_buff;
+ write_bytes = out_len * sample_bytes_;
+ }
+
+ if (rtp_info->info_seq_num != last_sequence_+1) {
+ out_of_seq_timestamps_.append(stop_rel_time_);
+ // XXX Add silence to tempfile_ and visual_samples_
+ }
+ last_sequence_ = rtp_info->info_seq_num;
+
+ // Write the decoded, possibly-resampled audio to our temp file.
+ tempfile_->write(write_buff, write_bytes);
+
+ // Collect our visual samples.
+ spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
+ spx_uint32_t out_len = (visual_out_rate * in_len / sample_rate) + (visual_out_rate % sample_rate != 0);
+ resample_buff = (SAMPLE *) g_realloc(resample_buff, out_len * sizeof(SAMPLE));
+
+ ws_codec_resampler_process_int(visual_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
+ for (unsigned i = 0; i < out_len; i++) {
+ packet_timestamps_[stop_rel_time_ + (double) i / visual_out_rate] = pinfo->fd->num;
+ if (qAbs(resample_buff[i]) > max_sample_val_) max_sample_val_ = qAbs(resample_buff[i]);
+ visual_samples_.append(resample_buff[i]);
+ }
+
+ // Finally, write the resampled audio to our temp file and clean up.
+ g_free(rtp_packet.payload_data);
+ g_free(decode_buff);
+ g_free(resample_buff);
+}
+
+void RtpAudioStream::reset(double start_rel_time)
+{
+ last_sequence_ = 0;
+ global_start_rel_time_ = start_rel_time;
+ stop_rel_time_ = start_rel_time_;
+ audio_out_rate_ = 0;
+ max_sample_val_ = 1;
+ packet_timestamps_.clear();
+ visual_samples_.clear();
+ out_of_seq_timestamps_.clear();
+
+ if (audio_resampler_) {
+ ws_codec_resampler_reset_mem(audio_resampler_);
+ }
+ if (visual_resampler_) {
+ ws_codec_resampler_reset_mem(visual_resampler_);
+ }
+ tempfile_->seek(0);
+}
+
+const QStringList RtpAudioStream::payloadNames() const
+{
+ QStringList payload_names = payload_names_.toList();
+ payload_names.sort();
+ return payload_names;
+}
+
+const QVector<double> RtpAudioStream::visualTimestamps(bool relative)
+{
+ QVector<double> ts_keys = packet_timestamps_.keys().toVector();
+ if (relative) return ts_keys;
+
+ QVector<double> adj_timestamps;
+ for (int i = 0; i < ts_keys.length(); i++) {
+ adj_timestamps.append(ts_keys[i] + start_abs_offset_);
+ }
+ return adj_timestamps;
+}
+
+// Scale the height of the waveform (max_sample_val_) and adjust its Y
+// offset so that they overlap slightly (stack_offset_).
+
+// XXX This means that waveforms can be misleading with respect to relative
+// amplitude. We might want to add a "global" max_sample_val_.
+static const double stack_offset_ = G_MAXINT16 / 3;
+const QVector<double> RtpAudioStream::visualSamples(int y_offset)
+{
+ QVector<double> adj_samples;
+ double scaled_offset = y_offset * stack_offset_;
+ for (int i = 0; i < visual_samples_.length(); i++) {
+ adj_samples.append(((double)visual_samples_[i] * G_MAXINT16 / max_sample_val_) + scaled_offset);
+ }
+ return adj_samples;
+}
+
+const QVector<double> RtpAudioStream::outOfSequenceTimestamps(bool relative)
+{
+ if (relative) return out_of_seq_timestamps_;
+
+ QVector<double> adj_timestamps;
+ for (int i = 0; i < out_of_seq_timestamps_.length(); i++) {
+ adj_timestamps.append(out_of_seq_timestamps_[i] + start_abs_offset_);
+ }
+ return adj_timestamps;
+}
+
+const QVector<double> RtpAudioStream::outOfSequenceSamples(int y_offset)
+{
+ QVector<double> adj_samples;
+ double scaled_offset = y_offset * stack_offset_;
+ for (int i = 0; i < out_of_seq_timestamps_.length(); i++) {
+ adj_samples.append(scaled_offset);
+ }
+ return adj_samples;
+}
+
+quint32 RtpAudioStream::nearestPacket(double timestamp, bool is_relative)
+{
+ if (packet_timestamps_.keys().count() < 1) return 0;
+
+ if (!is_relative) timestamp -= start_abs_offset_;
+ QMap<double, quint32>::const_iterator it = packet_timestamps_.lowerBound(timestamp);
+ if (it == packet_timestamps_.begin()) return 0;
+ return it.value();
+}
+
+QAudio::State RtpAudioStream::outputState() const
+{
+ if (!audio_output_) return QAudio::IdleState;
+ return audio_output_->state();
+}
+
+void RtpAudioStream::startPlaying()
+{
+ if (audio_output_) return;
+
+ QAudioFormat format;
+ format.setSampleRate(audio_out_rate_);
+ format.setSampleSize(sample_bytes_ * 8); // bits
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setChannelCount(1);
+ format.setCodec("audio/pcm");
+
+ // RTP_STREAM_DEBUG("playing %s %d samples @ %u Hz",
+ // tempfile_->fileName().toUtf8().constData(),
+ // (int) tempfile_->size(), audio_out_rate_);
+
+ audio_output_ = new QAudioOutput(format, this);
+ audio_output_->setNotifyInterval(65); // ~15 fps
+ connect(audio_output_, SIGNAL(stateChanged(QAudio::State)), this, SLOT(outputStateChanged()));
+ connect(audio_output_, SIGNAL(notify()), this, SLOT(outputNotify()));
+ tempfile_->seek(0);
+ audio_output_->start(tempfile_);
+ emit startedPlaying();
+}
+
+void RtpAudioStream::stopPlaying()
+{
+ if (audio_output_) {
+ audio_output_->stop();
+ delete audio_output_;
+ audio_output_ = NULL;
+ }
+ emit finishedPlaying();
+}
+
+void RtpAudioStream::outputStateChanged()
+{
+ if (!audio_output_) return;
+
+ if (audio_output_->state() == QAudio::IdleState) {
+ // RTP_STREAM_DEBUG("stopped %f", audio_output_->processedUSecs() / 100000.0);
+ delete audio_output_;
+ audio_output_ = NULL;
+
+ emit finishedPlaying();
+ }
+}
+
+void RtpAudioStream::outputNotify()
+{
+ if (!audio_output_) return;
+ emit processedSecs(audio_output_->processedUSecs() / 1000000.0);
+}
+
+#endif // QT_MULTIMEDIA_LIB
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/qt/rtp_audio_stream.h b/ui/qt/rtp_audio_stream.h
new file mode 100644
index 0000000000..b2842aad37
--- /dev/null
+++ b/ui/qt/rtp_audio_stream.h
@@ -0,0 +1,151 @@
+/* rtp_audio_stream.h
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef RTPAUDIOSTREAM_H
+#define RTPAUDIOSTREAM_H
+
+#include "config.h"
+
+#ifdef QT_MULTIMEDIA_LIB
+
+#include <glib.h>
+
+#include <epan/address.h>
+
+#include <QAudio>
+#include <QColor>
+#include <QMap>
+#include <QObject>
+#include <QSet>
+#include <QVector>
+
+class QAudioOutput;
+class QTemporaryFile;
+
+struct _rtp_stream_info;
+struct _rtp_sample;
+
+class RtpAudioStream : public QObject
+{
+ Q_OBJECT
+public:
+ explicit RtpAudioStream(QObject *parent, struct _rtp_stream_info *rtp_stream);
+ ~RtpAudioStream();
+ bool isMatch(const struct _rtp_stream_info *rtp_stream) const;
+ bool isMatch(const struct _packet_info *pinfo, const struct _rtp_info *rtp_info) const;
+ void addRtpStream(const struct _rtp_stream_info *rtp_stream);
+ void addRtpPacket(const struct _packet_info *pinfo, const struct _rtp_info *rtp_info);
+ void reset(double start_rel_time);
+
+ double startRelTime() const { return start_rel_time_; }
+ double stopRelTime() const { return stop_rel_time_; }
+ unsigned sampleRate() const { return audio_out_rate_; }
+ const QStringList payloadNames() const;
+
+ /**
+ * @brief Return a list of visual timestamps.
+ * @return A set of timestamps suitable for passing to QCPGraph::setData.
+ */
+ const QVector<double> visualTimestamps(bool relative = true);
+ /**
+ * @brief Return a list of visual samples. There will be fewer visual samples
+ * per second (1000) than the actual audio.
+ * @param y_offset Y axis offset to be used for stacking graphs.
+ * @return A set of values suitable for passing to QCPGraph::setData.
+ */
+ const QVector<double> visualSamples(int y_offset = 0);
+
+ /**
+ * @brief Return a list of out-of-sequence timestamps.
+ * @return A set of timestamps suitable for passing to QCPGraph::setData.
+ */
+ const QVector<double> outOfSequenceTimestamps(bool relative = true);
+ int outOfSequence() { return out_of_seq_timestamps_.length(); }
+ /**
+ * @brief Return a list of out-of-sequence samples. Y value is constant.
+ * @param y_offset Y axis offset to be used for stacking graphs.
+ * @return A set of values suitable for passing to QCPGraph::setData.
+ */
+ const QVector<double> outOfSequenceSamples(int y_offset = 0);
+
+ quint32 nearestPacket(double timestamp, bool is_relative = true);
+
+ QRgb color() { return color_; }
+ void setColor(QRgb color) { color_ = color; }
+
+ QAudio::State outputState() const;
+
+signals:
+ void startedPlaying();
+ void processedSecs(double secs);
+ void finishedPlaying();
+
+public slots:
+ void startPlaying();
+ void stopPlaying();
+
+private:
+ address src_addr_;
+ quint16 src_port_;
+ address dst_addr_;
+ quint16 dst_port_;
+ quint32 ssrc_;
+ int last_sequence_;
+ QTemporaryFile *tempfile_;
+ struct _GHashTable *decoders_hash_;
+ QList<const struct _rtp_stream_info *>rtp_streams_;
+ double global_start_rel_time_;
+ double start_abs_offset_;
+ double start_rel_time_;
+ double stop_rel_time_;
+ quint32 audio_out_rate_;
+ QSet<QString> payload_names_;
+ struct SpeexResamplerState_ *audio_resampler_;
+ struct SpeexResamplerState_ *visual_resampler_;
+ QAudioOutput *audio_output_;
+ QMap<double, quint32> packet_timestamps_;
+ QVector<qint16> visual_samples_;
+ QVector<double> out_of_seq_timestamps_;
+ qint16 max_sample_val_;
+ QRgb color_;
+
+private slots:
+ void outputStateChanged();
+ void outputNotify();
+
+};
+
+#endif // QT_MULTIMEDIA_LIB
+
+#endif // RTPAUDIOSTREAM_H
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/qt/rtp_player_dialog.cpp b/ui/qt/rtp_player_dialog.cpp
new file mode 100644
index 0000000000..b930a08d0b
--- /dev/null
+++ b/ui/qt/rtp_player_dialog.cpp
@@ -0,0 +1,656 @@
+/* rtp_player_dialog.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "rtp_player_dialog.h"
+#include "ui_rtp_player_dialog.h"
+
+#ifdef QT_MULTIMEDIA_LIB
+
+#include <epan/dissectors/packet-rtp.h>
+
+#include <ui/utf8_entities.h>
+
+#include "color_utils.h"
+#include "qcustomplot.h"
+#include "qt_ui_utils.h"
+#include "rtp_audio_stream.h"
+#include "stock_icon.h"
+#include "tango_colors.h"
+
+#include <QAudio>
+#include <QFrame>
+#include <QMenu>
+#include <QVBoxLayout>
+
+Q_DECLARE_METATYPE(RtpAudioStream *)
+Q_DECLARE_METATYPE(QCPGraph *)
+
+#endif // QT_MULTIMEDIA_LIB
+
+#include "wireshark_application.h"
+
+// To do:
+// - Fully implement shorcuts (drag, go to packet, etc.)
+// - Figure out selection and highlighting.
+// - Make streams checkable.
+// - Add silence, drop & jitter indicators to the graph.
+// - How to handle multiple channels?
+// - Threaded decoding?
+// - Play MP3s. As per Zawinski's Law we already read emails.
+// - RTP audio streams are currently keyed on src addr + src port + dst addr
+// + dst port + ssrc. This means that we can have multiple rtp_stream_info
+// structs per RtpAudioStream. Should we make them 1:1 instead?
+
+// Current RTP player bugs:
+// Bug 3368 - The timestamp line in a RTP or RTCP packet display's "Not Representable"
+// Bug 3952 - VoIP Call RTP Player: audio played is corrupted when RFC2833 packets are present
+// Bug 4960 - RTP Player: Audio and visual feedback get rapidly out of sync
+// Bug 5527 - Adding arbitrary value to x-axis RTP player
+// Bug 7935 - Wrong Timestamps in RTP Player-Decode
+// Bug 8007 - UI gets confused on playing decoded audio in rtp_player
+// Bug 9007 - Switching SSRC values in RTP stream
+// Bug 10613 - RTP audio player crashes
+// Bug 11125 - RTP Player does not show progress in selected stream in Window 7
+// Bug 11409 - Wireshark crashes when using RTP player
+
+// XXX It looks like we duplicate some functionality here and in the RTP
+// analysis code, which has its own routines for writing audio data to a
+// file.
+
+// In some places we match by conv/call number, in others we match by first frame.
+
+enum {
+ src_addr_col_,
+ src_port_col_,
+ dst_addr_col_,
+ dst_port_col_,
+ ssrc_col_,
+ first_pkt_col_,
+ num_pkts_col_,
+ time_span_col_,
+ sample_rate_col_,
+ payload_col_,
+
+ stream_data_col_ = src_addr_col_, // RtpAudioStream
+ graph_data_col_ = src_port_col_ // QCPGraph
+};
+
+static const double wf_graph_normal_width_ = 0.5;
+static const double wf_graph_selected_width_ = 2.0;
+
+RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf)
+#ifdef QT_MULTIMEDIA_LIB
+ , ui(new Ui::RtpPlayerDialog)
+ , start_rel_time_(0.0)
+#endif // QT_MULTIMEDIA_LIB
+{
+ ui->setupUi(this);
+ setWindowTitle(wsApp->windowTitleString(tr("RTP Player")));
+ resize(parent.size());
+
+#ifdef QT_MULTIMEDIA_LIB
+ ui->splitter->setStretchFactor(0, 3);
+ ui->splitter->setStretchFactor(1, 1);
+
+ ctx_menu_ = new QMenu(this);
+
+ ctx_menu_->addAction(ui->actionZoomIn);
+ ctx_menu_->addAction(ui->actionZoomOut);
+ ctx_menu_->addAction(ui->actionReset);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionMoveRight10);
+ ctx_menu_->addAction(ui->actionMoveLeft10);
+ ctx_menu_->addAction(ui->actionMoveRight1);
+ ctx_menu_->addAction(ui->actionMoveLeft1);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionGoToPacket);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionDragZoom);
+ ctx_menu_->addAction(ui->actionToggleTimeOrigin);
+ ctx_menu_->addAction(ui->actionCrosshairs);
+
+ connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
+ this, SLOT(mouseMoved(QMouseEvent*)));
+ connect(ui->audioPlot, SIGNAL(mousePress(QMouseEvent*)),
+ this, SLOT(graphClicked(QMouseEvent*)));
+
+ cur_play_pos_ = new QCPItemStraightLine(ui->audioPlot);
+ ui->audioPlot->addItem(cur_play_pos_);
+ cur_play_pos_->setVisible(false);
+
+ ui->audioPlot->xAxis->setNumberFormat("gb");
+ ui->audioPlot->xAxis->setNumberPrecision(3);
+ ui->audioPlot->xAxis->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss.zzz");
+ ui->audioPlot->yAxis->setVisible(false);
+
+ ui->playButton->setIcon(StockIcon("media-playback-start"));
+ ui->stopButton->setIcon(StockIcon("media-playback-stop"));
+
+ ui->audioPlot->setMouseTracking(true);
+ ui->audioPlot->setEnabled(true);
+ ui->audioPlot->setInteractions(
+ QCP::iRangeDrag |
+ QCP::iRangeZoom
+ );
+ ui->audioPlot->setFocus();
+
+ QTimer::singleShot(0, this, SLOT(retapPackets()));
+#endif // QT_MULTIMEDIA_LIB
+}
+
+#ifdef QT_MULTIMEDIA_LIB
+RtpPlayerDialog::~RtpPlayerDialog()
+{
+ delete ui;
+}
+
+void RtpPlayerDialog::retapPackets(bool rescale_axes)
+{
+ int row_count = ui->streamTreeWidget->topLevelItemCount();
+ // Clear existing graphs and reset stream values
+ for (int row = 0; row < row_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ audio_stream->reset(start_rel_time_);
+
+ ti->setData(graph_data_col_, Qt::UserRole, QVariant());
+ }
+ ui->audioPlot->clearGraphs();
+
+ register_tap_listener("rtp", this, NULL, 0, NULL, tapPacket, NULL);
+ cap_file_.retapPackets();
+ remove_tap_listener(this);
+
+ bool show_legend = false;
+ bool relative_timestamps = !ui->todCheckBox->isChecked();
+
+ ui->audioPlot->xAxis->setTickLabelType(relative_timestamps ? QCPAxis::ltNumber : QCPAxis::ltDateTime);
+
+ for (int row = 0; row < row_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ int y_offset = row_count - row - 1;
+
+ // Waveform
+ QCPGraph *audio_graph = ui->audioPlot->addGraph();
+ QPen wf_pen(audio_stream->color());
+ wf_pen.setWidthF(wf_graph_normal_width_);
+ audio_graph->setPen(wf_pen);
+ wf_pen.setWidthF(wf_graph_selected_width_);
+ audio_graph->setSelectedPen(wf_pen);
+ audio_graph->setSelectable(false);
+ audio_graph->setData(audio_stream->visualTimestamps(relative_timestamps), audio_stream->visualSamples(y_offset));
+ audio_graph->removeFromLegend();
+ ti->setData(graph_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(audio_graph));
+ // RTP_STREAM_DEBUG("Plotting %s, %d samples", ti->text(src_addr_col_).toUtf8().constData(), audio_graph->data()->keys().length());
+
+ QString span_str = QString("%1 - %2 (%3)")
+ .arg(QString::number(audio_stream->startRelTime(), 'g', 3))
+ .arg(QString::number(audio_stream->stopRelTime(), 'g', 3))
+ .arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'g', 3));
+ ti->setText(time_span_col_, span_str);
+ ti->setText(sample_rate_col_, QString::number(audio_stream->sampleRate()));
+ ti->setText(payload_col_, audio_stream->payloadNames().join(", "));
+
+ if (audio_stream->outOfSequence() > 0) {
+ // Sequence numbers
+ QCPGraph *seq_graph = ui->audioPlot->addGraph();
+ seq_graph->setLineStyle(QCPGraph::lsNone);
+ seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssSquare, tango_aluminium_6, Qt::white, 4)); // Arbitrary
+ seq_graph->setSelectable(false);
+ seq_graph->setData(audio_stream->outOfSequenceTimestamps(relative_timestamps), audio_stream->outOfSequenceSamples(y_offset));
+ if (row < 1) {
+ seq_graph->setName(tr("Out of Sequence"));
+ show_legend = true;
+ } else {
+ seq_graph->removeFromLegend();
+ }
+ }
+ }
+ ui->audioPlot->legend->setVisible(show_legend);
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount() - 1; col++) {
+ ui->streamTreeWidget->resizeColumnToContents(col);
+ }
+
+ ui->audioPlot->replot();
+ if (rescale_axes) resetXAxis();
+
+ updateWidgets();
+}
+
+void RtpPlayerDialog::addRtpStream(struct _rtp_stream_info *rtp_stream)
+{
+ if (!rtp_stream) return;
+
+ // Find the RTP streams associated with this conversation.
+ // gtk/rtp_player.c:mark_rtp_stream_to_play does this differently.
+
+ RtpAudioStream *audio_stream = NULL;
+ int tli_count = ui->streamTreeWidget->topLevelItemCount();
+ for (int row = 0; row < tli_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ if (row_stream->isMatch(rtp_stream)) {
+ audio_stream = row_stream;
+ break;
+ }
+ }
+
+ if (!audio_stream) {
+ audio_stream = new RtpAudioStream(this, rtp_stream);
+ audio_stream->setColor(ColorUtils::graph_colors_[tli_count % ColorUtils::graph_colors_.length()]);
+
+ QTreeWidgetItem *ti = new QTreeWidgetItem(ui->streamTreeWidget);
+ ti->setText(src_addr_col_, address_to_qstring(&rtp_stream->src_addr));
+ ti->setText(src_port_col_, QString::number(rtp_stream->src_port));
+ ti->setText(dst_addr_col_, address_to_qstring(&rtp_stream->dest_addr));
+ ti->setText(dst_port_col_, QString::number(rtp_stream->dest_port));
+ ti->setText(ssrc_col_, int_to_qstring(rtp_stream->ssrc, 8, 16));
+ ti->setText(first_pkt_col_, QString::number(rtp_stream->setup_frame_number));
+ ti->setText(num_pkts_col_, QString::number(rtp_stream->packet_count));
+
+ ti->setData(stream_data_col_, Qt::UserRole, QVariant::fromValue(audio_stream));
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
+ ti->setTextColor(col, audio_stream->color());
+ }
+
+ connect(ui->playButton, SIGNAL(clicked(bool)), audio_stream, SLOT(startPlaying()));
+ connect(ui->stopButton, SIGNAL(clicked(bool)), audio_stream, SLOT(stopPlaying()));
+
+ connect(audio_stream, SIGNAL(startedPlaying()), this, SLOT(updateWidgets()));
+ connect(audio_stream, SIGNAL(finishedPlaying()), this, SLOT(updateWidgets()));
+ connect(audio_stream, SIGNAL(processedSecs(double)), this, SLOT(setPlayPosition(double)));
+ }
+ audio_stream->addRtpStream(rtp_stream);
+ double start_rel_time = nstime_to_sec(&rtp_stream->start_rel_time);
+ if (tli_count < 2) {
+ start_rel_time_ = start_rel_time;
+ } else {
+ start_rel_time_ = qMin(start_rel_time_, start_rel_time);
+ }
+ // RTP_STREAM_DEBUG("adding stream %s to layout, %u packets, start %u", stream_key.toUtf8().constData(), rtp_stream->packet_count, rtp_stream->start_fd->num);
+}
+
+void RtpPlayerDialog::showEvent(QShowEvent *)
+{
+ QList<int> split_sizes = ui->splitter->sizes();
+ int tot_size = split_sizes[0] + split_sizes[1];
+ int plot_size = tot_size * 3 / 4;
+ split_sizes.clear();
+ split_sizes << plot_size << tot_size - plot_size;
+ ui->splitter->setSizes(split_sizes);
+}
+
+void RtpPlayerDialog::keyPressEvent(QKeyEvent *event)
+{
+ int pan_secs = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
+
+ switch(event->key()) {
+ case Qt::Key_Minus:
+ case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
+ case Qt::Key_O: // GTK+
+ case Qt::Key_R:
+ on_actionZoomOut_triggered();
+ break;
+ case Qt::Key_Plus:
+ case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
+ case Qt::Key_I: // GTK+
+ on_actionZoomIn_triggered();
+ break;
+
+ case Qt::Key_Right:
+ case Qt::Key_L:
+ panXAxis(pan_secs);
+ break;
+ case Qt::Key_Left:
+ case Qt::Key_H:
+ panXAxis(-1 * pan_secs);
+ break;
+
+ case Qt::Key_Space:
+// toggleTracerStyle();
+ break;
+
+ case Qt::Key_0:
+ case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
+ case Qt::Key_Home:
+ on_actionReset_triggered();
+ break;
+
+ case Qt::Key_G:
+ on_actionGoToPacket_triggered();
+ break;
+ case Qt::Key_T:
+// on_actionToggleTimeOrigin_triggered();
+ break;
+ case Qt::Key_Z:
+// on_actionDragZoom_triggered();
+ break;
+ }
+
+ QDialog::keyPressEvent(event);
+}
+
+void RtpPlayerDialog::updateWidgets()
+{
+ bool enable_play = true;
+ bool enable_stop = false;
+
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+
+ RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
+ if (audio_stream->outputState() != QAudio::IdleState) {
+ enable_play = false;
+ enable_stop = true;
+ }
+ }
+
+ ui->playButton->setEnabled(enable_play);
+ ui->stopButton->setEnabled(enable_stop);
+ cur_play_pos_->setVisible(enable_stop);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::graphClicked(QMouseEvent *event)
+{
+ updateWidgets();
+ if (event->button() == Qt::RightButton) {
+ ctx_menu_->exec(event->globalPos());
+ }
+ ui->audioPlot->setFocus();
+}
+
+void RtpPlayerDialog::mouseMoved(QMouseEvent *)
+{
+ int packet_num = getHoveredPacket();
+ QString hint = "<small><i>";
+
+ if (packet_num > 0) {
+ hint += tr("%1. Press \"G\" to to to packet %2")
+ .arg(getHoveredTime())
+ .arg(packet_num);
+ }
+
+ hint += "</i></small>";
+ ui->hintLabel->setText(hint);
+}
+
+void RtpPlayerDialog::resetXAxis()
+{
+ QCustomPlot *ap = ui->audioPlot;
+ QCPRange x_range = ap->xAxis->range();
+
+ double pixel_pad = 10.0; // per side
+
+ ap->rescaleAxes(true);
+
+ double axis_pixels = ap->xAxis->axisRect()->width();
+ ap->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
+
+ axis_pixels = ap->yAxis->axisRect()->height();
+ ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
+
+ ap->replot();
+}
+
+void RtpPlayerDialog::setPlayPosition(double secs)
+{
+ secs+= ui->audioPlot->xAxis->range().lower;
+ double cur_secs = cur_play_pos_->point1->key();
+ if (secs > cur_secs) {
+ cur_play_pos_->point1->setCoords(secs, 0.0);
+ cur_play_pos_->point2->setCoords(secs, 1.0);
+ ui->audioPlot->replot();
+ }
+}
+
+gboolean RtpPlayerDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
+{
+ RtpPlayerDialog *rtp_player_dialog = dynamic_cast<RtpPlayerDialog *>((RtpPlayerDialog*)tapinfo_ptr);
+ if (!rtp_player_dialog) return FALSE;
+
+ const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
+ if (!rtpinfo) return FALSE;
+
+ /* we ignore packets that are not displayed */
+ if (pinfo->fd->flags.passed_dfilter == 0)
+ return FALSE;
+ /* also ignore RTP Version != 2 */
+ else if (rtpinfo->info_version != 2)
+ return FALSE;
+
+ rtp_player_dialog->addPacket(pinfo, rtpinfo);
+
+ return FALSE;
+}
+
+void RtpPlayerDialog::addPacket(packet_info *pinfo, const _rtp_info *rtpinfo)
+{
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+
+ if (row_stream->isMatch(pinfo, rtpinfo)) {
+ row_stream->addRtpPacket(pinfo, rtpinfo);
+ return;
+ }
+ }
+// qDebug() << "=ap no match!" << address_to_qstring(&pinfo->src) << address_to_qstring(&pinfo->dst);
+}
+
+void RtpPlayerDialog::zoomXAxis(bool in)
+{
+ QCustomPlot *ap = ui->audioPlot;
+ double h_factor = ap->axisRect()->rangeZoomFactor(Qt::Horizontal);
+
+ if (!in) {
+ h_factor = pow(h_factor, -1);
+ }
+
+ ap->xAxis->scaleRange(h_factor, ap->xAxis->range().center());
+ ap->replot();
+}
+
+// XXX I tried using seconds but pixels make more sense at varying zoom
+// levels.
+void RtpPlayerDialog::panXAxis(int x_pixels)
+{
+ QCustomPlot *ap = ui->audioPlot;
+ double h_pan;
+
+ h_pan = ap->xAxis->range().size() * x_pixels / ap->xAxis->axisRect()->width();
+ if (x_pixels) {
+ ap->xAxis->moveRange(h_pan);
+ ap->replot();
+ }
+}
+
+void RtpPlayerDialog::on_playButton_clicked()
+{
+ double left = ui->audioPlot->xAxis->range().lower;
+ cur_play_pos_->point1->setCoords(left, 0.0);
+ cur_play_pos_->point2->setCoords(left, 1.0);
+ cur_play_pos_->setVisible(true);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::on_stopButton_clicked()
+{
+ cur_play_pos_->setVisible(false);
+}
+
+void RtpPlayerDialog::on_actionReset_triggered()
+{
+ resetXAxis();
+}
+
+void RtpPlayerDialog::on_actionZoomIn_triggered()
+{
+ zoomXAxis(true);
+}
+
+void RtpPlayerDialog::on_actionZoomOut_triggered()
+{
+ zoomXAxis(false);
+}
+
+void RtpPlayerDialog::on_actionMoveLeft10_triggered()
+{
+ panXAxis(-10);
+}
+
+void RtpPlayerDialog::on_actionMoveRight10_triggered()
+{
+ panXAxis(10);
+}
+
+void RtpPlayerDialog::on_actionMoveLeft1_triggered()
+{
+ panXAxis(-1);
+}
+
+void RtpPlayerDialog::on_actionMoveRight1_triggered()
+{
+ panXAxis(1);
+}
+
+void RtpPlayerDialog::on_actionGoToPacket_triggered()
+{
+ int packet_num = getHoveredPacket();
+ if (packet_num > 0) emit goToPacket(packet_num);
+}
+
+// XXX Make waveform graphs selectable and update the treewidget selection accordingly.
+void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
+{
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ QCPGraph *audio_graph = ti->data(graph_data_col_, Qt::UserRole).value<QCPGraph*>();
+ if (audio_graph) {
+ audio_graph->setSelected(ti->isSelected());
+ }
+ }
+ ui->audioPlot->replot();
+}
+
+double RtpPlayerDialog::getLowestTimestamp()
+{
+ double lowest = QCPRange::maxRange;
+
+ for (int i = 0; i < ui->audioPlot->graphCount(); i++) {
+ QCPGraph *graph = ui->audioPlot->graph(i);
+ if (!graph->visible()) continue;
+ QCPDataMap *dm = graph->data();
+ if (dm->keys().length() < 1) continue;
+ lowest = qMin(lowest, dm->keys().first());
+ }
+ return lowest;
+}
+
+const QString RtpPlayerDialog::getHoveredTime()
+{
+ QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
+ if (!ti) return tr("Unknown");
+
+ QString time_str;
+ double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
+
+ if (ui->todCheckBox->isChecked()) {
+ QDateTime date_time = QDateTime::fromMSecsSinceEpoch(ts * 1000.0);
+ time_str = date_time.toString("yyyy-MM-dd hh:mm:ss.zzz");
+ } else {
+ time_str = QString::number(ts, 'f', 3);
+ time_str += " s";
+ }
+ return time_str;
+}
+
+int RtpPlayerDialog::getHoveredPacket()
+{
+ QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
+ if (!ti) return 0;
+
+ RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
+
+ double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
+
+ return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
+}
+
+void RtpPlayerDialog::on_todCheckBox_toggled(bool)
+{
+ QCPAxis *x_axis = ui->audioPlot->xAxis;
+ double old_lowest = getLowestTimestamp();
+
+ retapPackets(false);
+ x_axis->moveRange(getLowestTimestamp() - old_lowest);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_TELEPHONY_RTP_PLAYER_DIALOG);
+}
+
+#if 0
+// This also serves as a title in RtpAudioFrame.
+static const QString stream_key_tmpl_ = "%1:%2 " UTF8_RIGHTWARDS_ARROW " %3:%4 0x%5";
+const QString RtpPlayerDialog::streamKey(const struct _rtp_stream_info *rtp_stream)
+{
+ const QString stream_key = QString(stream_key_tmpl_)
+ .arg(address_to_display_qstring(&rtp_stream->src_addr))
+ .arg(rtp_stream->src_port)
+ .arg(address_to_display_qstring(&rtp_stream->dest_addr))
+ .arg(rtp_stream->dest_port)
+ .arg(rtp_stream->ssrc, 0, 16);
+ return stream_key;
+}
+
+const QString RtpPlayerDialog::streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo)
+{
+ const QString stream_key = QString(stream_key_tmpl_)
+ .arg(address_to_display_qstring(&pinfo->src))
+ .arg(pinfo->srcport)
+ .arg(address_to_display_qstring(&pinfo->dst))
+ .arg(pinfo->destport)
+ .arg(rtpinfo->info_sync_src, 0, 16);
+ return stream_key;
+}
+#endif
+
+#endif // QT_MULTIMEDIA_LIB
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/qt/rtp_player_dialog.h b/ui/qt/rtp_player_dialog.h
new file mode 100644
index 0000000000..a52544ab1c
--- /dev/null
+++ b/ui/qt/rtp_player_dialog.h
@@ -0,0 +1,136 @@
+/* rtp_player_dialog.h
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef RTP_PLAYER_DIALOG_H
+#define RTP_PLAYER_DIALOG_H
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "ui/rtp_stream.h"
+
+#include "wireshark_dialog.h"
+
+#include <QMap>
+
+namespace Ui {
+class RtpPlayerDialog;
+}
+
+struct _rtp_stream_info;
+
+class QCPItemStraightLine;
+class QMenu;
+class RtpAudioStream;
+
+class RtpPlayerDialog : public WiresharkDialog
+{
+ Q_OBJECT
+
+public:
+ explicit RtpPlayerDialog(QWidget &parent, CaptureFile &cf);
+
+#ifdef QT_MULTIMEDIA_LIB
+ ~RtpPlayerDialog();
+
+ /** Add an RTP stream to play.
+ * MUST be called before exec().
+ *
+ * @param rtp_stream
+ */
+ void addRtpStream(struct _rtp_stream_info *rtp_stream);
+
+public slots:
+
+signals:
+ void goToPacket(int packet_num);
+
+protected:
+ virtual void showEvent(QShowEvent *);
+ virtual void keyPressEvent(QKeyEvent *event);
+
+private slots:
+ /** Retap the capture file, adding RTP packets that match the
+ * streams added using ::addRtpStream and display the dialog.
+ */
+ void retapPackets(bool rescale_axes = true);
+ void updateWidgets();
+ void graphClicked(QMouseEvent *event);
+ void mouseMoved(QMouseEvent *);
+ void resetXAxis();
+
+ void setPlayPosition(double secs);
+ void on_playButton_clicked();
+ void on_stopButton_clicked();
+ void on_actionReset_triggered();
+ void on_actionZoomIn_triggered();
+ void on_actionZoomOut_triggered();
+ void on_actionMoveLeft10_triggered();
+ void on_actionMoveRight10_triggered();
+ void on_actionMoveLeft1_triggered();
+ void on_actionMoveRight1_triggered();
+ void on_actionGoToPacket_triggered();
+ void on_streamTreeWidget_itemSelectionChanged();
+ void on_todCheckBox_toggled(bool checked);
+ void on_buttonBox_helpRequested();
+
+private:
+ Ui::RtpPlayerDialog *ui;
+ QMenu *ctx_menu_;
+ double start_rel_time_;
+ QCPItemStraightLine *cur_play_pos_;
+
+// const QString streamKey(const struct _rtp_stream_info *rtp_stream);
+// const QString streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo);
+
+ // Tap callbacks
+// static void tapReset(void *tapinfo_ptr);
+ static gboolean tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr);
+ static void tapDraw(void *tapinfo_ptr);
+
+ void addPacket(packet_info *pinfo, const struct _rtp_info *rtpinfo);
+ void zoomXAxis(bool in);
+ void panXAxis(int x_pixels);
+ double getLowestTimestamp();
+ const QString getHoveredTime();
+ int getHoveredPacket();
+
+#else // QT_MULTIMEDIA_LIB
+private:
+ Ui::RtpPlayerDialog *ui;
+#endif // QT_MULTIMEDIA_LIB
+};
+
+#endif // RTP_PLAYER_DIALOG_H
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/qt/rtp_player_dialog.ui b/ui/qt/rtp_player_dialog.ui
new file mode 100644
index 0000000000..03d405cce0
--- /dev/null
+++ b/ui/qt/rtp_player_dialog.ui
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RtpPlayerDialog</class>
+ <widget class="QDialog" name="RtpPlayerDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>708</width>
+ <height>400</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>RTP Player</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QCustomPlot" name="audioPlot" native="true"/>
+ <widget class="QTreeWidget" name="streamTreeWidget">
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ <column>
+ <property name="text">
+ <string>Source Address</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Source Port</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Destination Address</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Destination Port</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>SSRC</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>First Packet</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Packets</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Time Span (s)</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Sample Rate (Hz)</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Payloads</string>
+ </property>
+ </column>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="hintLabel">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+
+&lt;h3&gt;Valuable and amazing time-saving keyboard shortcuts&lt;/h3&gt;
+&lt;table&gt;&lt;tbody&gt;
+
+&lt;tr&gt;&lt;th&gt;+&lt;/th&gt;&lt;td&gt;Zoom in&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;-&lt;/th&gt;&lt;td&gt;Zoom out&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;0&lt;/th&gt;&lt;td&gt;Reset graph to its initial state&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;→&lt;/th&gt;&lt;td&gt;Move right 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;←&lt;/th&gt;&lt;td&gt;Move left 10 pixels&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;→&lt;/th&gt;&lt;td&gt;Move right 1 pixel&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;&lt;i&gt;Shift+&lt;/i&gt;←&lt;/th&gt;&lt;td&gt;Move left 1 pixel&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;g&lt;/th&gt;&lt;td&gt;Go to packet under cursor&lt;/td&gt;&lt;/th&gt;
+
+&lt;tr&gt;&lt;th&gt;z&lt;/th&gt;&lt;td&gt;Toggle mouse drag / zoom&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;t&lt;/th&gt;&lt;td&gt;Toggle capture / session time origin&lt;/td&gt;&lt;/th&gt;
+&lt;tr&gt;&lt;th&gt;Space&lt;/th&gt;&lt;td&gt;Toggle crosshairs&lt;/td&gt;&lt;/th&gt;
+
+&lt;/tbody&gt;&lt;/table&gt;
+&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>&lt;small&gt;&lt;i&gt;No audio&lt;/i&gt;&lt;/small&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0,0,0,1,0">
+ <item>
+ <widget class="QToolButton" name="playButton">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="stopButton">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Jitter Buffer:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="doubleSpinBox">
+ <property name="toolTip">
+ <string>The simulated jitter buffer in milliseconds.</string>
+ </property>
+ <property name="maximum">
+ <double>500.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>5.000000000000000</double>
+ </property>
+ <property name="value">
+ <double>50.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Playback Timing:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBox">
+ <property name="toolTip">
+ <string>&lt;strong&gt;Jitter Buffer&lt;/strong&gt;: Use jitter buffer to simulate the RTP stream as heard by the end user.
+&lt;br/&gt;
+&lt;strong&gt;RTP Timestamp&lt;/strong&gt;: Use RTP Timestamp instead of the arriving packet time. This will not reproduce the RTP stream as the user heard it, but is useful when the RTP is being tunneled and the original packet timing is missing.
+&lt;br/&gt;
+&lt;strong&gt;Uniterrupted Mode&lt;/strong&gt;: Ignore the RTP Timestamp. Play the stream as it is completed. This is useful when the RTP timestamp is missing.</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>Jitter Buffer</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>RTP Timestamp</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Uninterrupted Mode</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="todCheckBox">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;View the timestamps as time of day (checked) or seconds since beginning of capture (unchecked).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Time of Day</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close|QDialogButtonBox::Help</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <action name="actionReset">
+ <property name="text">
+ <string>Reset Graph</string>
+ </property>
+ <property name="toolTip">
+ <string>Reset the graph to its initial state.</string>
+ </property>
+ <property name="shortcut">
+ <string>0</string>
+ </property>
+ </action>
+ <action name="actionZoomIn">
+ <property name="text">
+ <string>Zoom In</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom In</string>
+ </property>
+ <property name="shortcut">
+ <string>+</string>
+ </property>
+ </action>
+ <action name="actionZoomOut">
+ <property name="text">
+ <string>Zoom Out</string>
+ </property>
+ <property name="toolTip">
+ <string>Zoom Out</string>
+ </property>
+ <property name="shortcut">
+ <string>-</string>
+ </property>
+ </action>
+ <action name="actionMoveLeft10">
+ <property name="text">
+ <string>Move Left 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move Left 10 Pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Left</string>
+ </property>
+ </action>
+ <action name="actionMoveRight10">
+ <property name="text">
+ <string>Move Right 10 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move Right 10 Pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Right</string>
+ </property>
+ </action>
+ <action name="actionMoveLeft1">
+ <property name="text">
+ <string>Move Left 1 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move Left 1 Pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Left</string>
+ </property>
+ </action>
+ <action name="actionMoveRight1">
+ <property name="text">
+ <string>Move Right 1 Pixels</string>
+ </property>
+ <property name="toolTip">
+ <string>Move Right 1 Pixels</string>
+ </property>
+ <property name="shortcut">
+ <string>Shift+Right</string>
+ </property>
+ </action>
+ <action name="actionGoToPacket">
+ <property name="text">
+ <string>Go To Packet Under Cursor</string>
+ </property>
+ <property name="toolTip">
+ <string>Go to packet currently under the cursor</string>
+ </property>
+ <property name="shortcut">
+ <string>G</string>
+ </property>
+ </action>
+ <action name="actionDragZoom">
+ <property name="text">
+ <string>Drag / Zoom</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle mouse drag / zoom behavior</string>
+ </property>
+ <property name="shortcut">
+ <string>Z</string>
+ </property>
+ </action>
+ <action name="actionToggleTimeOrigin">
+ <property name="text">
+ <string>Capture / Session Time Origin</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle capture / session time origin</string>
+ </property>
+ <property name="shortcut">
+ <string>T</string>
+ </property>
+ </action>
+ <action name="actionCrosshairs">
+ <property name="text">
+ <string>Crosshairs</string>
+ </property>
+ <property name="toolTip">
+ <string>Toggle crosshairs</string>
+ </property>
+ <property name="shortcut">
+ <string>Space</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QCustomPlot</class>
+ <extends>QWidget</extends>
+ <header>qcustomplot.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>RtpPlayerDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>RtpPlayerDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/ui/qt/voip_calls_dialog.cpp b/ui/qt/voip_calls_dialog.cpp
index e530993c56..b18fd91537 100644
--- a/ui/qt/voip_calls_dialog.cpp
+++ b/ui/qt/voip_calls_dialog.cpp
@@ -27,9 +27,11 @@
#include "epan/addr_resolv.h"
#include "epan/dissectors/packet-h225.h"
+#include "ui/rtp_stream.h"
#include "ui/utf8_entities.h"
#include "qt_ui_utils.h"
+#include "rtp_player_dialog.h"
#include "sequence_dialog.h"
#include "stock_icon.h"
#include "wireshark_application.h"
@@ -62,37 +64,47 @@ const int comments_col_ = 8;
Q_DECLARE_METATYPE(voip_calls_info_t*)
+enum { voip_calls_type_ = 1000 };
+
class VoipCallsTreeWidgetItem : public QTreeWidgetItem
{
public:
- VoipCallsTreeWidgetItem(QTreeWidget *tree, voip_calls_info_t *call_info) : QTreeWidgetItem(tree) {
- setData(0, Qt::UserRole, qVariantFromValue(call_info));
+ VoipCallsTreeWidgetItem(QTreeWidget *tree, voip_calls_info_t *call_info) :
+ QTreeWidgetItem(tree, voip_calls_type_),
+ call_info_(call_info)
+ {
drawData();
}
+ voip_calls_info_t *callInfo() {
+ // XXX Not needed? We explicitly pass selected conversations to RtpPlayerDialog.
+// call_info_->selected = isSelected() ? TRUE : FALSE;
+ return call_info_;
+ }
+
void drawData() {
- voip_calls_info_t *call_info = data(0, Qt::UserRole).value<voip_calls_info_t*>();
- if (!call_info) {
+ if (!call_info_) {
+ setText(start_time_col_, QObject::tr("Error"));
return;
}
// XXX Pull digit count from capture file precision
- setText(start_time_col_, QString::number(nstime_to_sec(&(call_info->start_rel_ts)), 'f', 6));
- setText(stop_time_col_, QString::number(nstime_to_sec(&(call_info->stop_rel_ts)), 'f', 6));
- setText(initial_speaker_col_, address_to_display_qstring(&(call_info->initial_speaker)));
- setText(from_col_, call_info->from_identity);
- setText(to_col_, call_info->to_identity);
- setText(protocol_col_, ((call_info->protocol == VOIP_COMMON) && call_info->protocol_name) ?
- call_info->protocol_name : voip_protocol_name[call_info->protocol]);
- setText(packets_col_, QString::number(call_info->npackets));
- setText(state_col_, voip_call_state_name[call_info->call_state]);
+ setText(start_time_col_, QString::number(nstime_to_sec(&(call_info_->start_rel_ts)), 'f', 6));
+ setText(stop_time_col_, QString::number(nstime_to_sec(&(call_info_->stop_rel_ts)), 'f', 6));
+ setText(initial_speaker_col_, address_to_display_qstring(&(call_info_->initial_speaker)));
+ setText(from_col_, call_info_->from_identity);
+ setText(to_col_, call_info_->to_identity);
+ setText(protocol_col_, ((call_info_->protocol == VOIP_COMMON) && call_info_->protocol_name) ?
+ call_info_->protocol_name : voip_protocol_name[call_info_->protocol]);
+ setText(packets_col_, QString::number(call_info_->npackets));
+ setText(state_col_, voip_call_state_name[call_info_->call_state]);
/* Add comments based on the protocol */
QString call_comments;
- switch (call_info->protocol) {
+ switch (call_info_->protocol) {
case VOIP_ISUP:
{
- isup_calls_info_t *isup_info = (isup_calls_info_t *)call_info->prot_info;
+ isup_calls_info_t *isup_info = (isup_calls_info_t *)call_info_->prot_info;
call_comments = QString("%1-%2 %3 %4-%5")
.arg(isup_info->ni)
.arg(isup_info->opc)
@@ -103,11 +115,11 @@ public:
break;
case VOIP_H323:
{
- h323_calls_info_t *h323_info = (h323_calls_info_t *)call_info->prot_info;
+ h323_calls_info_t *h323_info = (h323_calls_info_t *)call_info_->prot_info;
gboolean flag = FALSE;
static const QString on_str = QObject::tr("On");
static const QString off_str = QObject::tr("Off");
- if (call_info->call_state == VOIP_CALL_SETUP) {
+ if (call_info_->call_state == VOIP_CALL_SETUP) {
flag = h323_info->is_faststart_Setup;
} else {
if ((h323_info->is_faststart_Setup) && (h323_info->is_faststart_Proc)) {
@@ -121,7 +133,7 @@ public:
break;
case VOIP_COMMON:
default:
- call_comments = call_info->call_comment;
+ call_comments = call_info_->call_comment;
break;
}
setText(comments_col_, call_comments);
@@ -129,24 +141,25 @@ public:
bool operator< (const QTreeWidgetItem &other) const
{
- voip_calls_info_t *this_call_info = data(0, Qt::UserRole).value<voip_calls_info_t*>();
- voip_calls_info_t *other_call_info = other.data(0, Qt::UserRole).value<voip_calls_info_t*>();
- if (!this_call_info || !other_call_info) {
- return false;
+ if (other.type() != voip_calls_type_) return QTreeWidgetItem::operator< (other);
+ const VoipCallsTreeWidgetItem *other_row = static_cast<const VoipCallsTreeWidgetItem *>(&other);
+
+ if (!call_info_ || !other_row->call_info_) {
+ return QTreeWidgetItem::operator< (other);
}
switch (treeWidget()->sortColumn()) {
case start_time_col_:
- return nstime_cmp(&(this_call_info->start_rel_ts), &(other_call_info->start_rel_ts)) < 0;
+ return nstime_cmp(&(call_info_->start_rel_ts), &(other_row->call_info_->start_rel_ts)) < 0;
break;
case stop_time_col_:
- return nstime_cmp(&(this_call_info->stop_rel_ts), &(other_call_info->stop_rel_ts)) < 0;
+ return nstime_cmp(&(call_info_->stop_rel_ts), &(other_row->call_info_->stop_rel_ts)) < 0;
break;
case initial_speaker_col_:
- return cmp_address(&(this_call_info->initial_speaker), &(other_call_info->initial_speaker)) < 0;
+ return cmp_address(&(call_info_->initial_speaker), &(other_row->call_info_->initial_speaker)) < 0;
break;
case packets_col_:
- return this_call_info->npackets < other_call_info->npackets;
+ return call_info_->npackets < other_row->call_info_->npackets;
break;
default:
break;
@@ -155,7 +168,8 @@ public:
// Fall back to string comparison
return QTreeWidgetItem::operator <(other);
}
-
+private:
+ voip_calls_info_t *call_info_;
};
VoipCallsDialog::VoipCallsDialog(QWidget &parent, CaptureFile &cf, bool all_flows) :
@@ -240,7 +254,7 @@ void VoipCallsDialog::changeEvent(QEvent *event)
gboolean VoipCallsDialog::tapPacket(void *, packet_info *, epan_dissect_t *, const void *)
{
-#ifdef QT_MULTIMEDIAWIDGETS_LIB
+#ifdef QT_MULTIMEDIA_LIB
// voip_calls_tapinfo_t *tapinfo = (voip_calls_tapinfo_t *) tapinfo_ptr;
// add_rtp_packet for voip player.
// return TRUE;
@@ -256,6 +270,19 @@ void VoipCallsDialog::tapDraw(void *tapinfo_ptr)
return;
}
+ GList *graph_item = g_queue_peek_nth_link(tapinfo->graph_analysis->items, 0);
+ for (; graph_item; graph_item = g_list_next(graph_item)) {
+ for (GList *rsi_entry = g_list_first(tapinfo->rtp_stream_list); rsi_entry; rsi_entry = g_list_next(rsi_entry)) {
+ seq_analysis_item_t * sai = (seq_analysis_item_t *)graph_item->data;
+ rtp_stream_info_t *rsi = (rtp_stream_info_t *)rsi_entry->data;
+
+ if (rsi->start_fd->num == sai->fd->num) {
+ rsi->call_num = sai->conv_num;
+ // VOIP_CALLS_DEBUG("setting conv num %u for frame %u", sai->conv_num, sai->fd->num);
+ }
+ }
+ }
+
VoipCallsDialog *voip_calls_dialog = static_cast<VoipCallsDialog *>(tapinfo->tap_data);
if (voip_calls_dialog) {
voip_calls_dialog->updateCalls();
@@ -306,7 +333,7 @@ void VoipCallsDialog::updateWidgets()
}
prepare_button_->setEnabled(selected && have_ga_items);
sequence_button_->setEnabled(selected && have_ga_items);
-#if defined(QT_MULTIMEDIAWIDGETS_LIB) && 0 // We don't have a playback dialog yet.
+#if defined(QT_MULTIMEDIA_LIB)
player_button_->setEnabled(selected && have_ga_items);
#else
player_button_->setEnabled(false);
@@ -456,6 +483,35 @@ void VoipCallsDialog::showSequence()
sequence_dialog->show();
}
+void VoipCallsDialog::showPlayer()
+{
+#ifdef QT_MULTIMEDIA_LIB
+ RtpPlayerDialog rtp_player_dialog(*this, cap_file_);
+
+ foreach (QTreeWidgetItem *ti, ui->callTreeWidget->selectedItems()) {
+ VoipCallsTreeWidgetItem *vc_ti = static_cast<VoipCallsTreeWidgetItem *>(ti);
+ for (GList *rsi_entry = g_list_first(tapinfo_.rtp_stream_list); rsi_entry; rsi_entry = g_list_next(rsi_entry)) {
+ rtp_stream_info_t *rsi = (rtp_stream_info_t *)rsi_entry->data;
+ if (!rsi) continue;
+
+ //VOIP_CALLS_DEBUG("checking call %u, start frame %u == stream call %u, start frame %u, setup frame %u",
+ // vc_ti->callInfo()->call_num, vc_ti->callInfo()->start_fd->num,
+ // rsi->call_num, rsi->start_fd->num, rsi->setup_frame_number);
+ if (vc_ti->callInfo()->call_num == rsi->call_num) {
+ //VOIP_CALLS_DEBUG("adding call number %u", vc_ti->callInfo()->call_num);
+ rtp_player_dialog.addRtpStream(rsi);
+ }
+ }
+ }
+
+ connect(&rtp_player_dialog, SIGNAL(goToPacket(int)), this, SIGNAL(goToPacket(int)));
+
+ // XXX This retaps the packet list. We still have our own tap listener
+ // registered at this point.
+ rtp_player_dialog.exec();
+#endif // QT_MULTIMEDIA_LIB
+}
+
void VoipCallsDialog::on_callTreeWidget_itemActivated(QTreeWidgetItem *item, int)
{
voip_calls_info_t *call_info = item->data(0, Qt::UserRole).value<voip_calls_info_t*>();
@@ -481,6 +537,8 @@ void VoipCallsDialog::on_buttonBox_clicked(QAbstractButton *button)
prepareFilter();
} else if (button == sequence_button_) {
showSequence();
+ } else if (button == player_button_) {
+ showPlayer();
}
}
diff --git a/ui/qt/voip_calls_dialog.h b/ui/qt/voip_calls_dialog.h
index 55245f4d74..0b4d567878 100644
--- a/ui/qt/voip_calls_dialog.h
+++ b/ui/qt/voip_calls_dialog.h
@@ -82,6 +82,7 @@ private:
void updateWidgets();
void prepareFilter();
void showSequence();
+ void showPlayer();
private slots:
void captureFileClosing();
diff --git a/ui/qt/voip_calls_dialog.ui b/ui/qt/voip_calls_dialog.ui
index 27ec107427..d4d94ea541 100644
--- a/ui/qt/voip_calls_dialog.ui
+++ b/ui/qt/voip_calls_dialog.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>Dialog</string>
+ <string>VoIP Calls</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>