VoIP dialogs: Improvements and new functions

Changes:
- RTP Player added to Telephony/RTP menu.
- When openning RTP Analysis or RTP Player from RTP menu, just selected
  stream is added. When Ctrl is hold during opening, reverse stream is
searched and added too.
- RTP Player: Added tool to select/deselect all inaudible streams
- RTP Player: Added Prepare Filter button
- RTP Player: Added Analyze button
- RTP Analysis: Added Prepare Filter button
- documentation updated

Code changes:
- RTP Player::rescanPacket() is not fired multiple times during rate change and during dialog creation
- Error shown in RTP player is cleared after every new decode of streams
- RTP Player handles case when Qt do not emit stop stream event
- "Select" menu code unified between dialogs>
- RTP Player: Audio routing menu unified
- buttons are connected to actions by signals()
- Analyze dialog is called by list of rtpstream_id, not rtpstream_info
This commit is contained in:
Jirka Novak 2021-04-14 15:47:07 +02:00 committed by Wireshark GitLab Utility
parent 212ff30603
commit c8479e41ae
31 changed files with 930 additions and 420 deletions

View File

@ -155,6 +155,7 @@ set(WSUG_GRAPHICS
wsug_graphics/ws-filter-toolbar.png
wsug_graphics/ws-filters.png # GTK+
wsug_graphics/ws-find-packet.png
wsug_graphics/ws-follow-sip-stream.png
wsug_graphics/ws-follow-stream.png
wsug_graphics/ws-follow-http2-stream.png
wsug_graphics/ws-go-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

@ -22,8 +22,8 @@ display filter to show only the packets in a TLS or SSL stream. If so,
Wiresharks ability to follow protocol streams will be useful to you.
To filter to a particular stream,
select a TCP, UDP, DCCP, TLS, HTTP, HTTP/2 or QUIC packet in the packet list of the stream/connection you are
interested in and then select the menu item menu:Analyze[Follow TCP Stream]
select a TCP, UDP, DCCP, TLS, HTTP, HTTP/2, QUIC or SIP packet in the packet list of the stream/connection you are
interested in and then select the menu item menu:Analyze[Follow > TCP Stream]
(or use the context menu in the packet list). Wireshark will set an
appropriate display filter and display a dialog box with the data from the
stream laid out, as shown in <<ChAdvFollowStream>>.
@ -118,6 +118,12 @@ TCP connection. The “Stream” selector determines the TCP connection whereas
The QUIC protocol is similar, the first number selects the UDP stream index
while the "Substream" field selects the QUIC Stream ID.
.The “Follow SIP Call” dialog box
image::wsug_graphics/ws-follow-sip-stream.png[{screenshot-attrs}]
The SIP call is shown with same dialog, just filter is based on sip.Call-ID
field. Count of streams is fixed to 0 and the field is disabled.
[[ChAdvShowPacketBytes]]
=== Show Packet Bytes

View File

@ -21,9 +21,11 @@ specific protocols and might be described in a later version of this document.
Some of these statistics are described at the
{wireshark-wiki-url}Statistics pages.
[[ChTelPlayingCalls]]
=== Playing VoIP Calls
The tool for playing VoIP calls is called RTP Player. It shows RTP streams and its waveforms, allows play stream and export it as audio or payload to file.
The tool for playing VoIP calls is called <<ChTelRtpPlayer,RTP Player>>. It shows RTP streams and its waveforms, allows play stream and export it as audio or payload to file. Its capabilites depends on supported codecs.
==== Supported codecs
@ -58,7 +60,7 @@ btn:[Remove from playlist] is useful e. g. in case user selected all RTP streams
Tools below can be used to maintain content of playlist, they contain btn:[Play Streams] button. You can use one of procedures (Note: btn:[Add to playlist] action is demonstrated):
* Open menu:Telephony[RTP > RTP Streams] window, it will show all streams in the capture. Select one or more streams and then press btn:[Play Streams]. Selected streams are added to playlist.
* Select any RTP packet in packet list, open menu:Telephony[RTP > Stream Analysis] window. It will show analysis of selected forward stream and its reverse stream, if any. Then press btn:[Play Streams]. Forward and reverse stream is added to playlist.
* Select any RTP packet in packet list, open menu:Telephony[RTP > Stream Analysis] window. It will show analysis of selected forward stream and its reverse stream (if btn:[Ctrl] is pressed during window openning). Then press btn:[Play Streams]. Forward and reverse stream is added to playlist.
** menu:RTP Stream Analysis[] window can be opened from other tools too.
* Open menu:Telephony[VoIP Calls] or menu:Telephony[SIP Flows] window, it will show all calls. Select one or more calls and then press btn:[Play Streams]. It will add all RTP streams related to selected calls to playlist.
* Open btn:[Flow Sequence] window in menu:Telephony[VoIP Calls] or menu:Telephony[SIP Flows] window, it will show flow sequence of calls. Select any RTP stream and then press btn:[Play Streams]. It will add selected RTP stream to playlist.
@ -71,123 +73,6 @@ image::wsug_graphics/ws-tel-playlist.png[]
Same approach with set/add/remove actions is used for RTP Stream Analysis window. The playlist is there handled as different tabs in the window, see <<ChTelRTPAnalysis,RTP Stream Analysis>> window.
====
==== RTP Player Window
[[ChTelRtpPlayer]]
.RTP Player window
image::wsug_graphics/ws-tel-rtp-player_1.png[{screenshot-attrs}]
RTP Player Window consists of three parts:
. Waveform view
. Playlist
. Controls
Waveform view shows visual presentation of RTP stream. Color of waveform and playlist row is matching. Height of wave shows volume.
Waveform shows error marks for Out of Sequence, Jitter Drops, Wrong Timestamps and Inserted Silence marks if it happens in a stream.
.Waveform with error marks
image::wsug_graphics/ws-tel-rtp-player_3.png[{screenshot-attrs}]
Playlist shows information about every stream:
* Play - Audio routing
* Source Address, Source Port, Destination Address, Destination Port, SSRC
* Setup Frame
** SETUP <number> is shown, when there is known signaling packet. Number is packet number of signaling packet. Note: Word SETUP is shown even RTP stream was initiated e. g. by SKINNY where no SETUP message exists.
** RTP <number> is shown, when no related signaling was found. Number is packet number of first packet of the stream.
* Packets - Count of packets in the stream.
* Time Span - Start - Stop (Duration) of the stream
* SR - Sample rate of used codec
* PR - Decoded play rate used for stream playing
* Payloads - One or more playload types used by the stream
[NOTE]
====
When rtp_udp is active, most of streams shows just RTP <number> even there is setup frame in capture.
When RTP stream contains multiple codecs, SR and PR is based on first observed coded. Later codecs in stream are resampled to first one.
====
Controls allow a user to:
* btn:[Start]/btn:[Pause]/btn:[Stop] playing of unmuted streams
* Select btn:[Output audio device] and btn:[Output audio rate]
* Select btn:[Playback Timing]
** Jitter Buffer - Packets outside btn:[Jitter Buffer] size are discarded during decoding
** RTP Timestamp - Packets are ordered and played by its Timestamp, no Jitter Buffer is used
** Uninterrupted Mode - All gaps (e. g. Comfort Noise, lost packets) are discarded therefore audio is shorted than timespan
* btn:[Time of Day] selects whether waveform timescale is shown in seconds from start of capture or in absolute time of received packets
* btn:[Export] - See <<tel-rtp-export>>.
.RTP stream state indication
image::wsug_graphics/ws-tel-rtp-player_2.png[{screenshot-attrs}]
Waveform view and playlist shows state of a RTP stream:
. stream is muted (dashed waveform, menu:Muted[] is shown in Play column) or unmuted (non-dashed waveform, audio routing is shown in Play column)
. stream is selected (blue waveform, blue row)
. stream is below mouse cursor (bold waveform, bold font)
User can control to where audio of a stream is routed to:
* L - Left channel
* L+R - Left and Right (Middle) channel
* R - Left channel
* P - Play (when mono soundcard is available only)
* M - Muted
Audio routing can be changed by double clicking on first column of a row, by shortcut or by menu.
User can use shortcuts:
* Selection
** kbd:[Ctrl + A] - Select all streams
** kbd:[Ctrl + I] - Invert selection
** kbd:[Ctrl + Shift + A] - Select none
** Note: Common kbd:[Mouse click], kbd:[Shift + Mouse click] and kbd:[Ctrl + Mouse click] works too
* Go to packet
** kbd:[G] - Go to packet of stream under the mouse cursor
** kbd:[Shift + G] - Go to setup packet of stream under the mouse cursor
* Audio routing
** kbd:[M] - Mute
** kbd:[Shift + M] - Unmute
** kbd:[Ctrl + M] - Invert muting
* kbd:[P] - Play audio
* kbd:[S] - Stop playing
* kbd:[Del] or kbd:[Ctrl + X] - Remove stream from playlist
[[tel-rtp-export]]
===== Export
[NOTE]
====
menu:Export[] was moved from menu:RTP Stream Analysis[] window to menu:RTP Player[] window in 3.5.0.
Wireshark is able to export decoded audio in .au or .wav file format. Prior to version 3.2.0, Wireshark only supports exporting audio using the G.711 codec. From 3.2.0 it supports audio export using any codec with 8000 Hz sampling. From 3.5.0 is supported export of any codec, rate is defined by Output Audio Rate.
====
Export options available:
* for one or more selected non-muted streams
* Stream Synchronized Audio - streams are synchronized to earliest stream in export (there is no silence at beginning of it)
* File Synchronized Audio - streams starts at beginning of file, therefore silence can be at start of file
* for just one selected stream
* Payload - just payload with no information about coded is stored in the file
Audio is exported as multi-channel file - one channel per RTP stream. One or two channels are equal to mono or stereo, but Wireshark can export e g. 100 channels. For later playing a tool with multi-channel support must be used (e.g. https://www.audacityteam.org/).
Payload export is useful for codecs not supported by Wireshark.
[NOTE]
====
Default value of btn:[Output Audio Rate] is btn:[Automatic]. When multiple codecs with different codec rates are captured, Wireshark decodes each stream with its own play audio rate. Therefore each stream can has different play audio rate. When export of audio is used in this case, it will fail because .au or .wav requires one common play audio rate.
In this case user must manually select one of rates in btn:[Output Audio Rate], streams will be resampled and audio export succeeds.
====
==== RTP Decoding Settings
RTP is carried usually in UDP packets, on random source and destination port. Therefore without "help" Wireshark can't recognize it and shows just UDP packets. Wireshark recognizes RTP streams based on VoIP signaling, e. g. based on SDP message in SIP signaling. When signaling is not captured, Wireshark shows just UDP packets. There are multiple settings which helps Wireshark to recognize RTP even there is no related signaling.
@ -250,7 +135,7 @@ Available controls are:
* btn:[Time of Day] switches format of shown time between relative to start of capture or absolute time of received packets.
* btn:[Flow Sequence] opens <<ChStatFlowGraph,Flow Sequence>> window and shows selected calls in it.
* btn:[Prepare Filter] generates display filter matching to selected calls (signaling and RTP streams) and apply it.
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window.
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window. Actions btn:[Set], btn:[Add] and btn:[Remove] are available.
* btn:[Copy] copies information from table to clipboard in CSV or YAML.
[[ChTelANSI]]
@ -372,7 +257,10 @@ User can use shortcuts:
** kbd:[Ctrl + I] - Invert selection
** kbd:[Ctrl + Shift + A] - Select none
** Note: Common kbd:[Mouse click], kbd:[Shift + Mouse click] and kbd:[Ctrl + Mouse click] works too
* kbd:[R] - Try search for reverse stream. If found, selects it in the list.
* Find Reverse
** kbd:[R] - Try search for reverse streams related to already selected streams. If found, selects them in the list too.
** btn:[Shift+R] - Select all pair streams (forward/reverse relation).
** btn:[Ctrl+R] - Select all single streams (no reverse stream does exist).
* kbd:[G] - Go to packet of stream under the mouse cursor.
* kbd:[M] - Mark all packets of selected streams.
* kbd:[P] - Prepare filter matching selected streams and apply it.
@ -381,10 +269,13 @@ User can use shortcuts:
Available controls are:
* btn:[Find Reverse] tries to search for reverse stream. If found, selects it in the list.
* btn:[Analyze] opens <<ChTelRTPAnalysis,RTP Stream Analysis>> window.
* Find Reverse
** btn:[Find Reverse] search for reverse stream of every selected stream. If found, selects it in the list too.
** btn:[Find All Pairs] select all streams which have forward/reverse relation.
** btn:[Find Only Single] select all streams which are single - have no reverse stream.
* btn:[Analyze] opens <<ChTelRTPAnalysis,RTP Stream Analysis>> window. Actions btn:[Set], btn:[Add] and btn:[Remove] are available.
* btn:[Prepare Filter] prepares filter matching selected streams and apply it.
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window.
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window. Actions btn:[Set], btn:[Add] and btn:[Remove] are available.
* btn:[Copy] copies information from table to clipboard in CSV or YAML.
* btn:[Export] exports selected streams in RTPDump format.
@ -395,7 +286,9 @@ Available controls are:
The RTP analysis function takes the selected RTP streams and generates a list of statistics on it including graph.
Every stream is shown on own tab. Tabs are counted as streams are added. When tab is closed, number is not reused. Color of tab matches color of graphs on graph tab.
Menu menu:Telephony[RTP > RTP Stream Analysis] is enabled only when selected packed is RTP packet. When window is opened, selected RTP stream is added to analysis. If btn:[Ctrl] is pressed during menu opening, reverse RTP stream (if exists) is added to the window too.
Every stream is shown on own tab. Tabs are numbered as streams are added and its tooltip shows identification of the stream. When tab is closed, number is not reused. Color of tab matches color of graphs on graph tab.
.The “RTP Stream Analysis” window
image::wsug_graphics/ws-tel-rtpstream-analysis_1.png[{screenshot-attrs}]
@ -439,7 +332,10 @@ Available shortcuts are:
Available controls are:
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window.
* Prepare Filter
** btn:[Current Tab] prepares filter matching current tab and applies it.
** btn:[All Tabs] prepares filter matching all tabs and applies it.
* btn:[Play Streams] opens <<ChTelRtpPlayer,RTP Player>> window. Actions btn:[Set], btn:[Add] and btn:[Remove] are available.
* btn:[Export] allows export current stream or all streams as CSV or export graph as image in multiple different formats (PDF, PNG, BMP and JPEG).
.Graph in “RTP Stream Analysis” window
@ -448,7 +344,7 @@ image::wsug_graphics/ws-tel-rtpstream-analysis_2.png[{screenshot-attrs}]
Graph view shows graph of:
* jitter
* difference - difference between expected and real time of packet arrival
* difference - absolute value of difference between expected and real time of packet arrival
* delta - time difference from reception of previous packet
for every stream. Checkboxes below graph are enabling or disabling showing of a graph for every stream. btn:[Stream X] checkbox enables or disables all graphs for the stream.
@ -458,6 +354,136 @@ for every stream. Checkboxes below graph are enabling or disabling showing of a
Stream Analysis window contained tool for save audio and payload for analyzed streams. This tool was moved in Wireshark 3.5.0 to <<ChTelRtpPlayer,RTP Player>> window. New tool has more features.
====
[[ChTelRtpPlayer]]
==== RTP Player Window
The RTP Player function is tool for playing VoIP calls. It shows RTP streams and its waveforms, allows play stream and export it as audio or payload to file. See related concepts in <<ChTelPlayingCalls>>.
Menu menu:Telephony[RTP > RTP Player] is enabled only when selected packed is RTP packet. When window is opened, selected RTP stream is added to playlist. If btn:[Ctrl] is pressed during menu opening, reverse RTP stream (if exists) is added to the playlist too.
.RTP Player window
image::wsug_graphics/ws-tel-rtp-player_1.png[{screenshot-attrs}]
RTP Player Window consists of three parts:
. Waveform view
. Playlist
. Controls
Waveform view shows visual presentation of RTP stream. Color of waveform and playlist row are matching. Height of wave shows volume.
Waveform shows error marks for Out of Sequence, Jitter Drops, Wrong Timestamps and Inserted Silence marks if it happens in a stream.
.Waveform with error marks
image::wsug_graphics/ws-tel-rtp-player_3.png[{screenshot-attrs}]
Playlist shows information about every stream:
* Play - Audio routing
* Source Address, Source Port, Destination Address, Destination Port, SSRC
* Setup Frame
** SETUP <number> is shown, when there is known signaling packet. Number is packet number of signaling packet. Note: Word SETUP is shown even RTP stream was initiated e. g. by SKINNY where no SETUP message exists.
** RTP <number> is shown, when no related signaling was found. Number is packet number of first packet of the stream.
* Packets - Count of packets in the stream.
* Time Span - Start - Stop (Duration) of the stream
* SR - Sample rate of used codec
* PR - Decoded play rate used for stream playing
* Payloads - One or more playload types used by the stream
[NOTE]
====
When rtp_udp is active, most of streams shows just RTP <number> even there is setup frame in capture.
When RTP stream contains multiple codecs, SR and PR is based on first observed coded. Later codecs in stream are resampled to first one.
====
Controls allow a user to:
* btn:[Start]/btn:[Pause]/btn:[Stop] playing of unmuted streams
* Select btn:[Output audio device] and btn:[Output audio rate]
* Select btn:[Playback Timing]
** Jitter Buffer - Packets outside btn:[Jitter Buffer] size are discarded during decoding
** RTP Timestamp - Packets are ordered and played by its Timestamp, no Jitter Buffer is used
** Uninterrupted Mode - All gaps (e. g. Comfort Noise, lost packets) are discarded therefore audio is shorted than timespan
* btn:[Time of Day] selects whether waveform timescale is shown in seconds from start of capture or in absolute time of received packets
* Inaudible streams
** btn:[Select] select all inaudible streams (streams with zero play rate)
** btn:[Deselect] deselect all inaudible streams (streams with zero play rate)
* btn:[Analyze] open <<ChTelRTPAnalysis,RTP Stream Analysis>> window. Actions btn:[Set], btn:[Add] and btn:[Remove] are available.
* btn:[Prepare Filter] prepare filter matching selected streams and apply it.
* btn:[Export] - See <<tel-rtp-export>>.
.RTP stream state indication
image::wsug_graphics/ws-tel-rtp-player_2.png[{screenshot-attrs}]
Waveform view and playlist shows state of a RTP stream:
. stream is muted (dashed waveform, menu:Muted[] is shown in Play column) or unmuted (non-dashed waveform, audio routing is shown in Play column)
. stream is selected (blue waveform, blue row)
. stream is below mouse cursor (bold waveform, bold font)
User can control to where audio of a stream is routed to:
* L - Left channel
* L+R - Left and Right (Middle) channel
* R - Left channel
* P - Play (when mono soundcard is available only)
* M - Muted
Audio routing can be changed by double clicking on first column of a row, by shortcut or by menu.
User can use shortcuts:
* Selection
** kbd:[Ctrl + A] - Select all streams
** kbd:[Ctrl + I] - Invert selection
** kbd:[Ctrl + Shift + A] - Select none
** Note: Common kbd:[Mouse click], kbd:[Shift + Mouse click] and kbd:[Ctrl + Mouse click] works too
* Go to packet
** kbd:[G] - Go to packet of stream under the mouse cursor
** kbd:[Shift + G] - Go to setup packet of stream under the mouse cursor
* Audio routing
** kbd:[M] - Mute all selected streams
** kbd:[Shift + M] - Unmute all selected streams
** kbd:[Ctrl + M] - Invert muting of all selected streams
* kbd:[P] - Play audio
* kbd:[S] - Stop playing
* kbd:[Del] or kbd:[Ctrl + X] - Remove all selected streams from playlist
* Inaudible steams
** kbd:[N] - Select all inaudible streams
** kbd:[Shift + N] - Deselect all inaudible streams
[[tel-rtp-export]]
===== Export
[NOTE]
====
menu:Export[] was moved from menu:RTP Stream Analysis[] window to menu:RTP Player[] window in 3.5.0.
Wireshark is able to export decoded audio in .au or .wav file format. Prior to version 3.2.0, Wireshark only supported exporting audio using the G.711 codec. From 3.2.0 it supports audio export using any codec with 8000 Hz sampling. From 3.5.0 is supported export of any codec, rate is defined by Output Audio Rate.
====
Export options available:
* for one or more selected non-muted streams
** Stream Synchronized Audio - streams are synchronized to earliest stream in export (there is no silence at beginning of it)
** File Synchronized Audio - streams starts at beginning of file, therefore silence can be at start of file
* for just one selected stream
** Payload - just payload with no information about coded is stored in the file
Audio is exported as multi-channel file - one channel per RTP stream. One or two channels are equal to mono or stereo, but Wireshark can export e g. 100 channels. For later playing a tool with multi-channel support must be used (e.g. https://www.audacityteam.org/).
Export of payload function is useful for codecs not supported by Wireshark.
[NOTE]
====
Default value of btn:[Output Audio Rate] is btn:[Automatic]. When multiple codecs with different codec rates are captured, Wireshark decodes each stream with its own play audio rate. Therefore each stream can has different play audio rate. When export of audio is used in this case, it will fail because .au or .wav requires one common play audio rate.
In this case user must manually select one of rates in btn:[Output Audio Rate], streams will be resampled and audio export succeeds.
====
[[ChTelRTSP]]
=== RTSP Window

View File

@ -26,6 +26,7 @@ DIAG_ON(frame-larger-than=)
#include <epan/stats_tree_priv.h>
#include <epan/plugin_if.h>
#include <epan/export_object.h>
#include <frame_tvbuff.h>
#include "ui/iface_toolbar.h"
@ -3016,3 +3017,119 @@ frame_data * MainWindow::frameDataForRow(int row) const
return Q_NULLPTR;
}
// Finds rtp information for selected stream and adds it to stream_infos
// If reverse is set, tries to find reverse stream too
// Return error string if error happens
//
// Note: Caller must free each returned rtpstream_info_t
QString MainWindow::findRtpStreams(QVector<rtpstream_info_t *> *stream_infos, bool reverse)
{
rtpstream_tapinfo_t tapinfo;
rtpstream_info_t *fwd_info, *rev_info;
const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
dfilter_t *sfcode;
gchar *err_msg;
fwd_info = rtpstream_info_malloc_and_init();
rev_info = rtpstream_info_malloc_and_init();
/* Try to get the hfid for "rtp.ssrc". */
int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
if (hfid_rtp_ssrc == -1) {
return tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
}
/* Try to compile the filter. */
if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
QString err = QString(err_msg);
g_free(err_msg);
return err;
}
if (!capture_file_.capFile() || !capture_file_.capFile()->current_frame) close();
if (!cf_read_current_record(capture_file_.capFile())) close();
frame_data *fdata = capture_file_.capFile()->current_frame;
epan_dissect_t edt;
epan_dissect_init(&edt, capture_file_.capFile()->epan, true, false);
epan_dissect_prime_with_dfilter(&edt, sfcode);
epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
epan_dissect_run(&edt, capture_file_.capFile()->cd_t,
&capture_file_.capFile()->rec,
frame_tvbuff_new_buffer(
&capture_file_.capFile()->provider, fdata,
&capture_file_.capFile()->buf),
fdata, NULL);
/*
* Packet must be an RTPv2 packet with an SSRC; we use the filter to
* check.
*/
if (!dfilter_apply_edt(sfcode, &edt)) {
epan_dissect_cleanup(&edt);
dfilter_free(sfcode);
return tr("Please select an RTPv2 packet with an SSRC value");
}
dfilter_free(sfcode);
/* OK, it is an RTP frame. Let's get the IP and port values */
rtpstream_id_copy_pinfo(&(edt.pi), &(fwd_info->id), false);
/* assume the inverse ip/port combination for the reverse direction */
rtpstream_id_copy_pinfo(&(edt.pi), &(rev_info->id), true);
/* now we need the SSRC value of the current frame */
GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
if (gp == NULL || gp->len == 0) {
/* XXX - should not happen, as the filter includes rtp.ssrc */
epan_dissect_cleanup(&edt);
return tr("SSRC value not found.");
}
fwd_info->id.ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
epan_dissect_cleanup(&edt);
/* Register the tap listener */
memset(&tapinfo, 0, sizeof(rtpstream_tapinfo_t));
tapinfo.tap_data = this;
tapinfo.mode = TAP_ANALYSE;
/* Scan for RTP streams (redissect all packets) */
rtpstream_scan(&tapinfo, capture_file_.capFile(), Q_NULLPTR);
for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
if (rtpstream_id_equal(&(strinfo->id), &(fwd_info->id),RTPSTREAM_ID_EQUAL_NONE))
{
fwd_info->packet_count = strinfo->packet_count;
fwd_info->setup_frame_number = strinfo->setup_frame_number;
nstime_copy(&fwd_info->start_rel_time, &strinfo->start_rel_time);
nstime_copy(&fwd_info->stop_rel_time, &strinfo->stop_rel_time);
nstime_copy(&fwd_info->start_abs_time, &strinfo->start_abs_time);
*stream_infos << fwd_info;
}
if (rtpstream_id_equal(&(strinfo->id), &(rev_info->id),RTPSTREAM_ID_EQUAL_NONE))
{
rev_info->packet_count = strinfo->packet_count;
rev_info->setup_frame_number = strinfo->setup_frame_number;
nstime_copy(&rev_info->start_rel_time, &strinfo->start_rel_time);
nstime_copy(&rev_info->stop_rel_time, &strinfo->stop_rel_time);
nstime_copy(&rev_info->start_abs_time, &strinfo->start_abs_time);
if (rev_info->id.ssrc == 0) {
rev_info->id.ssrc = strinfo->id.ssrc;
}
if (reverse) {
*stream_infos << rev_info;
}
}
}
return NULL;
}

