wireshark/ui/qt/utils/rtp_audio_file.cpp
Jirka Novak 4c7c377d42 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.
2021-05-03 21:38:48 +00:00

385 lines
11 KiB
C++

/* rtp_audio_file.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
* RTP samples are stored in "sparse" file. File knows where are silence gaps
* and they are handled special way (not stored).
*
* File uses Frame as piece of information. One Frame match audio of one
* decoded packet or audio silence in between them. Frame holds information
* about frame type (audio/silence), its length and realtime position and
* sample possition (where decoded audio is really stored, with gaps omitted).
*
* There are three stages of the object use
* - writing data by frames during decoding of the stream
* - reading data by frames during creating the visual waveform
* - reading data by bytes/samples during audio play or audio save
*
* There is no stage indication in the object, but there are different calls
* used by the code. For last stage the object looks like QIODevice therefore
* any read of it looks like reading of sequence of bytes.
*
* If audio starts later than start of the file, first Frame contains silence
* record. It is leaved out at some cases.
*/
#include "rtp_audio_file.h"
#include <ws_attributes.h>
RtpAudioFile::RtpAudioFile(bool use_disk_for_temp, bool use_disk_for_frames):
real_pos_(0)
, real_size_(0)
, sample_pos_(0)
, sample_size_(0)
{
QString tempname;
// ReadOnly because we write different way
QIODevice::open(QIODevice::ReadOnly);
tempname = "memory";
if (use_disk_for_temp) {
tempname = QString("%1/wireshark_rtp_stream").arg(QDir::tempPath());
sample_file_ = new QTemporaryFile(tempname, this);
} else {
sample_file_ = new QBuffer(this);
}
if (!sample_file_->open(QIODevice::ReadWrite)) {
// We are out of file resources
delete sample_file_;
qWarning() << "Can't create temp file in " << tempname;
throw -1;
}
tempname = "memory";
if (use_disk_for_frames) {
tempname = QString("%1/wireshark_rtp_frames").arg(QDir::tempPath());
sample_file_frame_ = new QTemporaryFile(tempname, this);
} else {
sample_file_frame_ = new QBuffer(this);
}
if (!sample_file_frame_->open(QIODevice::ReadWrite)) {
// We are out of file resources
delete sample_file_;
delete sample_file_frame_;
qWarning() << "Can't create frame file in " << tempname;
throw -1;
}
}
RtpAudioFile::~RtpAudioFile()
{
if (sample_file_) delete sample_file_;
if (sample_file_frame_) delete sample_file_frame_;
}
/*
* Functions for writing Frames
*/
void RtpAudioFile::setFrameWriteStage()
{
sample_file_->seek(0);
sample_file_frame_->seek(0);
real_pos_ = 0;
real_size_ = 0;
sample_pos_ = 0;
sample_size_ = 0;
}
void RtpAudioFile::frameUpdateRealCounters(qint64 written_bytes)
{
if (real_pos_ < real_size_) {
// We are writing before end, calculate if we are over real_size_
qint64 diff = real_pos_ + written_bytes - real_size_;
if (diff > 0) {
// Update size
real_size_ += diff;
}
} else {
real_size_ += written_bytes;
}
real_pos_ += written_bytes;
}
void RtpAudioFile::frameUpdateSampleCounters(qint64 written_bytes)
{
if (sample_pos_ < sample_size_) {
// We are writing before end, calculate if we are over sample_size_
qint64 diff = sample_pos_ + written_bytes - sample_size_;
if (diff > 0) {
// Update size
sample_size_ += diff;
}
} else {
sample_size_ += written_bytes;
}
sample_pos_ += written_bytes;
}
qint64 RtpAudioFile::frameWriteFrame(guint32 frame_num, qint64 real_pos, qint64 sample_pos, qint64 len, rtp_frame_type type)
{
rtp_frame_info frame_info;
frame_info.real_pos = real_pos;
frame_info.sample_pos = sample_pos;
frame_info.len = len;
frame_info.frame_num = frame_num;
frame_info.type = type;
return sample_file_frame_->write((char *)&frame_info, sizeof(frame_info));
}
void RtpAudioFile::frameWriteSilence(guint32 frame_num, qint64 samples)
{
if (samples < 1) return;
qint64 silence_bytes = samples * SAMPLE_BYTES;
frameWriteFrame(frame_num, real_pos_, sample_pos_, silence_bytes, RTP_FRAME_SILENCE);
frameUpdateRealCounters(silence_bytes);
}
qint64 RtpAudioFile::frameWriteSamples(guint32 frame_num, const char *data, qint64 max_size)
{
gint64 written;
written = sample_file_->write(data, max_size);
if (written != -1) {
frameWriteFrame(frame_num, real_pos_, sample_pos_, written, RTP_FRAME_AUDIO);
frameUpdateRealCounters(written);
frameUpdateSampleCounters(written);
}
return written;
}
/*
* Functions for reading Frames
*/
void RtpAudioFile::setFrameReadStage(qint64 prepend_samples)
{
sample_file_frame_->seek(0);
if (prepend_samples > 0) {
// Skip first frame which contains openning silence
sample_file_frame_->read((char *)&cur_frame_, sizeof(cur_frame_));
}
}
bool RtpAudioFile::readFrameSamples(gint32 *read_buff_bytes, SAMPLE **read_buff, spx_uint32_t *read_len, guint32 *frame_num, rtp_frame_type *type)
{
rtp_frame_info frame_info;
guint64 read_bytes = 0;
if (!sample_file_frame_->read((char *)&frame_info, sizeof(frame_info))) {
// Can't read frame, some error occured
return false;
}
*frame_num = frame_info.frame_num;
*type = frame_info.type;
if (frame_info.type == RTP_FRAME_AUDIO) {
// Resize buffer when needed
if (frame_info.len > *read_buff_bytes) {
while ((frame_info.len > *read_buff_bytes)) {
*read_buff_bytes *= 2;
}
*read_buff = (SAMPLE *) g_realloc(*read_buff, *read_buff_bytes);
}
sample_file_->seek(frame_info.sample_pos);
read_bytes = sample_file_->read((char *)*read_buff, frame_info.len);
} else {
// For silence we do nothing
read_bytes = frame_info.len;
}
*read_len = (spx_uint32_t)(read_bytes / SAMPLE_BYTES);
return true;
}
/*
* Functions for reading data during play
*/
void RtpAudioFile::setDataReadStage()
{
sample_file_frame_->seek(0);
sample_file_frame_->read((char *)&cur_frame_, sizeof(cur_frame_));
real_pos_ = cur_frame_.real_pos;
}
bool RtpAudioFile::open(QIODevice::OpenMode mode)
{
if (mode == QIODevice::ReadOnly) {
return true;
}
return false;
}
qint64 RtpAudioFile::size() const
{
return real_size_;
}
qint64 RtpAudioFile::pos() const
{
return real_pos_;
}
/*
* Seek starts from beginning of Frames and search one where offset belongs
* to. It looks inefficient, but seek is used usually just to jump to 0 or
* to skip first Frame where silence is stored.
*/
bool RtpAudioFile::seek(qint64 off)
{
if (real_size_ <= off) {
// Can't seek above end of file
return false;
}
// Search for correct offset from first frame
sample_file_frame_->seek(0);
while (1) {
// Read frame
if (!sample_file_frame_->read((char *)&cur_frame_, sizeof(cur_frame_))) {
// Can't read frame, some error occured
return false;
}
if ((cur_frame_.real_pos + cur_frame_.len) > off) {
// We found correct frame
// Calculate offset in frame
qint64 diff = off - cur_frame_.real_pos;
qint64 new_real_pos = cur_frame_.real_pos + diff;
qint64 new_sample_pos = cur_frame_.sample_pos + diff;
if (cur_frame_.type == RTP_FRAME_AUDIO) {
// For audio frame we should to seek to correct place
if (!sample_file_->seek(new_sample_pos)) {
return false;
}
// Real seek was successful
real_pos_ = new_real_pos;
return true;
} else {
// For silence frame we blindly confirm it
real_pos_ = new_real_pos;
return true;
}
}
}
return false;
}
qint64 RtpAudioFile::sampleFileSize()
{
return real_size_;
}
void RtpAudioFile::seekSample(qint64 samples)
{
seek(sizeof(SAMPLE) * samples);
}
qint64 RtpAudioFile::readFrameData(char *data , qint64 want_read)
{
// Calculate remaining data in frame
qint64 remaining = cur_frame_.len - (real_pos_ - cur_frame_.real_pos);
qint64 was_read;
if (remaining < want_read) {
// Incorrect call, can't read more than is stored in frame
return -1;
}
if (cur_frame_.type == RTP_FRAME_AUDIO) {
was_read = sample_file_->read(data, want_read);
real_pos_ += was_read;
} else {
memset(data, 0, want_read);
real_pos_ += want_read;
was_read = want_read;
}
return was_read;
}
qint64 RtpAudioFile::readSample(SAMPLE *sample)
{
return read((char *)sample, sizeof(SAMPLE));
}
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;
qint64 can_read;
qint64 was_read = 0;
qint64 remaining;
while (1) {
// Calculate remaining data in frame
remaining = cur_frame_.len - (real_pos_ - cur_frame_.real_pos);
if (remaining > to_read) {
// Even we want to read more, we can read just till end of frame
can_read = to_read;
} else {
can_read = remaining;
}
if (can_read==readFrameData(data, can_read)) {
to_read -= can_read;
data += can_read;
was_read += can_read;
if (real_pos_ >= cur_frame_.real_pos + cur_frame_.len) {
// We exhausted the frame, read next one
if (!sample_file_frame_->read((char *)&cur_frame_, sizeof(cur_frame_))) {
// We are at the end of the file
return was_read;
}
if ((cur_frame_.type == RTP_FRAME_AUDIO) && (!sample_file_->seek(cur_frame_.sample_pos))) {
// We tried to seek to correct place, but it failed
return -1;
}
}
if (to_read == 0) {
return was_read;
}
} else {
return -1;
}
}
}
qint64 RtpAudioFile::writeData(const char *data _U_, qint64 maxSize _U_)
{
// Writing is not supported
return -1;
}