forked from osmocom/wireshark
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:
parent
71f66bee3b
commit
4c7c377d42
Binary file not shown.
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 281 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 321 KiB |
Binary file not shown.
|
@ -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}]
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_); }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
void seekSample(qint64 samples);
|
||||
qint64 readSample(SAMPLE *sample);
|
||||
qint64 getTotalSamples();
|
||||
qint64 getEndOfSilenceSample();
|
||||
|
||||
protected:
|
||||
// Functions for reading data during play
|
||||
|
|
Loading…
Reference in New Issue