View File

@ -366,9 +366,9 @@ public slots:
void rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpStreamsDialogSelectRtpStream(rtpstream_id_t *id);
void rtpStreamsDialogDeselectRtpStream(rtpstream_id_t *id);
@ -702,6 +702,7 @@ private slots:
void on_actionTelephonyOsmuxPacketCounter_triggered();
void on_actionTelephonyRtpStreams_triggered();
void on_actionTelephonyRtpStreamAnalysis_triggered();
void on_actionTelephonyRtpPlayer_triggered();
void on_actionTelephonyRTSPPacketCounter_triggered();
void on_actionTelephonySMPPOperations_triggered();
void on_actionTelephonyUCPMessages_triggered();
@ -724,6 +725,8 @@ private slots:
void extcap_options_finished(int result);
void showExtcapOptionsDialog(QString & device_name);
QString findRtpStreams(QVector<rtpstream_info_t *> *stream_infos, bool reverse);
friend WiresharkApplication;
};

View File

@ -568,6 +568,7 @@
</property>
<addaction name="actionTelephonyRtpStreams"/>
<addaction name="actionTelephonyRtpStreamAnalysis"/>
<addaction name="actionTelephonyRtpPlayer"/>
</widget>
<widget class="QMenu" name="menuTelephonySCTP">
<property name="title">
@ -2868,7 +2869,15 @@
<string>RTP Stream Analysis</string>
</property>
<property name="toolTip">
<string>RTP Stream Analysis</string>
<string>RTP Stream Analysis for selected stream. Press CTRL key for adding reverse stream too.</string>
</property>
</action>
<action name="actionTelephonyRtpPlayer">
<property name="text">
<string>RTP Player</string>
</property>
<property name="toolTip">
<string>Play selected stream. Press CTRL key for playing reverse stream too.</string>
</property>
</action>
<action name="actionTelephonyIax2StreamAnalysis">

View File

@ -1275,6 +1275,7 @@ void MainWindow::setMenusForSelectedPacket()
main_ui_->actionSCTPShowAllAssociations->setEnabled(is_sctp);
main_ui_->actionSCTPFilterThisAssociation->setEnabled(is_sctp);
main_ui_->actionTelephonyRtpStreamAnalysis->setEnabled(is_rtp);
main_ui_->actionTelephonyRtpPlayer->setEnabled(is_rtp);
main_ui_->actionTelephonyLteRlcGraph->setEnabled(is_lte_rlc);
}
@ -3314,6 +3315,14 @@ void MainWindow::openTelephonyRtpPlayerDialog()
connect(rtp_player_dialog_, SIGNAL(goToPacket(int)),
packet_list_, SLOT(goToPacket(int)));
connect(rtp_player_dialog_, SIGNAL(updateFilter(QString, bool)),
this, SLOT(filterPackets(QString, bool)));
connect(rtp_player_dialog_, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
connect(rtp_player_dialog_, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
connect(rtp_player_dialog_, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
}
rtp_player_dialog_->show();
}
@ -3361,6 +3370,8 @@ void MainWindow::openTelephonyRtpAnalysisDialog()
connect(rtp_analysis_dialog_, SIGNAL(goToPacket(int)),
packet_list_, SLOT(goToPacket(int)));
connect(rtp_analysis_dialog_, SIGNAL(updateFilter(QString, bool)),
this, SLOT(filterPackets(QString, bool)));
connect(rtp_analysis_dialog_, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_info_t *>)),
this, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_analysis_dialog_, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_info_t *>)),
@ -3478,12 +3489,12 @@ void MainWindow::openTelephonyRtpStreamsDialog()
this, SLOT(rtpPlayerDialogAddRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *>)),
this, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_info_t *>)),
this, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_info_t *>)),
this, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_info_t *>)),
this, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_info_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
connect(rtp_stream_dialog_, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
this, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
}
rtp_stream_dialog_->show();
}
@ -3497,9 +3508,55 @@ void MainWindow::on_actionTelephonyRtpStreams_triggered()
void MainWindow::on_actionTelephonyRtpStreamAnalysis_triggered()
{
QVector<rtpstream_info_t *> stream_infos;
QString err;
telephony_dialog_mutex.lock();
openTelephonyRtpAnalysisDialog();
rtp_analysis_dialog_->findRtpStreams();
if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) {
err = findRtpStreams(&stream_infos, true);
} else {
err = findRtpStreams(&stream_infos, false);
}
if (err != NULL) {
QMessageBox::warning(this, tr("RTP packet search failed"),
err,
QMessageBox::Ok);
} else {
QVector<rtpstream_id_t *> ids;
foreach(rtpstream_info_t *stream, stream_infos) {
ids << &stream->id;
}
rtp_analysis_dialog_->addRtpStreams(ids);
}
foreach(rtpstream_info_t *rtpstream, stream_infos) {
rtpstream_info_free_data(rtpstream);
}
telephony_dialog_mutex.unlock();
}
void MainWindow::on_actionTelephonyRtpPlayer_triggered()
{
QVector<rtpstream_info_t *> stream_infos;
QString err;
telephony_dialog_mutex.lock();
openTelephonyRtpPlayerDialog();
if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) {
err = findRtpStreams(&stream_infos, true);
} else {
err = findRtpStreams(&stream_infos, false);
}
if (err != NULL) {
QMessageBox::warning(this, tr("RTP packet search failed"),
err,
QMessageBox::Ok);
} else {
rtp_player_dialog_->addRtpStreams(stream_infos);
}
foreach(rtpstream_info_t *rtpstream, stream_infos) {
rtpstream_info_free_data(rtpstream);
}
telephony_dialog_mutex.unlock();
}
@ -4100,27 +4157,27 @@ void MainWindow::rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *> str
telephony_dialog_mutex.unlock();
}
void MainWindow::rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos)
void MainWindow::rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
telephony_dialog_mutex.lock();
openTelephonyRtpAnalysisDialog();
rtp_analysis_dialog_->replaceRtpStreams(stream_infos);
rtp_analysis_dialog_->replaceRtpStreams(stream_ids);
telephony_dialog_mutex.unlock();
}
void MainWindow::rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos)
void MainWindow::rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
telephony_dialog_mutex.lock();
openTelephonyRtpAnalysisDialog();
rtp_analysis_dialog_->addRtpStreams(stream_infos);
rtp_analysis_dialog_->addRtpStreams(stream_ids);
telephony_dialog_mutex.unlock();
}
void MainWindow::rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos)
void MainWindow::rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
telephony_dialog_mutex.lock();
openTelephonyRtpAnalysisDialog();
rtp_analysis_dialog_->removeRtpStreams(stream_infos);
rtp_analysis_dialog_->removeRtpStreams(stream_ids);
telephony_dialog_mutex.unlock();
}

View File

@ -239,7 +239,7 @@ enum {
num_graphs_
};
RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf, rtpstream_info_t *stream_fwd _U_, rtpstream_info_t *stream_rev _U_) :
RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf) :
WiresharkDialog(parent, cf),
ui(new Ui::RtpAnalysisDialog),
tab_seq(0)
@ -265,6 +265,10 @@ RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf, rtpstream
ui->streamGraph->xAxis->setLabel("Arrival Time");
ui->streamGraph->yAxis->setLabel("Value (ms)");
QPushButton *prepare_button = ui->buttonBox->addButton(ui->actionPrepareButton->text(), QDialogButtonBox::ActionRole);
prepare_button->setToolTip(ui->actionPrepareButton->toolTip());
prepare_button->setMenu(ui->menuPrepareFilter);
player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
QPushButton *export_btn = ui->buttonBox->addButton(ui->actionExportButton->text(), QDialogButtonBox::ActionRole);
@ -513,6 +517,9 @@ void RtpAnalysisDialog::updateWidgets()
ui->actionSaveAllCsv->setEnabled(enable_tab);
ui->actionSaveGraph->setEnabled(enable_tab);
ui->actionPrepareFilterOne->setEnabled(enable_nav);
ui->actionPrepareFilterAll->setEnabled(enable_tab);
#if defined(QT_MULTIMEDIA_LIB)
player_button_->setEnabled(enable_tab);
#endif
@ -935,133 +942,20 @@ void RtpAnalysisDialog::closeTab(int index)
QWidget *remove_tab = qobject_cast<QWidget *>(ui->tabWidget->widget(index));
tab_info_t *tab = tabs_[index];
tab_hash_.remove(rtpstream_to_hash(&tab->stream), tab);
tabs_.remove(index);
ui->tabWidget->removeTab(index);
ui->streamGraph->removeGraph(tab->jitter_graph);
ui->streamGraph->removeGraph(tab->diff_graph);
ui->streamGraph->removeGraph(tab->delta_graph);
clearLayout(tab->graphHorizontalLayout);
delete remove_tab;
deleteTabInfo(tabs_[index]);
g_free(tabs_[index]);
tabs_.remove(index);
deleteTabInfo(tab);
g_free(tab);
updateGraph();
}
}
void RtpAnalysisDialog::findRtpStreams()
{
rtpstream_info_t fwd_info, rev_info;
const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
dfilter_t *sfcode;
gchar *err_msg;
memset(&fwd_info, 0, sizeof(rtpstream_info_t));
memset(&rev_info, 0, sizeof(rtpstream_info_t));
/* Try to get the hfid for "rtp.ssrc". */
int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
if (hfid_rtp_ssrc == -1) {
err_str_ = tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
updateWidgets();
return;
}
/* Try to compile the filter. */
if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
err_str_ = QString(err_msg);
g_free(err_msg);
updateWidgets();
return;
}
if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close();
if (!cf_read_current_record(cap_file_.capFile())) close();
frame_data *fdata = cap_file_.capFile()->current_frame;
epan_dissect_t edt;
epan_dissect_init(&edt, cap_file_.capFile()->epan, true, false);
epan_dissect_prime_with_dfilter(&edt, sfcode);
epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->rec,
frame_tvbuff_new_buffer(&cap_file_.capFile()->provider, fdata, &cap_file_.capFile()->buf),
fdata, NULL);
/*
* Packet must be an RTPv2 packet with an SSRC; we use the filter to
* check.
*/
if (!dfilter_apply_edt(sfcode, &edt)) {
epan_dissect_cleanup(&edt);
dfilter_free(sfcode);
err_str_ = tr("Please select an RTPv2 packet with an SSRC value");
updateWidgets();
return;
}
dfilter_free(sfcode);
/* OK, it is an RTP frame. Let's get the IP and port values */
rtpstream_id_copy_pinfo(&(edt.pi),&(fwd_info.id),false);
/* assume the inverse ip/port combination for the reverse direction */
rtpstream_id_copy_pinfo(&(edt.pi),&(rev_info.id),true);
/* now we need the SSRC value of the current frame */
GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
if (gp == NULL || gp->len == 0) {
/* XXX - should not happen, as the filter includes rtp.ssrc */
epan_dissect_cleanup(&edt);
err_str_ = tr("SSRC value not found.");
updateWidgets();
return;
}
fwd_info.id.ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
epan_dissect_cleanup(&edt);
/* Register the tap listener */
memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
tapinfo_.tap_data = this;
tapinfo_.mode = TAP_ANALYSE;
/* Scan for RTP streams (redissect all packets) */
rtpstream_scan(&tapinfo_, cap_file_.capFile(), Q_NULLPTR);
QVector<rtpstream_info_t *> stream_infos;
for (GList *strinfo_list = g_list_first(tapinfo_.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
if (rtpstream_id_equal(&(strinfo->id), &(fwd_info.id),RTPSTREAM_ID_EQUAL_NONE))
{
fwd_info.packet_count = strinfo->packet_count;
fwd_info.setup_frame_number = strinfo->setup_frame_number;
nstime_copy(&fwd_info.start_rel_time, &strinfo->start_rel_time);
nstime_copy(&fwd_info.stop_rel_time, &strinfo->stop_rel_time);
nstime_copy(&fwd_info.start_abs_time, &strinfo->start_abs_time);
stream_infos << &fwd_info;
}
if (rtpstream_id_equal(&(strinfo->id), &(rev_info.id),RTPSTREAM_ID_EQUAL_NONE))
{
rev_info.packet_count = strinfo->packet_count;
rev_info.setup_frame_number = strinfo->setup_frame_number;
nstime_copy(&rev_info.start_rel_time, &strinfo->start_rel_time);
nstime_copy(&rev_info.stop_rel_time, &strinfo->stop_rel_time);
nstime_copy(&rev_info.start_abs_time, &strinfo->start_abs_time);
stream_infos << &rev_info;
if (rev_info.id.ssrc == 0) {
rev_info.id.ssrc = strinfo->id.ssrc;
}
}
}
addRtpStreamsPrivate(stream_infos);
}
void RtpAnalysisDialog::showStreamMenu(QPoint pos)
{
tab_info_t *tab_data = getTabInfoForCurrentTab();
@ -1074,7 +968,7 @@ void RtpAnalysisDialog::showStreamMenu(QPoint pos)
stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
}
void RtpAnalysisDialog::replaceRtpStreams(QVector<rtpstream_info_t *> stream_infos)
void RtpAnalysisDialog::replaceRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
// Delete existing tabs (from last to first)
if (tabs_.count() > 0) {
@ -1082,26 +976,26 @@ void RtpAnalysisDialog::replaceRtpStreams(QVector<rtpstream_info_t *> stream_inf
closeTab(i-1);
}
}
addRtpStreamsPrivate(stream_infos);
addRtpStreamsPrivate(stream_ids);
}
void RtpAnalysisDialog::addRtpStreams(QVector<rtpstream_info_t *> stream_infos)
void RtpAnalysisDialog::addRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
addRtpStreamsPrivate(stream_infos);
addRtpStreamsPrivate(stream_ids);
}
void RtpAnalysisDialog::addRtpStreamsPrivate(QVector<rtpstream_info_t *> stream_infos)
void RtpAnalysisDialog::addRtpStreamsPrivate(QVector<rtpstream_id_t *> stream_ids)
{
int first_tab_no = -1;
setUpdatesEnabled(false);
foreach(rtpstream_info_t *rtpstream, stream_infos) {
foreach(rtpstream_id_t *id, stream_ids) {
bool found = false;
QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_to_hash(rtpstream));
QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_id_to_hash(id));
for (int i = 0; i < tabs.size(); i++) {
tab_info_t *tab = tabs.at(i);
if (rtpstream_id_equal(&tab->stream.id, &rtpstream->id, RTPSTREAM_ID_EQUAL_SSRC)) {
if (rtpstream_id_equal(&tab->stream.id, id, RTPSTREAM_ID_EQUAL_SSRC)) {
found = true;
break;
}
@ -1111,14 +1005,14 @@ void RtpAnalysisDialog::addRtpStreamsPrivate(QVector<rtpstream_info_t *> stream_
int cur_tab_no;
tab_info_t *new_tab = g_new0(tab_info_t, 1);
rtpstream_info_copy_deep(&new_tab->stream, rtpstream);
rtpstream_id_copy(id, &(new_tab->stream.id));
new_tab->time_vals = new QVector<double>();
new_tab->jitter_vals = new QVector<double>();
new_tab->diff_vals = new QVector<double>();
new_tab->delta_vals = new QVector<double>();
tabs_ << new_tab;
cur_tab_no = addTabUI(new_tab);
tab_hash_.insert(rtpstream_to_hash(rtpstream), new_tab);
tab_hash_.insert(rtpstream_id_to_hash(id), new_tab);
if (first_tab_no == -1) {
first_tab_no = cur_tab_no;
}
@ -1135,14 +1029,14 @@ void RtpAnalysisDialog::addRtpStreamsPrivate(QVector<rtpstream_info_t *> stream_
updateGraph();
}
void RtpAnalysisDialog::removeRtpStreams(QVector<rtpstream_info_t *> stream_infos _U_)
void RtpAnalysisDialog::removeRtpStreams(QVector<rtpstream_id_t *> stream_ids)
{
setUpdatesEnabled(false);
foreach(rtpstream_info_t *rtpstream, stream_infos) {
QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_to_hash(rtpstream));
foreach(rtpstream_id_t *id, stream_ids) {
QList<tab_info_t *> tabs = tab_hash_.values(rtpstream_id_to_hash(id));
for (int i = 0; i < tabs.size(); i++) {
tab_info_t *tab = tabs.at(i);
if (rtpstream_id_equal(&tab->stream.id, &rtpstream->id, RTPSTREAM_ID_EQUAL_SSRC)) {
if (rtpstream_id_equal(&tab->stream.id, id, RTPSTREAM_ID_EQUAL_SSRC)) {
closeTab(tabs_.indexOf(tab));
}
}
@ -1189,3 +1083,24 @@ QPushButton *RtpAnalysisDialog::addAnalyzeButton(QDialogButtonBox *button_box, Q
return analysis_button;
}
void RtpAnalysisDialog::on_actionPrepareFilterOne_triggered()
{
if ((ui->tabWidget->currentIndex() < (ui->tabWidget->count()-1))) {
QVector<rtpstream_info_t *> streams;
streams << &tabs_[ui->tabWidget->currentIndex()]->stream;
QString filter = make_filter_based_on_rtpstream_info(streams);
if (filter.length() > 0) {
emit updateFilter(filter);
}
}
}
void RtpAnalysisDialog::on_actionPrepareFilterAll_triggered()
{
QVector<rtpstream_info_t *>streams = getSelectedRtpStreams();
QString filter = make_filter_based_on_rtpstream_info(streams);
if (filter.length() > 0) {
emit updateFilter(filter);
}
}

View File

@ -61,7 +61,7 @@ class RtpAnalysisDialog : public WiresharkDialog
Q_OBJECT
public:
explicit RtpAnalysisDialog(QWidget &parent, CaptureFile &cf, rtpstream_info_t *stream_fwd = 0, rtpstream_info_t *stream_rev = 0);
explicit RtpAnalysisDialog(QWidget &parent, CaptureFile &cf);
~RtpAnalysisDialog();
/**
* @brief Common routine to add a "Analyze" button to a QDialogButtonBox.
@ -77,16 +77,16 @@ public:
*
* @param rtpstream struct with rtpstream info
*/
void replaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void addRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void removeRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void findRtpStreams();
void replaceRtpStreams(QVector<rtpstream_id_t *> stream_ids);
void addRtpStreams(QVector<rtpstream_id_t *> stream_ids);
void removeRtpStreams(QVector<rtpstream_id_t *> stream_ids);
signals:
void goToPacket(int packet_num);
void rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void updateFilter(QString filter, bool force = false);
public slots:
void rtpPlayerReplace();
@ -108,6 +108,8 @@ private slots:
void closeTab(int index);
void rowCheckboxChanged(int checked);
void singleCheckboxChanged(int checked);
void on_actionPrepareFilterOne_triggered();
void on_actionPrepareFilterAll_triggered();
private:
Ui::RtpAnalysisDialog *ui;
@ -122,7 +124,6 @@ private:
// Graph data for QCustomPlot
QList<QCPGraph *>graphs_;
rtpstream_tapinfo_t tapinfo_;
QString err_str_;
QMenu stream_ctx_menu_;
@ -148,7 +149,7 @@ private:
tab_info_t *getTabInfoForCurrentTab();
void deleteTabInfo(tab_info_t *tab_info);
void clearLayout(QLayout *layout);
void addRtpStreamsPrivate(QVector<rtpstream_info_t *> stream_infos);
void addRtpStreamsPrivate(QVector<rtpstream_id_t *> stream_ids);
};
#endif // RTP_ANALYSIS_DIALOG_H

View File

@ -159,6 +159,40 @@
<string>N</string>
</property>
</action>
<action name="actionPrepareButton">
<property name="text">
<string>Prepare &amp;Filter</string>
</property>
<property name="toolTip">
<string>Prepare a filter matching the selected stream(s).</string>
</property>
</action>
<widget class="QMenu" name="menuPrepareFilter">
<property name="title">
<string>Prepare &amp;Filter</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionPrepareFilterOne"/>
<addaction name="actionPrepareFilterAll"/>
</widget>
<action name="actionPrepareFilterOne">
<property name="text">
<string>&amp;Current Tab</string>
</property>
<property name="toolTip">
<string>Prepare a filter matching current tab.</string>
</property>
</action>
<action name="actionPrepareFilterAll">
<property name="text">
<string>&amp;All Tabs</string>
</property>
<property name="toolTip">
<string>Prepare a filter matching all tabs.</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -798,7 +798,15 @@ void RtpAudioStream::stopPlaying()
return;
if (audio_output_) {
audio_output_->stop();
if (audio_output_->state() == QAudio::StoppedState) {
// Looks like "delayed" QTBUG-6548
// It may happen that stream is stopped, but no signal emited
// Probably triggered by some issue in sound system which is not
// handled by Qt correctly
outputStateChanged(QAudio::StoppedState);
} else {
audio_output_->stop();
}
}
}
@ -838,8 +846,8 @@ void RtpAudioStream::outputStateChanged(QAudio::State new_state)
}
case QAudio::IdleState:
// Workaround for Qt behaving on some platforms with some soundcards:
// When ->stop() is called from outputStateChanged(), QMutexLocker is
// locked and application hangs.
// When ->stop() is called from outputStateChanged(),
// internalQMutexLock is locked and application hangs.
// We can stop the stream later.
QTimer::singleShot(0, this, SLOT(delayedStopStream()));

View File

@ -155,6 +155,7 @@ public:
qint64 getTotalSamples() { return (sample_file_->size()/(qint64)sizeof(SAMPLE)); }
bool savePayload(QIODevice *file);
guint getHash() { return rtpstream_id_to_hash(&id_); }
rtpstream_id_t *getID() { return &id_; }
QString getIDAsQString();
signals:
@ -172,8 +173,6 @@ private:
QIODevice *sample_file_frame_; // Stores rtp_packet_info per packet
QIODevice *temp_file_;
struct _GHashTable *decoders_hash_;
// TODO: It is not used
//QList<const rtpstream_info_t *>rtpstreams_;
double global_start_rel_time_;
double start_abs_offset_;
double start_rel_time_;

View File

@ -11,6 +11,12 @@
#include <ui/tap-rtp-common.h>
#include "rtp_player_dialog.h"
#include <ui_rtp_player_dialog.h>
#include "epan/epan_dissect.h"
#include "file.h"
#include "frame_tvbuff.h"
#include "rtp_analysis_dialog.h"
#ifdef QT_MULTIMEDIA_LIB
@ -219,17 +225,20 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
ui->stopButton->setIcon(StockIcon("media-playback-stop"));
ui->stopButton->setEnabled(false);
inaudible_btn_ = ui->buttonBox->addButton(ui->actionInaudibleButton->text(), QDialogButtonBox::ActionRole);
inaudible_btn_->setToolTip(ui->actionInaudibleButton->toolTip());
inaudible_btn_->setEnabled(false);
inaudible_btn_->setMenu(ui->menuInaudible);
analyze_btn_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
prepare_btn_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
prepare_btn_->setToolTip(ui->actionPrepareFilter->toolTip());
connect(prepare_btn_, SIGNAL(pressed()), this, SLOT(on_actionPrepareFilter_triggered()));
export_btn_ = ui->buttonBox->addButton(ui->actionExportButton->text(), QDialogButtonBox::ActionRole);
export_btn_->setToolTip(ui->actionExportButton->toolTip());
export_btn_->setEnabled(false);
QMenu *save_menu = new QMenu(export_btn_);
save_menu->addAction(ui->actionSaveAudioSyncStream);
save_menu->addAction(ui->actionSaveAudioSyncFile);
save_menu->addSeparator();
save_menu->addAction(ui->actionSavePayload);
save_menu->setToolTipsVisible(true);
export_btn_->setMenu(save_menu);
export_btn_->setMenu(ui->menuExport);
// Ordered, unique device names starting with the system default
QMap<QString, bool> out_device_map; // true == default device
@ -240,6 +249,7 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
}
}
ui->outputDeviceComboBox->blockSignals(true);
foreach (QString out_name, out_device_map.keys()) {
ui->outputDeviceComboBox->addItem(out_name);
if (out_device_map.value(out_name)) {
@ -257,6 +267,7 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
stereo_available_ = isStereoAvailable();
fillAudioRateMenu();
}
ui->outputDeviceComboBox->blockSignals(false);
ui->audioPlot->setMouseTracking(true);
ui->audioPlot->setEnabled(true);
@ -271,31 +282,10 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
graph_ctx_menu_->addAction(ui->actionPlay);
list_ctx_menu_->addAction(ui->actionStop);
graph_ctx_menu_->addAction(ui->actionStop);
QMenu *selection_menu1 = list_ctx_menu_->addMenu(tr("Select"));
QMenu *selection_menu2 = graph_ctx_menu_->addMenu(tr("Select"));
selection_menu1->addAction(ui->actionSelectAll);
selection_menu2->addAction(ui->actionSelectAll);
selection_menu1->addAction(ui->actionSelectNone);
selection_menu2->addAction(ui->actionSelectNone);
selection_menu1->addAction(ui->actionSelectInvert);
selection_menu2->addAction(ui->actionSelectInvert);
QMenu *audio_routing_menu1 = list_ctx_menu_->addMenu(tr("Audio Routing"));
QMenu *audio_routing_menu2 = graph_ctx_menu_->addMenu(tr("Audio Routing"));
// All AudioRouting actions are in menu, some of them are disabled later
audio_routing_menu1->addAction(ui->actionAudioRoutingMute);
audio_routing_menu2->addAction(ui->actionAudioRoutingMute);
audio_routing_menu1->addAction(ui->actionAudioRoutingUnmute);
audio_routing_menu2->addAction(ui->actionAudioRoutingUnmute);
audio_routing_menu1->addAction(ui->actionAudioRoutingMuteInvert);
audio_routing_menu2->addAction(ui->actionAudioRoutingMuteInvert);
audio_routing_menu1->addAction(ui->actionAudioRoutingP);
audio_routing_menu2->addAction(ui->actionAudioRoutingP);
audio_routing_menu1->addAction(ui->actionAudioRoutingL);
audio_routing_menu2->addAction(ui->actionAudioRoutingL);
audio_routing_menu1->addAction(ui->actionAudioRoutingLR);
audio_routing_menu2->addAction(ui->actionAudioRoutingLR);
audio_routing_menu1->addAction(ui->actionAudioRoutingR);
audio_routing_menu2->addAction(ui->actionAudioRoutingR);
list_ctx_menu_->addMenu(ui->menuSelect);
graph_ctx_menu_->addMenu(ui->menuSelect);
list_ctx_menu_->addMenu(ui->menuAudioRouting);
graph_ctx_menu_->addMenu(ui->menuAudioRouting);
list_ctx_menu_->addAction(ui->actionRemoveStream);
graph_ctx_menu_->addAction(ui->actionRemoveStream);
list_ctx_menu_->addAction(ui->actionGoToSetupPacketTree);
@ -419,6 +409,7 @@ void RtpPlayerDialog::rescanPackets(bool rescale_axes)
{
lockUI();
// Show information for a user - it can last long time...
playback_error_.clear();
ui->hintLabel->setText("<i><small>" + tr("Decoding streams...") + "</i></small>");
wsApp->processEvents();
@ -902,6 +893,16 @@ bool RtpPlayerDialog::eventFilter(QObject *, QEvent *event)
case Qt::Key_S:
on_actionStop_triggered();
return true;
case Qt::Key_N:
if (keyEvent.modifiers() == Qt::ShiftModifier) {
// Shift+N
on_actionDeselectInaudible_triggered();
return true;
} else {
on_actionSelectInaudible_triggered();
return true;
}
break;
}
}
@ -919,8 +920,10 @@ void RtpPlayerDialog::updateWidgets()
bool enable_pause = false;
bool enable_stop = false;
bool enable_timing = true;
int count = ui->streamTreeWidget->topLevelItemCount();
int selected = ui->streamTreeWidget->selectedItems().count();
if (ui->streamTreeWidget->topLevelItemCount() < 1)
if (count < 1)
enable_play = false;
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
@ -959,6 +962,10 @@ void RtpPlayerDialog::updateWidgets()
ui->timingComboBox->setEnabled(enable_timing);
ui->todCheckBox->setEnabled(enable_timing);
inaudible_btn_->setEnabled(count > 0);
analyze_btn_->setEnabled(selected > 0);
prepare_btn_->setEnabled(selected > 0);
updateHintLabel();
ui->audioPlot->replot();
}
@ -1452,11 +1459,17 @@ void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
int selected = ui->streamTreeWidget->selectedItems().count();
if (selected == 0) {
analyze_btn_->setEnabled(false);
prepare_btn_->setEnabled(false);
export_btn_->setEnabled(false);
} else if (selected == 1) {
analyze_btn_->setEnabled(true);
prepare_btn_->setEnabled(true);
export_btn_->setEnabled(true);
ui->actionSavePayload->setEnabled(true);
} else {
analyze_btn_->setEnabled(true);
prepare_btn_->setEnabled(true);
export_btn_->setEnabled(true);
ui->actionSavePayload->setEnabled(false);
}
@ -1704,11 +1717,13 @@ QString RtpPlayerDialog::currentOutputDeviceName()
void RtpPlayerDialog::fillAudioRateMenu()
{
ui->outputAudioRate->blockSignals(true);
ui->outputAudioRate->clear();
ui->outputAudioRate->addItem(tr("Automatic"));
foreach (int rate, getCurrentDeviceInfo().supportedSampleRates()) {
ui->outputAudioRate->addItem(QString::number(rate));
}
ui->outputAudioRate->blockSignals(false);
}
void RtpPlayerDialog::cleanupMarkerStream()
@ -1876,10 +1891,12 @@ bool RtpPlayerDialog::isStereoAvailable()
void RtpPlayerDialog::invertSelection()
{
block_redraw_ = true;
ui->streamTreeWidget->blockSignals(true);
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
ti->setSelected(!ti->isSelected());
}
ui->streamTreeWidget->blockSignals(false);
block_redraw_ = false;
ui->audioPlot->replot();
updateHintLabel();
@ -2167,7 +2184,24 @@ save_payload_t RtpPlayerDialog::selectFilePayloadFormatAndName(QString *file_pat
return save_format;
}
QVector<RtpAudioStream *>RtpPlayerDialog::getSelectedAudibleAudioStreams()
QVector<rtpstream_id_t *>RtpPlayerDialog::getSelectedRtpStreamIDs()
{
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
QVector<rtpstream_id_t *> ids;
if (items.count() > 0) {
foreach(QTreeWidgetItem *ti, items) {
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
if (audio_stream) {
ids << audio_stream->getID();
}
}
}
return ids;
}
QVector<RtpAudioStream *>RtpPlayerDialog::getSelectedAudibleNonmutedAudioStreams()
{
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
QVector<RtpAudioStream *> streams;
@ -2196,7 +2230,7 @@ void RtpPlayerDialog::saveAudio(bool sync_to_stream)
QString path;
QVector<RtpAudioStream *>streams;
streams = getSelectedAudibleAudioStreams();
streams = getSelectedAudibleNonmutedAudioStreams();
if (streams.count() < 1) {
QMessageBox::warning(this, tr("Warning"), tr("No stream selected or none of selected streams provide audio"));
return;
@ -2320,30 +2354,63 @@ void RtpPlayerDialog::on_actionSavePayload_triggered()
savePayload();
}
#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 rtpstream_info_t *rtpstream)
void RtpPlayerDialog::selectInaudible(bool select)
{
const QString stream_key = QString(stream_key_tmpl_)
.arg(address_to_display_qstring(&rtpstream->src_addr))
.arg(rtpstream->src_port)
.arg(address_to_display_qstring(&rtpstream->dst_addr))
.arg(rtpstream->dst_port)
.arg(rtpstream->ssrc, 0, 16);
return stream_key;
block_redraw_ = true;
ui->streamTreeWidget->blockSignals(true);
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
// Streams with no audio
if (audio_stream && (audio_stream->sampleRate()==0)) {
ti->setSelected(select);
}
}
ui->streamTreeWidget->blockSignals(false);
block_redraw_ = false;
ui->audioPlot->replot();
updateHintLabel();
}
const QString RtpPlayerDialog::streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo)
void RtpPlayerDialog::on_actionSelectInaudible_triggered()
{
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;
selectInaudible(true);
}
#endif
void RtpPlayerDialog::on_actionDeselectInaudible_triggered()
{
selectInaudible(false);
}
void RtpPlayerDialog::on_actionPrepareFilter_triggered()
{
QVector<rtpstream_id_t *> ids = getSelectedRtpStreamIDs();
QString filter = make_filter_based_on_rtpstream_id(ids);
if (filter.length() > 0) {
emit updateFilter(filter);
}
}
void RtpPlayerDialog::rtpAnalysisReplace()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpStreamIDs());
}
void RtpPlayerDialog::rtpAnalysisAdd()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpStreamIDs());
}
void RtpPlayerDialog::rtpAnalysisRemove()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpStreamIDs());
}
#endif // QT_MULTIMEDIA_LIB

