68be1ccd4ddd1eec9ecbfda646c80be0b7672c1e
[metze/wireshark/wip.git] / ui / qt / rtp_audio_stream.cpp
1 /* rtp_audio_frame.h
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
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.
11  *
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.
16  *
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.
20  */
21
22
23 #include "rtp_audio_stream.h"
24
25 #ifdef QT_MULTIMEDIA_LIB
26
27 #include <codecs/speex/speex_resampler.h>
28
29 #include <epan/rtp_pt.h>
30
31 #include <epan/dissectors/packet-rtp.h>
32
33 #include <ui/rtp_media.h>
34 #include <ui/rtp_stream.h>
35
36 #include <wsutil/nstime.h>
37
38 #include <QAudioFormat>
39 #include <QAudioOutput>
40 #include <QDir>
41 #include <QTemporaryFile>
42
43 static spx_int16_t default_audio_sample_rate_ = 8000;
44 static const spx_int16_t visual_sample_rate_ = 1000;
45
46 RtpAudioStream::RtpAudioStream(QObject *parent, _rtp_stream_info *rtp_stream) :
47     QObject(parent),
48     last_sequence_(0),
49     decoders_hash_(rtp_decoder_hash_table_new()),
50     global_start_rel_time_(0.0),
51     start_abs_offset_(0.0),
52     start_rel_time_(0.0),
53     stop_rel_time_(0.0),
54     audio_out_rate_(0),
55     audio_resampler_(0),
56     audio_output_(0),
57     max_sample_val_(1),
58     color_(0)
59 {
60     copy_address(&src_addr_, &rtp_stream->src_addr);
61     src_port_ = rtp_stream->src_port;
62     copy_address(&dst_addr_, &rtp_stream->dest_addr);
63     dst_port_ = rtp_stream->dest_port;
64     ssrc_ = rtp_stream->ssrc;
65
66     // We keep visual samples in memory. Make fewer of them.
67     visual_resampler_ = ws_codec_resampler_init(1, default_audio_sample_rate_,
68                                                 visual_sample_rate_, SPEEX_RESAMPLER_QUALITY_MIN, NULL);
69     ws_codec_resampler_skip_zeros(visual_resampler_);
70
71     QString tempname = QString("%1/wireshark_rtp_stream").arg(QDir::tempPath());
72     tempfile_ = new QTemporaryFile(tempname, this);
73     tempfile_->open();
74
75     // RTP_STREAM_DEBUG("Writing to %s", tempname.toUtf8().constData());
76 }
77
78 RtpAudioStream::~RtpAudioStream()
79 {
80     g_hash_table_destroy(decoders_hash_);
81     if (audio_resampler_) ws_codec_resampler_destroy (audio_resampler_);
82     ws_codec_resampler_destroy (visual_resampler_);
83 }
84
85 bool RtpAudioStream::isMatch(const _rtp_stream_info *rtp_stream) const
86 {
87     if (rtp_stream
88             && addresses_equal(&rtp_stream->src_addr, &src_addr_)
89             && rtp_stream->src_port == src_port_
90             && addresses_equal(&rtp_stream->dest_addr, &dst_addr_)
91             && rtp_stream->dest_port == dst_port_
92             && rtp_stream->ssrc == ssrc_)
93         return true;
94     return false;
95 }
96
97 bool RtpAudioStream::isMatch(const _packet_info *pinfo, const _rtp_info *rtp_info) const
98 {
99     if (pinfo && rtp_info
100             && addresses_equal(&pinfo->src, &src_addr_)
101             && pinfo->srcport == src_port_
102             && addresses_equal(&pinfo->dst, &dst_addr_)
103             && pinfo->destport == dst_port_
104             && rtp_info->info_sync_src == ssrc_)
105         return true;
106     return false;
107 }
108
109 // XXX We add multiple RTP streams here because that's what the GTK+ UI does.
110 // Should we make these distinct, with their own waveforms? It seems like
111 // that would simplify a lot of things.
112 void RtpAudioStream::addRtpStream(const _rtp_stream_info *rtp_stream)
113 {
114     if (!rtp_stream) return;
115
116     // RTP_STREAM_DEBUG("added %d:%u packets", g_list_length(rtp_stream->rtp_packet_list), rtp_stream->packet_count);
117     rtp_streams_ << rtp_stream;
118
119     double stream_srt = nstime_to_sec(&rtp_stream->start_rel_time);
120     if (rtp_streams_.length() < 2 || stream_srt > start_rel_time_) {
121         start_rel_time_ = stop_rel_time_ = stream_srt;
122         start_abs_offset_ = nstime_to_sec(&rtp_stream->start_fd->abs_ts) - start_rel_time_;
123     }
124 }
125
126 static const int sample_bytes_ = sizeof(SAMPLE) / sizeof(char);
127 void RtpAudioStream::addRtpPacket(const struct _packet_info *pinfo, const _rtp_info *rtp_info)
128 {
129     if (!rtp_info) return;
130
131     // Combination of gtk/rtp_player.c:decode_rtp_stream + decode_rtp_packet
132     // XXX This is more messy than it should be.
133
134     SAMPLE *decode_buff = NULL;
135     SAMPLE *resample_buff = NULL;
136     spx_uint32_t cur_in_rate, visual_out_rate;
137     char *write_buff;
138     qint64 write_bytes;
139     unsigned channels;
140     unsigned sample_rate;
141     rtp_packet_t rtp_packet;
142
143     stop_rel_time_ = nstime_to_sec(&pinfo->rel_ts);
144     ws_codec_resampler_get_rate(visual_resampler_, &cur_in_rate, &visual_out_rate);
145
146     QString payload_name;
147     if (rtp_info->info_payload_type_str) {
148         payload_name = rtp_info->info_payload_type_str;
149     } else {
150         payload_name = try_val_to_str_ext(rtp_info->info_payload_type, &rtp_payload_type_short_vals_ext);
151     }
152     if (!payload_name.isEmpty()) {
153         payload_names_ << payload_name;
154     }
155
156     // First, decode the payload.
157     rtp_packet.info = (_rtp_info *) g_memdup(rtp_info, sizeof(struct _rtp_info));
158     rtp_packet.arrive_offset = start_rel_time_;
159     if (rtp_info->info_all_data_present && (rtp_info->info_payload_len != 0)) {
160         rtp_packet.payload_data = (guint8 *)g_malloc(rtp_info->info_payload_len);
161         memcpy(rtp_packet.payload_data, rtp_info->info_data + rtp_info->info_payload_offset, rtp_info->info_payload_len);
162     } else {
163         rtp_packet.payload_data = NULL;
164     }
165
166     //size_t decoded_bytes =
167     decode_rtp_packet(&rtp_packet, &decode_buff, decoders_hash_, &channels, &sample_rate);
168     write_buff = (char *) decode_buff;
169     write_bytes = rtp_info->info_payload_len * sample_bytes_;
170
171     if (tempfile_->pos() == 0) {
172         // First packet. Let it determine our sample rate.
173         audio_out_rate_ = sample_rate;
174
175         last_sequence_ = rtp_info->info_seq_num - 1;
176
177         // Prepend silence to match our sibling streams.
178         int prepend_samples = (start_rel_time_ - global_start_rel_time_) * audio_out_rate_;
179         if (prepend_samples > 0) {
180             int prepend_bytes = prepend_samples * sample_bytes_;
181             char *prepend_buff = (char *) g_malloc(prepend_bytes);
182             SAMPLE silence = 0;
183             memccpy(prepend_buff, &silence, prepend_samples, sample_bytes_);
184             tempfile_->write(prepend_buff, prepend_bytes);
185         }
186     } else if (audio_out_rate_ != sample_rate) {
187         // Resample the audio to match our previous output rate.
188         if (!audio_resampler_) {
189             audio_resampler_ = ws_codec_resampler_init(1, sample_rate, audio_out_rate_, 10, NULL);
190             ws_codec_resampler_skip_zeros(audio_resampler_);
191             // RTP_STREAM_DEBUG("Started resampling from %u to (out) %u Hz.", sample_rate, audio_out_rate_);
192         } else {
193             spx_uint32_t audio_out_rate;
194             ws_codec_resampler_get_rate(audio_resampler_, &cur_in_rate, &audio_out_rate);
195
196             // Adjust rates if needed.
197             if (sample_rate != cur_in_rate) {
198                 ws_codec_resampler_set_rate(audio_resampler_, sample_rate, audio_out_rate);
199                 ws_codec_resampler_set_rate(visual_resampler_, sample_rate, visual_out_rate);
200                 // RTP_STREAM_DEBUG("Changed input rate from %u to %u Hz. Out is %u.", cur_in_rate, sample_rate, audio_out_rate_);
201             }
202         }
203         spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
204         spx_uint32_t out_len = (audio_out_rate_ * (spx_uint32_t)rtp_info->info_payload_len / sample_rate) + (audio_out_rate_ % sample_rate != 0);
205         resample_buff = (SAMPLE *) g_malloc(out_len * sample_bytes_);
206
207         ws_codec_resampler_process_int(audio_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
208         write_buff = (char *) decode_buff;
209         write_bytes = out_len * sample_bytes_;
210     }
211
212     if (rtp_info->info_seq_num != last_sequence_+1) {
213         out_of_seq_timestamps_.append(stop_rel_time_);
214         // XXX Add silence to tempfile_ and visual_samples_
215     }
216     last_sequence_ = rtp_info->info_seq_num;
217
218     // Write the decoded, possibly-resampled audio to our temp file.
219     tempfile_->write(write_buff, write_bytes);
220
221     // Collect our visual samples.
222     spx_uint32_t in_len = (spx_uint32_t)rtp_info->info_payload_len;
223     spx_uint32_t out_len = (visual_out_rate * in_len / sample_rate) + (visual_out_rate % sample_rate != 0);
224     resample_buff = (SAMPLE *) g_realloc(resample_buff, out_len * sizeof(SAMPLE));
225
226     ws_codec_resampler_process_int(visual_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len);
227     for (unsigned i = 0; i < out_len; i++) {
228         packet_timestamps_[stop_rel_time_ + (double) i / visual_out_rate] = pinfo->fd->num;
229         if (qAbs(resample_buff[i]) > max_sample_val_) max_sample_val_ = qAbs(resample_buff[i]);
230         visual_samples_.append(resample_buff[i]);
231     }
232
233     // Finally, write the resampled audio to our temp file and clean up.
234     g_free(rtp_packet.payload_data);
235     g_free(decode_buff);
236     g_free(resample_buff);
237 }
238
239 void RtpAudioStream::reset(double start_rel_time)
240 {
241     last_sequence_ = 0;
242     global_start_rel_time_ = start_rel_time;
243     stop_rel_time_ = start_rel_time_;
244     audio_out_rate_ = 0;
245     max_sample_val_ = 1;
246     packet_timestamps_.clear();
247     visual_samples_.clear();
248     out_of_seq_timestamps_.clear();
249
250     if (audio_resampler_) {
251         ws_codec_resampler_reset_mem(audio_resampler_);
252     }
253     if (visual_resampler_) {
254         ws_codec_resampler_reset_mem(visual_resampler_);
255     }
256     tempfile_->seek(0);
257 }
258
259 const QStringList RtpAudioStream::payloadNames() const
260 {
261     QStringList payload_names = payload_names_.toList();
262     payload_names.sort();
263     return payload_names;
264 }
265
266 const QVector<double> RtpAudioStream::visualTimestamps(bool relative)
267 {
268     QVector<double> ts_keys = packet_timestamps_.keys().toVector();
269     if (relative) return ts_keys;
270
271     QVector<double> adj_timestamps;
272     for (int i = 0; i < ts_keys.size(); i++) {
273         adj_timestamps.append(ts_keys[i] + start_abs_offset_);
274     }
275     return adj_timestamps;
276 }
277
278 // Scale the height of the waveform (max_sample_val_) and adjust its Y
279 // offset so that they overlap slightly (stack_offset_).
280
281 // XXX This means that waveforms can be misleading with respect to relative
282 // amplitude. We might want to add a "global" max_sample_val_.
283 static const double stack_offset_ = G_MAXINT16 / 3;
284 const QVector<double> RtpAudioStream::visualSamples(int y_offset)
285 {
286     QVector<double> adj_samples;
287     double scaled_offset = y_offset * stack_offset_;
288     for (int i = 0; i < visual_samples_.size(); i++) {
289         adj_samples.append(((double)visual_samples_[i] * G_MAXINT16 / max_sample_val_) + scaled_offset);
290     }
291     return adj_samples;
292 }
293
294 const QVector<double> RtpAudioStream::outOfSequenceTimestamps(bool relative)
295 {
296     if (relative) return out_of_seq_timestamps_;
297
298     QVector<double> adj_timestamps;
299     for (int i = 0; i < out_of_seq_timestamps_.size(); i++) {
300         adj_timestamps.append(out_of_seq_timestamps_[i] + start_abs_offset_);
301     }
302     return adj_timestamps;
303 }
304
305 const QVector<double> RtpAudioStream::outOfSequenceSamples(int y_offset)
306 {
307     QVector<double> adj_samples;
308     double scaled_offset = y_offset * stack_offset_;
309     for (int i = 0; i < out_of_seq_timestamps_.size(); i++) {
310         adj_samples.append(scaled_offset);
311     }
312     return adj_samples;
313 }
314
315 quint32 RtpAudioStream::nearestPacket(double timestamp, bool is_relative)
316 {
317     if (packet_timestamps_.keys().count() < 1) return 0;
318
319     if (!is_relative) timestamp -= start_abs_offset_;
320     QMap<double, quint32>::const_iterator it = packet_timestamps_.lowerBound(timestamp);
321     if (it == packet_timestamps_.end()) return 0;
322     return it.value();
323 }
324
325 QAudio::State RtpAudioStream::outputState() const
326 {
327     if (!audio_output_) return QAudio::IdleState;
328     return audio_output_->state();
329 }
330
331 void RtpAudioStream::startPlaying()
332 {
333     if (audio_output_) return;
334
335     QAudioFormat format;
336     format.setSampleRate(audio_out_rate_);
337     format.setSampleSize(sample_bytes_ * 8); // bits
338     format.setSampleType(QAudioFormat::SignedInt);
339     format.setChannelCount(1);
340     format.setCodec("audio/pcm");
341
342     // RTP_STREAM_DEBUG("playing %s %d samples @ %u Hz",
343     //                 tempfile_->fileName().toUtf8().constData(),
344     //                 (int) tempfile_->size(), audio_out_rate_);
345
346     audio_output_ = new QAudioOutput(format, this);
347     audio_output_->setNotifyInterval(65); // ~15 fps
348     connect(audio_output_, SIGNAL(stateChanged(QAudio::State)), this, SLOT(outputStateChanged()));
349     connect(audio_output_, SIGNAL(notify()), this, SLOT(outputNotify()));
350     tempfile_->seek(0);
351     audio_output_->start(tempfile_);
352     emit startedPlaying();
353 }
354
355 void RtpAudioStream::stopPlaying()
356 {
357     if (audio_output_) {
358         audio_output_->stop();
359         delete audio_output_;
360         audio_output_ = NULL;
361     }
362     emit finishedPlaying();
363 }
364
365 void RtpAudioStream::outputStateChanged()
366 {
367     if (!audio_output_) return;
368
369     if (audio_output_->state() == QAudio::IdleState) {
370         // RTP_STREAM_DEBUG("stopped %f", audio_output_->processedUSecs() / 100000.0);
371         delete audio_output_;
372         audio_output_ = NULL;
373
374         emit finishedPlaying();
375     }
376 }
377
378 void RtpAudioStream::outputNotify()
379 {
380     if (!audio_output_) return;
381     emit processedSecs(audio_output_->processedUSecs() / 1000000.0);
382 }
383
384 #endif // QT_MULTIMEDIA_LIB
385
386 /*
387  * Editor modelines
388  *
389  * Local Variables:
390  * c-basic-offset: 4
391  * tab-width: 8
392  * indent-tabs-mode: nil
393  * End:
394  *
395  * ex: set shiftwidth=4 tabstop=8 expandtab:
396  * :indentSize=4:tabSize=8:noTabs=true:
397  */