RTP Player: Player is able to skip silence during playback

Code is NOT able to do VAD (Voice Activity Detection) so audio silence
(sequence of equal samples) nor noise are not recognized as silence. Just
missing RTP (Confort Noise, interupted RTP, ...) and muted streams are
recognized as silence for this feature.
User can control duration of shortest silence to skip.
Updated documentation.
This commit is contained in:
Jirka Novak 2021-05-01 13:51:31 +02:00 committed by Wireshark GitLab Utility
parent 71f66bee3b
commit 4c7c377d42
12 changed files with 191 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 321 KiB

View File

@ -442,6 +442,8 @@ When RTP stream contains multiple codecs, SR and PR is based on first observed c
Controls allow a user to:
* btn:[Start]/btn:[Pause]/btn:[Stop] playing of unmuted streams
* btn:[>>] enabling/disabling silence skipping
** Min silence - Minimal duration of silence to skip in seconds. Shorter silence is played as it is.
* 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
@ -456,6 +458,11 @@ Controls allow a user to:
* btn:[Prepare Filter] prepare filter matching selected streams and apply it.
* btn:[Export] - See <<tel-rtp-export>>.
[NOTE]
====
RTP Player detects silence just by missing voice samples (Comfort Noise, interrupted RTP, missing RTP, ...) or when some streams are muted.
====
.RTP stream state indication
image::wsug_graphics/ws-tel-rtp-player_2.png[{screenshot-attrs}]

View File