View File

@ -85,7 +85,20 @@ public:
void removeRtpStreams(QVector<rtpstream_info_t *> stream_infos);
signals:
// Tells the packet list to redraw. An alternative might be to add a
// cf_packet_marked callback to file.[ch] but that's synchronous and
// might incur too much overhead.
void packetsMarked();
void updateFilter(QString filter, bool force = false);
void goToPacket(int packet_num);
void rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *> stream_infos);
public slots:
void rtpAnalysisReplace();
void rtpAnalysisAdd();
void rtpAnalysisRemove();
protected:
virtual void showEvent(QShowEvent *);
@ -155,6 +168,9 @@ private slots:
void on_actionSaveAudioSyncStream_triggered();
void on_actionSaveAudioSyncFile_triggered();
void on_actionSavePayload_triggered();
void on_actionSelectInaudible_triggered();
void on_actionDeselectInaudible_triggered();
void on_actionPrepareFilter_triggered();
private:
Ui::RtpPlayerDialog *ui;
@ -177,6 +193,9 @@ private:
quint32 marker_stream_requested_out_rate_;
QTreeWidgetItem *last_ti_;
bool listener_removed_;
QPushButton *inaudible_btn_;
QPushButton *analyze_btn_;
QPushButton *prepare_btn_;
QPushButton *export_btn_;
QMultiHash<guint, RtpAudioStream *> stream_hash_;
bool block_redraw_;
@ -221,11 +240,13 @@ private:
bool writeAudioStreamsSamples(QFile *out_file, QVector<RtpAudioStream *> streams, bool swap_bytes);
save_audio_t selectFileAudioFormatAndName(QString *file_path);
save_payload_t selectFilePayloadFormatAndName(QString *file_path);
QVector<RtpAudioStream *>getSelectedAudibleAudioStreams();
QVector<RtpAudioStream *>getSelectedAudibleNonmutedAudioStreams();
void saveAudio(bool sync_to_stream);
void savePayload();
void lockUI();
void unlockUI();
void selectInaudible(bool select);
QVector<rtpstream_id_t *>getSelectedRtpStreamIDs();
#else // QT_MULTIMEDIA_LIB
private:

