3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "rtp_audio_stream.h"
25 #ifdef QT_MULTIMEDIA_LIB
27 #include <codecs/speex/speex_resampler.h>
29 #include <epan/rtp_pt.h>
31 #include <epan/dissectors/packet-rtp.h>
33 #include <ui/rtp_media.h>
34 #include <ui/rtp_stream.h>
36 #include <wsutil/nstime.h>
38 #include <QAudioFormat>
39 #include <QAudioOutput>
41 #include <QTemporaryFile>
43 static spx_int16_t default_audio_sample_rate_ = 8000;
44 static const spx_int16_t visual_sample_rate_ = 1000;
46 RtpAudioStream::RtpAudioStream(QObject *parent, _rtp_stream_info *rtp_stream) :
48 decoders_hash_(rtp_decoder_hash_table_new()),
49 global_start_rel_time_(0.0),
50 start_abs_offset_(0.0),
58 copy_address(&src_addr_, &rtp_stream->src_addr);
59 src_port_ = rtp_stream->src_port;
60 copy_address(&dst_addr_, &rtp_stream->dest_addr);
61 dst_port_ = rtp_stream->dest_port;
62 ssrc_ = rtp_stream->ssrc;
64 // We keep visual samples in memory. Make fewer of them.
65 visual_resampler_ = ws_codec_resampler_init(1, default_audio_sample_rate_,
66 visual_sample_rate_, SPEEX_RESAMPLER_QUALITY_MIN, NULL);
67 ws_codec_resampler_skip_zeros(visual_resampler_);
69 QString tempname = QString("%1/wireshark_rtp_stream").arg(QDir::tempPath());
70 tempfile_ = new QTemporaryFile(tempname, this);
73 // RTP_STREAM_DEBUG("Writing to %s", tempname.toUtf8().constData());
76 RtpAudioStream::~RtpAudioStream()
78 g_hash_table_destroy(decoders_hash_);
79 if (audio_resampler_) ws_codec_resampler_destroy (audio_resampler_);
80 ws_codec_resampler_destroy (visual_resampler_);
83 bool RtpAudioStream::isMatch(const _rtp_stream_info *rtp_stream) const
86 && addresses_equal(&rtp_stream->src_addr, &src_addr_)
87 && rtp_stream->src_port == src_port_
88 && addresses_equal(&rtp_stream->dest_addr, &dst_addr_)
89 && rtp_stream->dest_port == dst_port_
90 && rtp_stream->ssrc == ssrc_)
95 bool RtpAudioStream::isMatch(const _packet_info *pinfo, const _rtp_info *rtp_info) const
98 && addresses_equal(&pinfo->src, &src_addr_)
99 && pinfo->srcport == src_port_
100 && addresses_equal(&pinfo->dst, &dst_addr_)
101 && pinfo->destport == dst_port_
102 && rtp_info->info_sync_src == ssrc_)
107 // XXX We add multiple RTP streams here because that's what the GTK+ UI does.
108 // Should we make these distinct, with their own waveforms? It seems like
109 // that would simplify a lot of things.
110 void RtpAudioStream::addRtpStream(const _rtp_stream_info *rtp_stream)
112 if (!rtp_stream) return;
114 // RTP_STREAM_DEBUG("added %d:%u packets", g_list_length(rtp_stream->rtp_packet_list), rtp_stream->packet_count);
115 rtp_streams_ << rtp_stream;
117 double stream_srt = nstime_to_sec(&rtp_stream->start_rel_time);
118 if (rtp_streams_.length() < 2 || stream_srt > start_rel_time_) {
119 start_rel_time_ = stop_rel_time_ = stream_srt;
120 start_abs_offset_ = nstime_to_sec(&rtp_stream->start_fd->abs_ts) - start_rel_time_;
124 static const int sample_bytes_ = sizeof(SAMPLE) / sizeof(char);
125 void RtpAudioStream::addRtpPacket(const struct _packet_info *pinfo, const _rtp_info *rtp_info)
127 if (!rtp_info) return;
129 // Combination of gtk/rtp_player.c:decode_rtp_stream + decode_rtp_packet
130 // XXX This is more messy than it should be.
132 SAMPLE *decode_buff = NULL;
133 SAMPLE *resample_buff = NULL;
134 spx_uint32_t cur_in_rate, visual_out_rate;
138 unsigned sample_rate;
139 rtp_packet_t rtp_packet;
141 stop_rel_time_ = nstime_to_sec(&pinfo->rel_ts);
142 ws_codec_resampler_get_rate(visual_resampler_, &cur_in_rate, &visual_out_rate);
144 QString payload_name;
145 if (rtp_info->info_payload_type_str) {
146 payload_name = rtp_info->info_payload_type_str;
148 payload_name = try_val_to_str_ext(rtp_info->info_payload_type, &rtp_payload_type_short_vals_ext);
150 if (!payload_name.isEmpty()) {
151 payload_names_ << payload_name;
154 // First, decode the payload.
155 rtp_packet.info = (_rtp_info *) g_memdup(rtp_info, sizeof(struct _rtp_info));
156 rtp_packet.arrive_offset = start_rel_time_;
157 if (rtp_info->info_all_data_present && (rtp_info->info_payload_len != 0)) {
158 rtp_packet.payload_data = (guint8 *)g_malloc(rtp_info->info_payload_len);
159 memcpy(rtp_packet.payload_data, rtp_info->info_data + rtp_info->info_payload_offset, rtp_info->info_payload_len);
161 rtp_packet.payload_data = NULL;
164 //size_t decoded_bytes =
165 decode_rtp_packet(&rtp_packet, &decode_buff, decoders_hash_, &channels, &sample_rate);
166 write_buff = (char *) decode_buff;
167 write_bytes = rtp_info->info_payload_len * sample_bytes_;
169 if (tempfile_->pos() == 0) {
170 // First packet. Let it determine our sample rate.
171 audio_out_rate_ = sample_rate;
173 last_sequence_ = rtp_info->info_seq_num - 1;
175 // Prepend silence to match our sibling streams.
176 int prepend_samples = (start_rel_time_ - global_start_rel_time_) * audio_out_rate_;
177 if (prepend_samples > 0) {
178 int prepend_bytes = prepend_samples * sample_bytes_;
179 char *prepend_buff = (char *) g_malloc(prepend_bytes);
181 memccpy(prepend_buff, &silence, prepend_samples, sample_bytes_);
182 tempfile_->write(prepend_buff, prepend_bytes);
184 } else if (audio_out_rate_ != sample_rate) {
185 // Resample the audio to match our previous output rate.
186 if (!audio_resampler_) {
187 audio_resampler_ = ws_codec_resampler_init(1, sample_rate, audio_out_rate_, 10, NULL);
188 ws_codec_resampler_skip_zeros(audio_resampler_);
189 // RTP_STREAM_DEBUG("Started resampling from %u to (out) %u Hz.", sample_rate, audio_out_rate_);
191 spx_uint32_t audio_out_rate;
192 ws_codec_resampler_get_rate(audio_resampler_, &cur_in_rate, &audio_out_rate);
194 // Adjust rates if needed.
195 if (sample_rate != cur_in_rate) {
196 ws_codec_resampler_set_rate(audio_resampler_, sample_rate, audio_out_rate);
197 ws_codec_resampler_set_rate(visual_resampler_, sample_rate, visual_out_rate);
198 // RTP_STREAM_DEBUG("Changed input rate from %u to %u Hz. Out is %u.", cur_in_rate, sample_rate, audio_out_rate_);
201 spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
202 spx_uint32_t out_len = (audio_out_rate_ * (spx_uint32_t)rtp_info->info_payload_len / sample_rate) + (audio_out_rate_ % sample_rate != 0);
203 resample_buff = (SAMPLE *) g_malloc(out_len * sample_bytes_);
205 ws_codec_resampler_process_int(audio_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
206 write_buff = (char *) decode_buff;
207 write_bytes = out_len * sample_bytes_;
210 if (rtp_info->info_seq_num != last_sequence_+1) {
211 out_of_seq_timestamps_.append(stop_rel_time_);
212 // XXX Add silence to tempfile_ and visual_samples_
214 last_sequence_ = rtp_info->info_seq_num;
216 // Write the decoded, possibly-resampled audio to our temp file.
217 tempfile_->write(write_buff, write_bytes);
219 // Collect our visual samples.
220 spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
221 spx_uint32_t out_len = (visual_out_rate * in_len / sample_rate) + (visual_out_rate % sample_rate != 0);
222 resample_buff = (SAMPLE *) g_realloc(resample_buff, out_len * sizeof(SAMPLE));
224 ws_codec_resampler_process_int(visual_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
225 for (unsigned i = 0; i < out_len; i++) {
226 packet_timestamps_[stop_rel_time_ + (double) i / visual_out_rate] = pinfo->fd->num;
227 if (qAbs(resample_buff[i]) > max_sample_val_) max_sample_val_ = qAbs(resample_buff[i]);
228 visual_samples_.append(resample_buff[i]);
231 // Finally, write the resampled audio to our temp file and clean up.
232 g_free(rtp_packet.payload_data);
234 g_free(resample_buff);
237 void RtpAudioStream::reset(double start_rel_time)
240 global_start_rel_time_ = start_rel_time;
241 stop_rel_time_ = start_rel_time_;
244 packet_timestamps_.clear();
245 visual_samples_.clear();
246 out_of_seq_timestamps_.clear();
248 if (audio_resampler_) {
249 ws_codec_resampler_reset_mem(audio_resampler_);
251 if (visual_resampler_) {
252 ws_codec_resampler_reset_mem(visual_resampler_);
257 const QStringList RtpAudioStream::payloadNames() const
259 QStringList payload_names = payload_names_.toList();
260 payload_names.sort();
261 return payload_names;
264 const QVector<double> RtpAudioStream::visualTimestamps(bool relative)
266 QVector<double> ts_keys = packet_timestamps_.keys().toVector();
267 if (relative) return ts_keys;
269 QVector<double> adj_timestamps;
270 for (int i = 0; i < ts_keys.length(); i++) {
271 adj_timestamps.append(ts_keys[i] + start_abs_offset_);
273 return adj_timestamps;
276 // Scale the height of the waveform (max_sample_val_) and adjust its Y
277 // offset so that they overlap slightly (stack_offset_).
279 // XXX This means that waveforms can be misleading with respect to relative
280 // amplitude. We might want to add a "global" max_sample_val_.
281 static const double stack_offset_ = G_MAXINT16 / 3;
282 const QVector<double> RtpAudioStream::visualSamples(int y_offset)
284 QVector<double> adj_samples;
285 double scaled_offset = y_offset * stack_offset_;
286 for (int i = 0; i < visual_samples_.length(); i++) {
287 adj_samples.append(((double)visual_samples_[i] * G_MAXINT16 / max_sample_val_) + scaled_offset);
292 const QVector<double> RtpAudioStream::outOfSequenceTimestamps(bool relative)
294 if (relative) return out_of_seq_timestamps_;
296 QVector<double> adj_timestamps;
297 for (int i = 0; i < out_of_seq_timestamps_.length(); i++) {
298 adj_timestamps.append(out_of_seq_timestamps_[i] + start_abs_offset_);
300 return adj_timestamps;
303 const QVector<double> RtpAudioStream::outOfSequenceSamples(int y_offset)
305 QVector<double> adj_samples;
306 double scaled_offset = y_offset * stack_offset_;
307 for (int i = 0; i < out_of_seq_timestamps_.length(); i++) {
308 adj_samples.append(scaled_offset);
313 quint32 RtpAudioStream::nearestPacket(double timestamp, bool is_relative)
315 if (packet_timestamps_.keys().count() < 1) return 0;
317 if (!is_relative) timestamp -= start_abs_offset_;
318 QMap<double, quint32>::const_iterator it = packet_timestamps_.lowerBound(timestamp);
319 if (it == packet_timestamps_.begin()) return 0;
323 QAudio::State RtpAudioStream::outputState() const
325 if (!audio_output_) return QAudio::IdleState;
326 return audio_output_->state();
329 void RtpAudioStream::startPlaying()
331 if (audio_output_) return;
334 format.setSampleRate(audio_out_rate_);
335 format.setSampleSize(sample_bytes_ * 8); // bits
336 format.setSampleType(QAudioFormat::SignedInt);
337 format.setChannelCount(1);
338 format.setCodec("audio/pcm");
340 // RTP_STREAM_DEBUG("playing %s %d samples @ %u Hz",
341 // tempfile_->fileName().toUtf8().constData(),
342 // (int) tempfile_->size(), audio_out_rate_);
344 audio_output_ = new QAudioOutput(format, this);
345 audio_output_->setNotifyInterval(65); // ~15 fps
346 connect(audio_output_, SIGNAL(stateChanged(QAudio::State)), this, SLOT(outputStateChanged()));
347 connect(audio_output_, SIGNAL(notify()), this, SLOT(outputNotify()));
349 audio_output_->start(tempfile_);
350 emit startedPlaying();
353 void RtpAudioStream::stopPlaying()
356 audio_output_->stop();
357 delete audio_output_;
358 audio_output_ = NULL;
360 emit finishedPlaying();
363 void RtpAudioStream::outputStateChanged()
365 if (!audio_output_) return;
367 if (audio_output_->state() == QAudio::IdleState) {
368 // RTP_STREAM_DEBUG("stopped %f", audio_output_->processedUSecs() / 100000.0);
369 delete audio_output_;
370 audio_output_ = NULL;
372 emit finishedPlaying();
376 void RtpAudioStream::outputNotify()
378 if (!audio_output_) return;
379 emit processedSecs(audio_output_->processedUSecs() / 1000000.0);
382 #endif // QT_MULTIMEDIA_LIB
390 * indent-tabs-mode: nil
393 * ex: set shiftwidth=4 tabstop=8 expandtab:
394 * :indentSize=4:tabSize=8:noTabs=true: