forked from osmocom/wireshark
4c7c377d42
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.
385 lines
11 KiB
C++
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;
|
|
}
|
|
|