View File

@ -336,6 +336,18 @@
<string>Export audio of all unmuted selected channels or export payload of one channel.</string>
</property>
</action>
<widget class="QMenu" name="menuExport">
<property name="title">
<string>&amp;Export</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionSaveAudioSyncStream"/>
<addaction name="actionSaveAudioSyncFile"/>
<addaction name="separator"/>
<addaction name="actionSavePayload"/>
</widget>
<action name="actionSaveAudioSyncStream">
<property name="text">
<string>&amp;Stream Synchronized Audio</string>
@ -470,6 +482,21 @@
<string notr="true">Shift+G</string>
</property>
</action>
<widget class="QMenu" name="menuAudioRouting">
<property name="title">
<string>Audio Routing</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionAudioRoutingMute"/>
<addaction name="actionAudioRoutingUnmute"/>
<addaction name="actionAudioRoutingMuteInvert"/>
<addaction name="actionAudioRoutingP"/>
<addaction name="actionAudioRoutingL"/>
<addaction name="actionAudioRoutingLR"/>
<addaction name="actionAudioRoutingR"/>
</widget>
<action name="actionAudioRoutingMute">
<property name="text">
<string>Mute</string>
@ -546,6 +573,17 @@
<string notr="true">Delete</string>
</property>
</action>
<widget class="QMenu" name="menuSelect">
<property name="title">
<string>Select</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionSelectAll"/>
<addaction name="actionSelectNone"/>
<addaction name="actionSelectInvert"/>
</widget>
<action name="actionSelectAll">
<property name="text">
<string>All</string>
@ -601,6 +639,54 @@
<string notr="true">S</string>
</property>
</action>
<action name="actionInaudibleButton">
<property name="text">
<string>I&amp;naudible streams</string>
</property>
<property name="toolTip">
<string>Select/Deselect inaudible streams</string>
</property>
</action>
<widget class="QMenu" name="menuInaudible">
<property name="title">
<string>Inaudible streams</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionSelectInaudible"/>
<addaction name="actionDeselectInaudible"/>
</widget>
<action name="actionSelectInaudible">
<property name="text">
<string>&amp;Select</string>
</property>
<property name="toolTip">
<string>Select inaudible streams</string>
</property>
<property name="shortcut">
<string notr="true">N</string>
</property>
</action>
<action name="actionDeselectInaudible">
<property name="text">
<string>&amp;Deselect</string>
</property>
<property name="toolTip">
<string>Deselect inaudible streams</string>
</property>
<property name="shortcut">
<string notr="true">Shift+N</string>
</property>
</action>
<action name="actionPrepareFilter">
<property name="text">
<string>Prepare &amp;Filter</string>
</property>
<property name="toolTip">
<string>Prepare a filter matching the selected stream(s).</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -249,11 +249,8 @@ RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
setWindowSubtitle(tr("RTP Streams"));
ui->streamTreeWidget->installEventFilter(this);
QMenu *selection_menu = ctx_menu_.addMenu(tr("Select"));
selection_menu->addAction(ui->actionSelectAll);
selection_menu->addAction(ui->actionSelectNone);
selection_menu->addAction(ui->actionSelectInvert);
ctx_menu_.addAction(ui->actionFindReverse);
ctx_menu_.addMenu(ui->menuSelect);
ctx_menu_.addMenu(ui->menuFindReverse);
ctx_menu_.addAction(ui->actionGoToSetup);
ctx_menu_.addAction(ui->actionMarkPackets);
ctx_menu_.addAction(ui->actionPrepareFilter);
@ -268,19 +265,22 @@ RtpStreamDialog::RtpStreamDialog(QWidget &parent, CaptureFile &cf) :
connect(ui->streamTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
SLOT(showStreamMenu(QPoint)));
// Some GTK+ buttons have been left out intentionally in order to
// reduce clutter. Do you have a strong and informed opinion about
// this? Perhaps you should volunteer to maintain this code!
find_reverse_button_ = ui->buttonBox->addButton(ui->actionFindReverse->text(), QDialogButtonBox::ActionRole);
find_reverse_button_->setToolTip(ui->actionFindReverse->toolTip());
find_reverse_button_->setMenu(ui->menuFindReverse);
connect(ui->actionFindReverseNormal, SIGNAL(triggered()), this, SLOT(on_actionFindReverseNormal_triggered()));
connect(ui->actionFindReversePair, SIGNAL(triggered()), this, SLOT(on_actionFindReversePair_triggered()));
connect(ui->actionFindReverseSingle, SIGNAL(triggered()), this, SLOT(on_actionFindReverseSingle_triggered()));
analyze_button_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
prepare_button_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
prepare_button_->setToolTip(ui->actionPrepareFilter->toolTip());
connect(prepare_button_, SIGNAL(pressed()), this, SLOT(on_actionPrepareFilter_triggered()));
player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox, this);
copy_button_ = ui->buttonBox->addButton(ui->actionCopyButton->text(), QDialogButtonBox::ActionRole);
copy_button_->setToolTip(ui->actionCopyButton->toolTip());
export_button_ = ui->buttonBox->addButton(ui->actionExportAsRtpDump->text(), QDialogButtonBox::ActionRole);
export_button_->setToolTip(ui->actionExportAsRtpDump->toolTip());
connect(export_button_, SIGNAL(pressed()), this, SLOT(on_actionExportAsRtpDump_triggered()));
QMenu *copy_menu = new QMenu(copy_button_);
QAction *ca;
@ -362,7 +362,13 @@ bool RtpStreamDialog::eventFilter(QObject *, QEvent *event)
on_actionPrepareFilter_triggered();
return true;
case Qt::Key_R:
on_actionFindReverse_triggered();
if (keyEvent.modifiers() == Qt::ShiftModifier) {
on_actionFindReversePair_triggered();
} else if (keyEvent.modifiers() == Qt::ControlModifier) {
on_actionFindReverseSingle_triggered();
} else {
on_actionFindReverseNormal_triggered();
}
return true;
case Qt::Key_I:
if (keyEvent.modifiers() == Qt::ControlModifier) {
@ -529,13 +535,15 @@ void RtpStreamDialog::updateWidgets()
bool enable = selected && !file_closed_;
bool has_data = ui->streamTreeWidget->topLevelItemCount() > 0;
find_reverse_button_->setEnabled(enable);
find_reverse_button_->setEnabled(has_data);
prepare_button_->setEnabled(enable);
export_button_->setEnabled(enable);
copy_button_->setEnabled(has_data);
analyze_button_->setEnabled(enable);
ui->actionFindReverse->setEnabled(enable);
ui->actionFindReverseNormal->setEnabled(enable);
ui->actionFindReversePair->setEnabled(has_data);
ui->actionFindReverseSingle->setEnabled(has_data);
ui->actionGoToSetup->setEnabled(enable);
ui->actionMarkPackets->setEnabled(enable);
ui->actionPrepareFilter->setEnabled(enable);
@ -684,34 +692,85 @@ void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
}
}
void RtpStreamDialog::on_actionFindReverse_triggered()
// Search for reverse stream of every selected stream
void RtpStreamDialog::on_actionFindReverseNormal_triggered()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
// Gather up our selected streams...
QList<rtpstream_info_t *> selected_streams;
foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
rtpstream_info_t *stream_info = rsti->streamInfo();
if (stream_info) {
selected_streams << stream_info;
}
}
ui->streamTreeWidget->blockSignals(true);
// ...and compare them to our unselected streams.
QTreeWidgetItemIterator iter(ui->streamTreeWidget, QTreeWidgetItemIterator::Unselected);
while (*iter) {
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(*iter);
rtpstream_info_t *stream_info = rsti->streamInfo();
if (stream_info) {
foreach (rtpstream_info_t *fwd_stream, selected_streams) {
if (rtpstream_info_is_reverse(fwd_stream, stream_info)) {
(*iter)->setSelected(true);
// Traverse all items and if stream is selected, search reverse from
// current position till last item (NxN/2)
for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
if (fwd_stream && fwd_rsti->isSelected()) {
for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
rev_rsti->setSelected(true);
break;
}
}
}
++iter;
}
ui->streamTreeWidget->blockSignals(false);
updateWidgets();
}
// Select all pairs of forward/reverse streams
void RtpStreamDialog::on_actionFindReversePair_triggered()
{
ui->streamTreeWidget->blockSignals(true);
ui->streamTreeWidget->clearSelection();
// Traverse all items and search reverse from current position till last
// item (NxN/2)
for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
if (fwd_stream) {
for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
fwd_rsti->setSelected(true);
rev_rsti->setSelected(true);
break;
}
}
}
}
ui->streamTreeWidget->blockSignals(false);
updateWidgets();
}
// Select all streams which don't have reverse stream
void RtpStreamDialog::on_actionFindReverseSingle_triggered()
{
ui->streamTreeWidget->blockSignals(true);
ui->streamTreeWidget->selectAll();
// Traverse all items and search reverse from current position till last
// item (NxN/2)
for (int fwd_row = 0; fwd_row < ui->streamTreeWidget->topLevelItemCount(); fwd_row++) {
RtpStreamTreeWidgetItem *fwd_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(fwd_row));
rtpstream_info_t *fwd_stream = fwd_rsti->streamInfo();
if (fwd_stream) {
for (int rev_row = fwd_row + 1; rev_row < ui->streamTreeWidget->topLevelItemCount(); rev_row++) {
RtpStreamTreeWidgetItem *rev_rsti = static_cast<RtpStreamTreeWidgetItem*>(ui->streamTreeWidget->topLevelItem(rev_row));
rtpstream_info_t *rev_stream = rev_rsti->streamInfo();
if (rev_stream && rtpstream_info_is_reverse(fwd_stream, rev_stream)) {
fwd_rsti->setSelected(false);
rev_rsti->setSelected(false);
break;
}
}
}
}
ui->streamTreeWidget->blockSignals(false);
updateWidgets();
}
void RtpStreamDialog::on_actionGoToSetup_triggered()
@ -750,26 +809,9 @@ void RtpStreamDialog::on_actionMarkPackets_triggered()
void RtpStreamDialog::on_actionPrepareFilter_triggered()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
// Gather up our selected streams...
QStringList stream_filters;
foreach(QTreeWidgetItem *ti, ui->streamTreeWidget->selectedItems()) {
RtpStreamTreeWidgetItem *rsti = static_cast<RtpStreamTreeWidgetItem*>(ti);
rtpstream_info_t *stream_info = rsti->streamInfo();
if (stream_info) {
QString ip_proto = stream_info->id.src_addr.type == AT_IPv6 ? "ipv6" : "ip";
stream_filters << QString("(%1.src==%2 && udp.srcport==%3 && %1.dst==%4 && udp.dstport==%5 && rtp.ssrc==0x%6)")
.arg(ip_proto) // %1
.arg(address_to_qstring(&stream_info->id.src_addr)) // %2
.arg(stream_info->id.src_port) // %3
.arg(address_to_qstring(&stream_info->id.dst_addr)) // %4
.arg(stream_info->id.dst_port) // %5
.arg(stream_info->id.ssrc, 0, 16);
}
}
if (stream_filters.length() > 0) {
QString filter = stream_filters.join(" || ");
QVector<rtpstream_info_t *> streams = getSelectedRtpStreams();
QString filter = make_filter_based_on_rtpstream_info(streams);
if (filter.length() > 0) {
remove_tap_listener_rtpstream(&tapinfo_);
emit updateFilter(filter);
}
@ -780,17 +822,6 @@ void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
updateWidgets();
}
void RtpStreamDialog::on_buttonBox_clicked(QAbstractButton *button)
{
if (button == find_reverse_button_) {
on_actionFindReverse_triggered();
} else if (button == prepare_button_) {
on_actionPrepareFilter_triggered();
} else if (button == export_button_) {
on_actionExportAsRtpDump_triggered();
}
}
void RtpStreamDialog::on_buttonBox_helpRequested()
{
wsApp->helpTopicAction(HELP_TELEPHONY_RTP_STREAMS_DIALOG);
@ -849,6 +880,18 @@ QVector<rtpstream_info_t *>RtpStreamDialog::getSelectedRtpStreams()
return stream_infos;
}
QVector<rtpstream_id_t *>RtpStreamDialog::getSelectedRtpStreamIDs()
{
QVector<rtpstream_info_t *> stream_infos = getSelectedRtpStreams();
QVector<rtpstream_id_t *> ids;
foreach(rtpstream_info_t *stream, stream_infos) {
ids << &stream->id;
}
return ids;
}
void RtpStreamDialog::rtpPlayerReplace()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
@ -874,21 +917,21 @@ void RtpStreamDialog::rtpAnalysisReplace()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpStreams());
emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpStreamIDs());
}
void RtpStreamDialog::rtpAnalysisAdd()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpStreams());
emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpStreamIDs());
}
void RtpStreamDialog::rtpAnalysisRemove()
{
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpStreams());
emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpStreamIDs());
}
void RtpStreamDialog::displayFilterSuccess(bool success)
@ -900,10 +943,13 @@ void RtpStreamDialog::displayFilterSuccess(bool success)
void RtpStreamDialog::invertSelection()
{
ui->streamTreeWidget->blockSignals(true);
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
ti->setSelected(!ti->isSelected());
}
ui->streamTreeWidget->blockSignals(false);
updateWidgets();
}
void RtpStreamDialog::on_actionAnalyze_triggered()