@ -772,6 +772,18 @@ void RtpAudioStream::stopPlaying()
}
}
void RtpAudioStream::seekPlaying(qint64 samples _U_)
{
if (audio_routing_.isMuted())
return;
if (audio_output_) {
audio_output_->suspend();
audio_file_->seekSample(samples);
audio_output_->resume();
}
}
void RtpAudioStream::outputStateChanged(QAudio::State new_state)
{
if (!audio_output_) return;

View File

@ -139,6 +139,7 @@ public:
void startPlaying();
void pausePlaying();
void stopPlaying();
void seekPlaying(qint64 samples);
void setStereoRequired(bool stereo_required) { stereo_required_ = stereo_required; }
qint16 getMaxSampleValue() { return max_sample_val_; }
void setMaxSampleValue(gint16 max_sample_val) { max_sample_val_used_ = max_sample_val; }
@ -146,6 +147,9 @@ public:
qint64 readSample(SAMPLE *sample);
qint64 getLeadSilenceSamples() { return prepend_samples_; }
qint64 getTotalSamples() { return (audio_file_->getTotalSamples()); }
qint64 getEndOfSilenceSample() { return (audio_file_->getEndOfSilenceSample()); }
double getEndOfSilenceTime() { return (double)getEndOfSilenceSample() / (double)playRate(); }
qint64 convertTimeToSamples(double time) { return (qint64)(time * playRate()); }
bool savePayload(QIODevice *file);
guint getHash() { return rtpstream_id_to_hash(&(id_)); }
rtpstream_id_t *getID() { return &(id_); }

View File

@ -150,6 +150,7 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf, bool capture_
, block_redraw_(false)
, lock_ui_(0)
, read_capture_enabled_(capture_running)
, silence_skipped_time_(0.0)
{
ui->setupUi(this);
loadGeometry(parent.width(), parent.height());
@ -217,6 +218,9 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf, bool capture_
ui->pauseButton->setVisible(false);
ui->stopButton->setIcon(StockIcon("media-playback-stop"));
ui->stopButton->setEnabled(false);
ui->skipSilenceButton->setIcon(StockIcon("media-seek-forward"));
ui->skipSilenceButton->setCheckable(true);
ui->skipSilenceButton->setEnabled(false);
read_btn_ = ui->buttonBox->addButton(ui->actionReadCapture->text(), QDialogButtonBox::ActionRole);
read_btn_->setToolTip(ui->actionReadCapture->toolTip());
@ -259,6 +263,8 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf, bool capture_
ui->playButton->setEnabled(false);
ui->pauseButton->setEnabled(false);
ui->stopButton->setEnabled(false);
ui->skipSilenceButton->setEnabled(false);
ui->minSilenceSpinBox->setEnabled(false);
ui->outputDeviceComboBox->addItem(tr("No devices available"));
ui->outputAudioRate->setEnabled(false);
} else {
@ -939,8 +945,14 @@ void RtpPlayerDialog::updateWidgets()
int count = ui->streamTreeWidget->topLevelItemCount();
int selected = ui->streamTreeWidget->selectedItems().count();
if (count < 1)
if (count < 1) {
enable_play = false;
ui->skipSilenceButton->setEnabled(false);
ui->minSilenceSpinBox->setEnabled(false);
} else {
ui->skipSilenceButton->setEnabled(true);
ui->minSilenceSpinBox->setEnabled(true);
}
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
@ -1283,6 +1295,7 @@ void RtpPlayerDialog::on_playButton_clicked()
// Protect start time against move of marker during the play
start_marker_time_play_ = start_marker_time_;
silence_skipped_time_ = 0.0;
cur_play_pos_->point1->setCoords(start_marker_time_play_, 0.0);
cur_play_pos_->point2->setCoords(start_marker_time_play_, 1.0);
cur_play_pos_->setVisible(true);
@ -1364,12 +1377,55 @@ QAudioOutput *RtpPlayerDialog::getSilenceAudioOutput()
void RtpPlayerDialog::outputNotify()
{
double new_current_pos = 0.0;
double current_pos = 0.0;
double secs = marker_stream_->processedUSecs() / 1000000.0;
if (ui->skipSilenceButton->isChecked()) {
// We should check whether we can skip some silence
// We must calculate in time domain as every stream can use different
// play rate
double min_silence = playing_streams_[0]->getEndOfSilenceTime();
for( int i = 1; i<playing_streams_.count(); ++i ) {
qint64 cur_silence = playing_streams_[i]->getEndOfSilenceTime();
if (cur_silence < min_silence) {
min_silence = cur_silence;
}
}
if (min_silence > 0.0) {
double silence_duration;
// Calculate silence duration we can skip
new_current_pos = first_stream_rel_start_time_ + min_silence;
if (ui->todCheckBox->isChecked()) {
current_pos = secs + start_marker_time_play_ + first_stream_rel_start_time_;
} else {
current_pos = secs + start_marker_time_play_;
}
silence_duration = new_current_pos - current_pos;
if (silence_duration >= ui->minSilenceSpinBox->value()) {
// Skip silence gap and update cursor difference
for( int i = 0; i<playing_streams_.count(); ++i ) {
// Convert silence from time domain to samples
qint64 skip_samples = playing_streams_[i]->convertTimeToSamples(min_silence);
playing_streams_[i]->seekPlaying(skip_samples);
}
silence_skipped_time_ = silence_duration;
}
}
}
// Calculate new cursor position
if (ui->todCheckBox->isChecked()) {
secs += start_marker_time_play_;
secs += silence_skipped_time_;
} else {
secs += start_marker_time_play_;
secs -= first_stream_rel_start_time_;
secs += silence_skipped_time_;
}
setPlayPosition(secs);
}

View File

@ -211,6 +211,7 @@ private:
bool block_redraw_;
int lock_ui_;
bool read_capture_enabled_;
double silence_skipped_time_;
// const QString streamKey(const rtpstream_info_t *rtpstream);
// const QString streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo);

View File

@ -122,12 +122,15 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0,0,0,0,0,1,0,0">
<item>
<widget class="QToolButton" name="playButton">
<property name="text">
<string/>
</property>
<property name="toolTip">
<string>Start playback of all unmuted streams</string>
</property>
</widget>
</item>
<item>
@ -135,6 +138,9 @@
<property name="text">
<string/>
</property>
<property name="toolTip">
<string>Pause/unpause playback</string>
</property>
</widget>
</item>
<item>
@ -142,6 +148,9 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
@ -155,6 +164,9 @@
<property name="text">
<string/>
</property>
<property name="toolTip">
<string>Stop playback</string>
</property>
</widget>
</item>
<item>
@ -162,6 +174,77 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="skipSilenceButton">
<property name="text">
<string/>
</property>
<property name="toolTip">
<string>Enable/disable skipping of silence during playback</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Min silence:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="minSilenceSpinBox">
<property name="toolTip">
<string>Minimum silence duration to skip in seconds</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>2.000000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
@ -180,6 +263,22 @@
<item>
<widget class="QComboBox" name="outputDeviceComboBox"/>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
@ -190,19 +289,6 @@
<item>
<widget class="QComboBox" name="outputAudioRate"/>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>

View File

@ -327,6 +327,15 @@ qint64 RtpAudioFile::getTotalSamples()
return (real_size_/(qint64)sizeof(SAMPLE));
}
qint64 RtpAudioFile::getEndOfSilenceSample()
{
if (cur_frame_.type == RTP_FRAME_SILENCE) {
return (cur_frame_.real_pos + cur_frame_.len) / (qint64)sizeof(SAMPLE);
} else {
return -1;
}
}
qint64 RtpAudioFile::readData(char *data, qint64 maxSize)
{
qint64 to_read = maxSize;

View File

@ -69,6 +69,7 @@ public:
void seekSample(qint64 samples);
qint64 readSample(SAMPLE *sample);
qint64 getTotalSamples();
qint64 getEndOfSilenceSample();
protected:
// Functions for reading data during play