/* rtp_audio_frame.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "rtp_audio_stream.h" #ifdef QT_MULTIMEDIA_LIB #ifdef HAVE_SPEEXDSP #include #else #include "../../speexdsp/speex_resampler.h" #endif /* HAVE_SPEEXDSP */ #include #include #include #include #include #include #include #include #include #include #include #include #include // To do: // - Only allow one rtpstream_info_t per RtpAudioStream? static const spx_int16_t visual_sample_rate_ = 1000; RtpAudioStream::RtpAudioStream(QObject *parent, rtpstream_id_t *id, bool stereo_required) : QObject(parent) , first_packet_(true) , decoders_hash_(rtp_decoder_hash_table_new()) , global_start_rel_time_(0.0) , start_abs_offset_(0.0) , start_rel_time_(0.0) , stop_rel_time_(0.0) , stereo_required_(stereo_required) , first_sample_rate_(0) , audio_out_rate_(0) , audio_requested_out_rate_(0) , audio_resampler_(0) , audio_output_(NULL) , max_sample_val_(1) , max_sample_val_used_(1) , color_(0) , jitter_buffer_size_(50) , timing_mode_(RtpAudioStream::JitterBuffer) , start_play_time_(0) { rtpstream_id_copy(id, &id_); memset(&rtpstream_, 0, sizeof(rtpstream_)); rtpstream_id_copy(&id_, &rtpstream_.id); // Rates will be set later, we just init visual resampler visual_resampler_ = speex_resampler_init(1, visual_sample_rate_, visual_sample_rate_, SPEEX_RESAMPLER_QUALITY_MIN, NULL); try { // RtpAudioFile is ready for writing Frames audio_file_ = new RtpAudioFile(prefs.gui_rtp_player_use_disk1, prefs.gui_rtp_player_use_disk2); } catch (...) { speex_resampler_destroy(visual_resampler_); rtpstream_info_free_data(&rtpstream_); rtpstream_id_free(&id_); throw -1; } // RTP_STREAM_DEBUG("Writing to %s", tempname.toUtf8().constData()); } RtpAudioStream::~RtpAudioStream() { for (int i = 0; i < rtp_packets_.size(); i++) { rtp_packet_t *rtp_packet = rtp_packets_[i]; g_free(rtp_packet->info); g_free(rtp_packet->payload_data); g_free(rtp_packet); } g_hash_table_destroy(decoders_hash_); if (audio_resampler_) speex_resampler_destroy(audio_resampler_); speex_resampler_destroy(visual_resampler_); rtpstream_info_free_data(&rtpstream_); rtpstream_id_free(&id_); if (audio_file_) delete audio_file_; // temp_file_ is released by audio_output_ if (audio_output_) delete audio_output_; } bool RtpAudioStream::isMatch(const rtpstream_id_t *id) const { if (id && rtpstream_id_equal(&id_, id, RTPSTREAM_ID_EQUAL_SSRC)) return true; return false; } bool RtpAudioStream::isMatch(const _packet_info *pinfo, const _rtp_info *rtp_info) const { if (pinfo && rtp_info && rtpstream_id_equal_pinfo_rtp_info(&id_, pinfo, rtp_info)) return true; return false; } void RtpAudioStream::addRtpPacket(const struct _packet_info *pinfo, const struct _rtp_info *rtp_info) { if (!rtp_info) return; if (first_packet_) { rtpstream_info_analyse_init(&rtpstream_, pinfo, rtp_info); first_packet_ = false; } rtpstream_info_analyse_process(&rtpstream_, pinfo, rtp_info); rtp_packet_t *rtp_packet = g_new0(rtp_packet_t, 1); rtp_packet->info = (struct _rtp_info *) g_memdup2(rtp_info, sizeof(struct _rtp_info)); if (rtp_info->info_all_data_present && (rtp_info->info_payload_len != 0)) { rtp_packet->payload_data = (guint8 *) g_memdup2(&(rtp_info->info_data[rtp_info->info_payload_offset]), rtp_info->info_payload_len); } if (rtp_packets_.size() < 1) { // First packet start_abs_offset_ = nstime_to_sec(&pinfo->abs_ts) - start_rel_time_; start_rel_time_ = stop_rel_time_ = nstime_to_sec(&pinfo->rel_ts); } rtp_packet->frame_num = pinfo->num; rtp_packet->arrive_offset = nstime_to_sec(&pinfo->rel_ts) - start_rel_time_; rtp_packets_ << rtp_packet; } void RtpAudioStream::clearPackets() { for (int i = 0; i < rtp_packets_.size(); i++) { rtp_packet_t *rtp_packet = rtp_packets_[i]; g_free(rtp_packet->info); g_free(rtp_packet->payload_data); g_free(rtp_packet); } rtp_packets_.clear(); rtpstream_info_free_data(&rtpstream_); memset(&rtpstream_, 0, sizeof(rtpstream_)); rtpstream_id_copy(&id_, &rtpstream_.id); first_packet_ = true; } void RtpAudioStream::reset(double global_start_time) { global_start_rel_time_ = global_start_time; stop_rel_time_ = start_rel_time_; audio_out_rate_ = 0; max_sample_val_ = 1; packet_timestamps_.clear(); visual_samples_.clear(); out_of_seq_timestamps_.clear(); jitter_drop_timestamps_.clear(); } AudioRouting RtpAudioStream::getAudioRouting() { return audio_routing_; } void RtpAudioStream::setAudioRouting(AudioRouting audio_routing) { audio_routing_ = audio_routing; } void RtpAudioStream::decode(QAudioDeviceInfo out_device) { if (rtp_packets_.size() < 1) return; if (audio_resampler_) { speex_resampler_reset_mem(audio_resampler_); } audio_file_->setFrameWriteStage(); decodeAudio(out_device); // Skip silence at begin of the stream audio_file_->setFrameReadStage(prepend_samples_); speex_resampler_reset_mem(visual_resampler_); decodeVisual(); audio_file_->setDataReadStage(); } // Side effect: it creates and initiates resampler if needed quint32 RtpAudioStream::calculateAudioOutRate(QAudioDeviceInfo out_device, unsigned int sample_rate, unsigned int requested_out_rate) { quint32 out_rate; // Use the first non-zero rate we find. Ajust it to match // our audio hardware. QAudioFormat format; format.setSampleRate(sample_rate); format.setSampleSize(SAMPLE_BYTES * 8); // bits format.setSampleType(QAudioFormat::SignedInt); if (stereo_required_) { format.setChannelCount(2); } else { format.setChannelCount(1); } format.setCodec("audio/pcm"); if (!out_device.isFormatSupported(format) && (requested_out_rate==0) ) { out_rate = out_device.nearestFormat(format).sampleRate(); audio_resampler_ = speex_resampler_init(1, sample_rate, out_rate, 10, NULL); RTP_STREAM_DEBUG("Started resampling from %u to (out) %u Hz.", sample_rate, out_rate); } else { if ((requested_out_rate!=0) && (requested_out_rate != sample_rate) ) { out_rate = requested_out_rate; audio_resampler_ = speex_resampler_init(1, sample_rate, out_rate, 10, NULL); RTP_STREAM_DEBUG("Started resampling from %u to (out) %u Hz.", sample_rate, out_rate); } else { out_rate = sample_rate; } } RTP_STREAM_DEBUG("Audio sample rate is %u", out_rate); return out_rate; } void RtpAudioStream::decodeAudio(QAudioDeviceInfo out_device) { // XXX This is more messy than it should be. gint32 resample_buff_bytes = 0x1000; SAMPLE *resample_buff = (SAMPLE *) g_malloc(resample_buff_bytes); char *write_buff = NULL; qint64 write_bytes = 0; unsigned int channels = 0; unsigned int sample_rate = 0; guint32 last_sequence = 0; guint32 last_sequence_w = 0; // Last sequence number we wrote data double rtp_time_prev = 0.0; double arrive_time_prev = 0.0; double pack_period = 0.0; double start_time = 0.0; double start_rtp_time = 0.0; guint64 start_timestamp = 0; size_t decoded_bytes_prev = 0; for (int cur_packet = 0; cur_packet < rtp_packets_.size(); cur_packet++) { SAMPLE *decode_buff = NULL; // TODO: Update a progress bar here. rtp_packet_t *rtp_packet = rtp_packets_[cur_packet]; stop_rel_time_ = start_rel_time_ + rtp_packet->arrive_offset; QString payload_name; if (rtp_packet->info->info_payload_type_str) { payload_name = rtp_packet->info->info_payload_type_str; } else { payload_name = try_val_to_str_ext(rtp_packet->info->info_payload_type, &rtp_payload_type_short_vals_ext); } if (!payload_name.isEmpty()) { payload_names_ << payload_name; } if (cur_packet < 1) { // First packet start_timestamp = rtp_packet->info->info_extended_timestamp; start_rtp_time = 0; rtp_time_prev = 0; last_sequence = rtp_packet->info->info_extended_seq_num - 1; } size_t decoded_bytes = decode_rtp_packet(rtp_packet, &decode_buff, decoders_hash_, &channels, &sample_rate); unsigned rtp_clock_rate = sample_rate; if (rtp_packet->info->info_payload_type == PT_G722) { // G.722 sample rate is 16kHz, but RTP clock rate is 8kHz // for historic reasons. rtp_clock_rate = 8000; } // Length 2 for PT_PCM mean silence packet probably, ignore if (decoded_bytes == 0 || sample_rate == 0 || ((rtp_packet->info->info_payload_type == PT_PCMU || rtp_packet->info->info_payload_type == PT_PCMA ) && (decoded_bytes == 2) ) ) { // We didn't decode anything. Clean up and prep for // the next packet. last_sequence = rtp_packet->info->info_extended_seq_num; g_free(decode_buff); continue; } if (audio_out_rate_ == 0) { first_sample_rate_ = sample_rate; // We calculate audio_out_rate just for first sample_rate. // All later are just resampled to it. // Side effect: it creates and initiates resampler if needed audio_out_rate_ = calculateAudioOutRate(out_device, sample_rate, audio_requested_out_rate_); // Calculate count of prepend samples for the stream // The earliest stream starts at 0. // Note: Order of operations and separation to two formulas is // important. // When joined, calculated incorrectly - probably caused by // conversions between int/double prepend_samples_ = (start_rel_time_ - global_start_rel_time_) * sample_rate; prepend_samples_ = prepend_samples_ * audio_out_rate_ / sample_rate; // Prepend silence to match our sibling streams. if ((prepend_samples_ > 0) && (audio_out_rate_ != 0)) { audio_file_->frameWriteSilence(rtp_packet->frame_num, prepend_samples_); } } if (rtp_packet->info->info_extended_seq_num != last_sequence+1) { out_of_seq_timestamps_.append(stop_rel_time_); } last_sequence = rtp_packet->info->info_extended_seq_num; double rtp_time = (double)(rtp_packet->info->info_extended_timestamp-start_timestamp)/rtp_clock_rate - start_rtp_time; double arrive_time; if (timing_mode_ == RtpTimestamp) { arrive_time = rtp_time; } else { arrive_time = rtp_packet->arrive_offset - start_time; } double diff = qAbs(arrive_time - rtp_time); if (diff*1000 > jitter_buffer_size_ && timing_mode_ != Uninterrupted) { // rtp_player.c:628 jitter_drop_timestamps_.append(stop_rel_time_); RTP_STREAM_DEBUG("Packet drop by jitter buffer exceeded %f > %d", diff*1000, jitter_buffer_size_); /* if there was a silence period (more than two packetization * period) resync the source */ if ((rtp_time - rtp_time_prev) > pack_period*2) { qint64 silence_samples; RTP_STREAM_DEBUG("Resync..."); silence_samples = (qint64)((arrive_time - arrive_time_prev)*sample_rate - decoded_bytes_prev / SAMPLE_BYTES); silence_samples = silence_samples * audio_out_rate_ / sample_rate; silence_timestamps_.append(stop_rel_time_); // Timestamp shift can make silence calculation negative if ((silence_samples > 0) && (audio_out_rate_ != 0)) { audio_file_->frameWriteSilence(rtp_packet->frame_num, silence_samples); } decoded_bytes_prev = 0; start_timestamp = rtp_packet->info->info_extended_timestamp; start_rtp_time = 0; start_time = rtp_packet->arrive_offset; rtp_time_prev = 0; } } else { // rtp_player.c:664 /* Add silence if it is necessary */ qint64 silence_samples; if (timing_mode_ == Uninterrupted) { silence_samples = 0; } else { silence_samples = (int)((rtp_time - rtp_time_prev)*sample_rate - decoded_bytes_prev / SAMPLE_BYTES); silence_samples = silence_samples * audio_out_rate_ / sample_rate; } if (silence_samples != 0) { wrong_timestamp_timestamps_.append(stop_rel_time_); } if (silence_samples > 0) { silence_timestamps_.append(stop_rel_time_); if ((silence_samples > 0) && (audio_out_rate_ != 0)) { audio_file_->frameWriteSilence(rtp_packet->frame_num, silence_samples); } } // XXX rtp_player.c:696 adds audio here. rtp_time_prev = rtp_time; pack_period = (double) decoded_bytes / SAMPLE_BYTES / sample_rate; decoded_bytes_prev = decoded_bytes; arrive_time_prev = arrive_time; } // Prepare samples to write write_buff = (char *) decode_buff; write_bytes = decoded_bytes; if (audio_out_rate_ != sample_rate) { // Resample the audio to match output rate. // Buffer is in SAMPLEs spx_uint32_t in_len = (spx_uint32_t) (write_bytes / SAMPLE_BYTES); // Output is audio_out_rate_/sample_rate bigger than input spx_uint32_t out_len = (spx_uint32_t) ((guint64)in_len * audio_out_rate_ / sample_rate); resample_buff = resizeBufferIfNeeded(resample_buff, &resample_buff_bytes, out_len * SAMPLE_BYTES); speex_resampler_process_int(audio_resampler_, 0, decode_buff, &in_len, resample_buff, &out_len); write_buff = (char *) resample_buff; write_bytes = out_len * SAMPLE_BYTES; } // We should write only newer data to avoid duplicates in replay if (last_sequence_w < last_sequence) { // Write the decoded, possibly-resampled audio to our temp file. audio_file_->frameWriteSamples(rtp_packet->frame_num, write_buff, write_bytes); last_sequence_w = last_sequence; } g_free(decode_buff); } g_free(resample_buff); } // We preallocate buffer, 320 samples is enough for most scenarios #define VISUAL_BUFF_LEN (320) #define VISUAL_BUFF_BYTES (SAMPLE_BYTES * VISUAL_BUFF_LEN) void RtpAudioStream::decodeVisual() { spx_uint32_t read_len = 0; gint32 read_buff_bytes = VISUAL_BUFF_BYTES; SAMPLE *read_buff = (SAMPLE *) g_malloc(read_buff_bytes); gint32 resample_buff_bytes = VISUAL_BUFF_BYTES; SAMPLE *resample_buff = (SAMPLE *) g_malloc(resample_buff_bytes); unsigned int sample_no = 0; spx_uint32_t out_len; guint32 frame_num; rtp_frame_type type; speex_resampler_set_rate(visual_resampler_, audio_out_rate_, visual_sample_rate_); // Loop over every frame record // readFrameSamples() maintains size of buffer for us while (audio_file_->readFrameSamples(&read_buff_bytes, &read_buff, &read_len, &frame_num, &type)) { out_len = (spx_uint32_t)(((guint64)read_len * visual_sample_rate_ ) / audio_out_rate_); if (type == RTP_FRAME_AUDIO) { // We resample only audio samples resample_buff = resizeBufferIfNeeded(resample_buff, &resample_buff_bytes, out_len * SAMPLE_BYTES); // Resample speex_resampler_process_int(visual_resampler_, 0, read_buff, &read_len, resample_buff, &out_len); // Create timestamp and visual sample for (unsigned i = 0; i < out_len; i++) { double time = start_rel_time_ + (double) sample_no / visual_sample_rate_; packet_timestamps_[time] = frame_num; if (qAbs(resample_buff[i]) > max_sample_val_) max_sample_val_ = qAbs(resample_buff[i]); visual_samples_.append(resample_buff[i]); sample_no++; } } else { // Insert end of line mark double time = start_rel_time_ + (double) sample_no / visual_sample_rate_; packet_timestamps_[time] = frame_num; visual_samples_.append(SAMPLE_NaN); sample_no += out_len; } } max_sample_val_used_ = max_sample_val_; g_free(resample_buff); g_free(read_buff); } const QStringList RtpAudioStream::payloadNames() const { QStringList payload_names = payload_names_.values(); payload_names.sort(); return payload_names; } const QVector RtpAudioStream::visualTimestamps(bool relative) { QVector ts_keys = packet_timestamps_.keys().toVector(); if (relative) return ts_keys; QVector adj_timestamps; for (int i = 0; i < ts_keys.size(); i++) { adj_timestamps.append(ts_keys[i] + start_abs_offset_ - start_rel_time_); } return adj_timestamps; } // Scale the height of the waveform to global scale (max_sample_val_used_) // and adjust its Y offset so that they overlap slightly (stack_offset_). static const double stack_offset_ = G_MAXINT16 / 3; const QVector RtpAudioStream::visualSamples(int y_offset) { QVector adj_samples; double scaled_offset = y_offset * stack_offset_; for (int i = 0; i < visual_samples_.size(); i++) { if (SAMPLE_NaN != visual_samples_[i]) { adj_samples.append(((double)visual_samples_[i] * G_MAXINT16 / max_sample_val_used_) + scaled_offset); } else { // Convert to break in graph line adj_samples.append(qQNaN()); } } return adj_samples; } const QVector RtpAudioStream::outOfSequenceTimestamps(bool relative) { if (relative) return out_of_seq_timestamps_; QVector adj_timestamps; for (int i = 0; i < out_of_seq_timestamps_.size(); i++) { adj_timestamps.append(out_of_seq_timestamps_[i] + start_abs_offset_ - start_rel_time_); } return adj_timestamps; } const QVector RtpAudioStream::outOfSequenceSamples(int y_offset) { QVector adj_samples; double scaled_offset = y_offset * stack_offset_; // XXX Should be different for seq, jitter, wrong & silence for (int i = 0; i < out_of_seq_timestamps_.size(); i++) { adj_samples.append(scaled_offset); } return adj_samples; } const QVector RtpAudioStream::jitterDroppedTimestamps(bool relative) { if (relative) return jitter_drop_timestamps_; QVector adj_timestamps; for (int i = 0; i < jitter_drop_timestamps_.size(); i++) { adj_timestamps.append(jitter_drop_timestamps_[i] + start_abs_offset_ - start_rel_time_); } return adj_timestamps; } const QVector RtpAudioStream::jitterDroppedSamples(int y_offset) { QVector adj_samples; double scaled_offset = y_offset * stack_offset_; // XXX Should be different for seq, jitter, wrong & silence for (int i = 0; i < jitter_drop_timestamps_.size(); i++) { adj_samples.append(scaled_offset); } return adj_samples; } const QVector RtpAudioStream::wrongTimestampTimestamps(bool relative) { if (relative) return wrong_timestamp_timestamps_; QVector adj_timestamps; for (int i = 0; i < wrong_timestamp_timestamps_.size(); i++) { adj_timestamps.append(wrong_timestamp_timestamps_[i] + start_abs_offset_ - start_rel_time_); } return adj_timestamps; } const QVector RtpAudioStream::wrongTimestampSamples(int y_offset) { QVector adj_samples; double scaled_offset = y_offset * stack_offset_; // XXX Should be different for seq, jitter, wrong & silence for (int i = 0; i < wrong_timestamp_timestamps_.size(); i++) { adj_samples.append(scaled_offset); } return adj_samples; } const QVector RtpAudioStream::insertedSilenceTimestamps(bool relative) { if (relative) return silence_timestamps_; QVector adj_timestamps; for (int i = 0; i < silence_timestamps_.size(); i++) { adj_timestamps.append(silence_timestamps_[i] + start_abs_offset_ - start_rel_time_); } return adj_timestamps; } const QVector RtpAudioStream::insertedSilenceSamples(int y_offset) { QVector adj_samples; double scaled_offset = y_offset * stack_offset_; // XXX Should be different for seq, jitter, wrong & silence for (int i = 0; i < silence_timestamps_.size(); i++) { adj_samples.append(scaled_offset); } return adj_samples; } quint32 RtpAudioStream::nearestPacket(double timestamp, bool is_relative) { if (packet_timestamps_.keys().count() < 1) return 0; if (!is_relative) timestamp -= start_abs_offset_; QMap::const_iterator it = packet_timestamps_.lowerBound(timestamp); if (it == packet_timestamps_.end()) return 0; return it.value(); } QAudio::State RtpAudioStream::outputState() const { if (!audio_output_) return QAudio::IdleState; return audio_output_->state(); } const QString RtpAudioStream::formatDescription(const QAudioFormat &format) { QString fmt_descr = QString("%1 Hz, ").arg(format.sampleRate()); switch (format.sampleType()) { case QAudioFormat::SignedInt: fmt_descr += "Int"; break; case QAudioFormat::UnSignedInt: fmt_descr += "UInt"; break; case QAudioFormat::Float: fmt_descr += "Float"; break; default: fmt_descr += "Unknown"; break; } fmt_descr += QString::number(format.sampleSize()); fmt_descr += format.byteOrder() == QAudioFormat::BigEndian ? "BE" : "LE"; return fmt_descr; } QString RtpAudioStream::getIDAsQString() { gchar *src_addr_str = address_to_display(NULL, &id_.src_addr); gchar *dst_addr_str = address_to_display(NULL, &id_.dst_addr); QString str = QString("%1:%2 - %3:%4 %5") .arg(src_addr_str) .arg(id_.src_port) .arg(dst_addr_str) .arg(id_.dst_port) .arg(QString("0x%1").arg(id_.ssrc, 0, 16)); wmem_free(NULL, src_addr_str); wmem_free(NULL, dst_addr_str); return str; } bool RtpAudioStream::prepareForPlay(QAudioDeviceInfo out_device) { qint64 start_pos; qint64 size; if (audio_routing_.isMuted()) return false; if (audio_output_) return false; if (audio_out_rate_ == 0) { /* It is observed, but is not an error QString error = tr("RTP stream (%1) is empty or codec is unsupported.") .arg(getIDAsQString()); emit playbackError(error); */ return false; } QAudioFormat format; format.setSampleRate(audio_out_rate_); format.setSampleSize(SAMPLE_BYTES * 8); // bits format.setSampleType(QAudioFormat::SignedInt); if (stereo_required_) { format.setChannelCount(2); } else { format.setChannelCount(1); } format.setCodec("audio/pcm"); // RTP_STREAM_DEBUG("playing %s %d samples @ %u Hz", // sample_file_->fileName().toUtf8().constData(), // (int) sample_file_->size(), audio_out_rate_); if (!out_device.isFormatSupported(format)) { QString playback_error = tr("%1 does not support PCM at %2. Preferred format is %3") .arg(out_device.deviceName()) .arg(formatDescription(format)) .arg(formatDescription(out_device.nearestFormat(format))); emit playbackError(playback_error); } start_pos = (qint64)(start_play_time_ * SAMPLE_BYTES * audio_out_rate_); // Round to SAMPLE_BYTES boundary start_pos = (start_pos / SAMPLE_BYTES) * SAMPLE_BYTES; size = audio_file_->sampleFileSize(); if (stereo_required_) { // There is 2x more samples for stereo start_pos *= 2; size *= 2; } if (start_pos < size) { audio_file_->setDataReadStage(); temp_file_ = new AudioRoutingFilter(audio_file_, stereo_required_, audio_routing_); temp_file_->seek(start_pos); if (audio_output_) delete audio_output_; audio_output_ = new QAudioOutput(out_device, format, this); connect(audio_output_, &QAudioOutput::stateChanged, this, &RtpAudioStream::outputStateChanged); return true; } else { // Report stopped audio if start position is later than stream ends outputStateChanged(QAudio::StoppedState); return false; } return false; } void RtpAudioStream::startPlaying() { audio_output_->start(temp_file_); // QTBUG-6548 StoppedState is not always emitted on error, force a cleanup // in case playback fails immediately. if (audio_output_ && audio_output_->state() == QAudio::StoppedState) { outputStateChanged(QAudio::StoppedState); } } void RtpAudioStream::pausePlaying() { if (audio_routing_.isMuted()) return; if (audio_output_) { if (QAudio::ActiveState == audio_output_->state()) { audio_output_->suspend(); } else if (QAudio::SuspendedState == audio_output_->state()) { audio_output_->resume(); } } } void RtpAudioStream::stopPlaying() { if (audio_routing_.isMuted()) return; if (audio_output_) { if (audio_output_->state() == QAudio::StoppedState) { // Looks like "delayed" QTBUG-6548 // It may happen that stream is stopped, but no signal emited // Probably triggered by some issue in sound system which is not // handled by Qt correctly outputStateChanged(QAudio::StoppedState); } else { audio_output_->stop(); } } } 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; // On some platforms including macOS and Windows, the stateChanged signal // is emitted while a QMutexLocker is active. As a result we shouldn't // delete audio_output_ here. switch (new_state) { case QAudio::StoppedState: { // RTP_STREAM_DEBUG("stopped %f", audio_output_->processedUSecs() / 100000.0); // Detach from parent (RtpAudioStream) to prevent deleteLater // from being run during destruction of this class. QAudio::Error error = audio_output_->error(); audio_output_->setParent(0); audio_output_->disconnect(); audio_output_->deleteLater(); audio_output_ = NULL; emit finishedPlaying(this, error); break; } case QAudio::IdleState: // Workaround for Qt behaving on some platforms with some soundcards: // When ->stop() is called from outputStateChanged(), // internalQMutexLock is locked and application hangs. // We can stop the stream later. QTimer::singleShot(0, this, SLOT(delayedStopStream())); break; default: break; } } void RtpAudioStream::delayedStopStream() { audio_output_->stop(); } SAMPLE *RtpAudioStream::resizeBufferIfNeeded(SAMPLE *buff, gint32 *buff_bytes, qint64 requested_size) { if (requested_size > *buff_bytes) { while ((requested_size > *buff_bytes)) *buff_bytes *= 2; buff = (SAMPLE *) g_realloc(buff, *buff_bytes); } return buff; } void RtpAudioStream::seekSample(qint64 samples) { audio_file_->seekSample(samples); } qint64 RtpAudioStream::readSample(SAMPLE *sample) { return audio_file_->readSample(sample); } bool RtpAudioStream::savePayload(QIODevice *file) { for (int cur_packet = 0; cur_packet < rtp_packets_.size(); cur_packet++) { // TODO: Update a progress bar here. rtp_packet_t *rtp_packet = rtp_packets_[cur_packet]; if ((rtp_packet->info->info_payload_type != PT_CN) && (rtp_packet->info->info_payload_type != PT_CN_OLD)) { // All other payloads int64_t nchars; if (rtp_packet->payload_data && (rtp_packet->info->info_payload_len > 0)) { nchars = file->write((char *)rtp_packet->payload_data, rtp_packet->info->info_payload_len); if (nchars != rtp_packet->info->info_payload_len) { return false; } } } } return true; } #endif // QT_MULTIMEDIA_LIB