View File

@ -42,9 +42,9 @@ signals:
void rtpPlayerDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpPlayerDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_info_t *> stream_infos);
void rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *> stream_infos);
void rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *> stream_infos);
public slots:
void displayFilterSuccess(bool success);
@ -87,18 +87,20 @@ private:
void freeLastSelected();
void invertSelection();
QVector<rtpstream_info_t *>getSelectedRtpStreams();
QVector<rtpstream_id_t *>getSelectedRtpStreamIDs();
private slots:
void showStreamMenu(QPoint pos);
void on_actionCopyAsCsv_triggered();
void on_actionCopyAsYaml_triggered();
void on_actionFindReverse_triggered();
void on_actionFindReverseNormal_triggered();
void on_actionFindReversePair_triggered();
void on_actionFindReverseSingle_triggered();
void on_actionGoToSetup_triggered();
void on_actionMarkPackets_triggered();
void on_actionPrepareFilter_triggered();
void on_streamTreeWidget_itemSelectionChanged();
void on_buttonBox_helpRequested();
void on_buttonBox_clicked(QAbstractButton *button);
void on_actionExportAsRtpDump_triggered();
void captureEvent(CaptureEvent e);
void on_displayFilterCheckBox_toggled(bool checked);

View File

@ -168,6 +168,14 @@
</item>
</layout>
<action name="actionFindReverse">
<property name="text">
<string>Find &amp;Reverse</string>
</property>
<property name="toolTip">
<string>All forward/reverse stream actions</string>
</property>
</action>
<action name="actionFindReverseNormal">
<property name="text">
<string>Find &amp;Reverse</string>
</property>
@ -178,6 +186,39 @@
<string>R</string>
</property>
</action>
<action name="actionFindReversePair">
<property name="text">
<string>Find All &amp;Pairs</string>
</property>
<property name="toolTip">
<string>Select all streams which are paired in forward/reverse relation</string>
</property>
<property name="shortcut">
<string>Shift+R</string>
</property>
</action>
<action name="actionFindReverseSingle">
<property name="text">
<string>Find Only &amp;Singles</string>
</property>
<property name="toolTip">
<string>Find all streams which don't have paired reverse stream</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<widget class="QMenu" name="menuFindReverse">
<property name="title">
<string>Find &amp;Reverse</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionFindReverseNormal"/>
<addaction name="actionFindReversePair"/>
<addaction name="actionFindReverseSingle"/>
</widget>
<action name="actionMarkPackets">
<property name="text">
<string>Mark Packets</string>
@ -189,6 +230,17 @@
<string>M</string>
</property>
</action>
<widget class="QMenu" name="menuSelect">
<property name="title">
<string>Select</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionSelectAll"/>
<addaction name="actionSelectNone"/>
<addaction name="actionSelectInvert"/>
</widget>
<action name="actionSelectAll">
<property name="text">
<string>All</string>

View File

@ -256,3 +256,37 @@ void set_action_shortcuts_visible_in_context_menu(QList<QAction *> actions)
Q_UNUSED(actions)
#endif
}
QString make_filter_based_on_rtpstream_id(QVector<rtpstream_id_t *> ids)
{
QStringList stream_filters;
QString filter;
foreach(rtpstream_id_t *id, ids) {
QString ip_proto = id->src_addr.type == AT_IPv6 ? "ipv6" : "ip";
stream_filters << QString("(%1.src==%2 && udp.srcport==%3 && %1.dst==%4 && udp.dstport==%5 && rtp.ssrc==0x%6)")
.arg(ip_proto) // %1
.arg(address_to_qstring(&id->src_addr)) // %2
.arg(id->src_port) // %3
.arg(address_to_qstring(&id->dst_addr)) // %4
.arg(id->dst_port) // %5
.arg(id->ssrc, 0, 16);
}
if (stream_filters.length() > 0) {
filter = stream_filters.join(" || ");
}
return filter;
}
QString make_filter_based_on_rtpstream_info(QVector<rtpstream_info_t *> streams)
{
QVector<rtpstream_id_t *> ids;
foreach(rtpstream_info_t *stream_info, streams) {
ids << &stream_info->id;
}
return make_filter_based_on_rtpstream_id(ids);
}

View File

@ -21,6 +21,8 @@
#include <glib.h>
#include "ui/rtp_stream.h"
#include <QString>
class QAction;
@ -231,6 +233,22 @@ bool rect_on_screen(const QRect &rect);
*/
void set_action_shortcuts_visible_in_context_menu(QList<QAction *> actions);
/**
* Make display filter from list of rtpstream_id
*
* @param List of ids
* @return Filter or empty string
*/
QString make_filter_based_on_rtpstream_id(QVector<rtpstream_id_t *> ids);
/**
* Make display filter from list of rtpstream_infos
*
* @param List of streams
* @return Filter or empty string
*/
QString make_filter_based_on_rtpstream_info(QVector<rtpstream_info_t *> streams);
#endif /* __QT_UI_UTILS__H__ */
// XXX Add a routine to fetch the HWND corresponding to a widget using QPlatformIntegration

View File

@ -204,10 +204,7 @@ void VoipCallsDialog::contextMenuEvent(QContextMenuEvent *event)
QMenu popupMenu;
QAction *action;
QMenu *selection_menu = popupMenu.addMenu(tr("Select"));
selection_menu->addAction(ui->actionSelectAll);
selection_menu->addAction(ui->actionSelectNone);
selection_menu->addAction(ui->actionSelectInvert);
popupMenu.addMenu(ui->menuSelect);
action = popupMenu.addAction(tr("Display time as time of day"), this, SLOT(switchTimeOfDay()));
action->setCheckable(true);
action->setChecked(call_infos_model_->timeOfDay());

View File

@ -109,6 +109,17 @@
<string>Open copy menu</string>
</property>
</action>
<widget class="QMenu" name="menuSelect">
<property name="title">
<string>Select</string>
</property>
<property name="toolTipsVisible">
<bool>true</bool>
</property>
<addaction name="actionSelectAll"/>
<addaction name="actionSelectNone"/>
<addaction name="actionSelectInvert"/>
</widget>
<action name="actionSelectAll">
<property name="text">
<string>All</string>