| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/cast/sender/audio_sender.h" |
| |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/media_switches.h" |
| #include "media/cast/common/openscreen_conversion_helpers.h" |
| #include "media/cast/common/rtp_time.h" |
| #include "media/cast/common/sender_encoded_frame.h" |
| #include "media/cast/encoding/audio_encoder.h" |
| #include "media/cast/net/cast_transport_config.h" |
| #include "media/cast/sender/openscreen_frame_sender.h" |
| #include "third_party/openscreen/src/cast/streaming/sender.h" |
| |
| namespace media::cast { |
| namespace { |
| |
| // UMA histogram for the percentage of dropped audio frames. |
| constexpr char kHistogramDroppedFrames[] = |
| "CastStreaming.Sender.Audio.PercentDroppedFrames"; |
| |
| // UMA histogram for recording when a frame is dropped. |
| constexpr char kHistogramFrameDropped[] = |
| "CastStreaming.Sender.Audio.FrameDropped"; |
| |
| } // namespace |
| |
| AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, |
| const FrameSenderConfig& audio_config, |
| StatusChangeOnceCallback status_change_cb, |
| CastTransport* const transport_sender) |
| : AudioSender(cast_environment, |
| audio_config, |
| std::move(status_change_cb), |
| FrameSender::Create(cast_environment, |
| audio_config, |
| transport_sender, |
| *this)) {} |
| |
| AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, |
| const FrameSenderConfig& audio_config, |
| StatusChangeOnceCallback status_change_cb, |
| std::unique_ptr<openscreen::cast::Sender> sender) |
| : AudioSender(cast_environment, |
| audio_config, |
| std::move(status_change_cb), |
| FrameSender::Create(cast_environment, |
| audio_config, |
| std::move(sender), |
| *this)) { |
| DCHECK(base::FeatureList::IsEnabled(kOpenscreenCastStreamingSession)); |
| } |
| |
| AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, |
| const FrameSenderConfig& audio_config, |
| StatusChangeOnceCallback status_change_cb, |
| std::unique_ptr<FrameSender> sender) |
| : cast_environment_(cast_environment), |
| rtp_timebase_(audio_config.rtp_timebase), |
| frame_sender_(std::move(sender)) { |
| if (!audio_config.use_hardware_encoder) { |
| audio_encoder_ = std::make_unique<AudioEncoder>( |
| std::move(cast_environment), audio_config.channels, rtp_timebase_, |
| audio_config.max_bitrate, audio_config.codec, |
| base::BindRepeating(&AudioSender::OnEncodedAudioFrame, AsWeakPtr())); |
| } |
| |
| // AudioEncoder provides no operational status changes during normal use. |
| // Post a task now with its initialization result status to allow the client |
| // to start sending frames. |
| cast_environment_->PostTask( |
| CastEnvironment::MAIN, FROM_HERE, |
| base::BindOnce(std::move(status_change_cb), |
| audio_encoder_ ? audio_encoder_->InitializationResult() |
| : STATUS_INVALID_CONFIGURATION)); |
| |
| // The number of samples per encoded audio frame depends on the codec and its |
| // initialization parameters. Now that we have an encoder, we can calculate |
| // the maximum frame rate. |
| frame_sender_->SetMaxFrameRate(rtp_timebase_ / |
| audio_encoder_->GetSamplesPerFrame()); |
| } |
| |
| AudioSender::~AudioSender() { |
| // Record the number of frames dropped during this session. |
| base::UmaHistogramPercentage(kHistogramDroppedFrames, |
| (number_of_frames_dropped_ * 100) / |
| std::max(1, number_of_frames_inserted_)); |
| } |
| |
| void AudioSender::InsertAudio(std::unique_ptr<AudioBus> audio_bus, |
| const base::TimeTicks& recorded_time) { |
| DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| |
| if (!audio_encoder_) { |
| NOTREACHED(); |
| return; |
| } |
| |
| number_of_frames_inserted_++; |
| const base::TimeDelta next_frame_duration = |
| ToTimeDelta(RtpTimeDelta::FromTicks(audio_bus->frames()), rtp_timebase_); |
| const CastStreamingFrameDropReason reason = |
| frame_sender_->ShouldDropNextFrame(next_frame_duration); |
| if (reason != CastStreamingFrameDropReason::kNotDropped) { |
| number_of_frames_dropped_++; |
| base::UmaHistogramEnumeration(kHistogramFrameDropped, reason); |
| TRACE_EVENT_INSTANT2("cast.stream", "Audio Frame Drop (raw frame)", |
| TRACE_EVENT_SCOPE_THREAD, "duration", |
| next_frame_duration, "reason", reason); |
| return; |
| } |
| |
| samples_in_encoder_ += audio_bus->frames(); |
| |
| audio_encoder_->InsertAudio(std::move(audio_bus), recorded_time); |
| } |
| |
| void AudioSender::SetTargetPlayoutDelay( |
| base::TimeDelta new_target_playout_delay) { |
| frame_sender_->SetTargetPlayoutDelay(new_target_playout_delay); |
| } |
| |
| base::TimeDelta AudioSender::GetTargetPlayoutDelay() const { |
| return frame_sender_->GetTargetPlayoutDelay(); |
| } |
| |
| int AudioSender::GetEncoderBitrate() const { |
| return audio_encoder_->GetBitrate(); |
| } |
| |
| base::WeakPtr<AudioSender> AudioSender::AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| int AudioSender::GetNumberOfFramesInEncoder() const { |
| // Note: It's possible for a partial frame to be in the encoder, but returning |
| // the floor() is good enough for the "design limit" check in FrameSenderImpl. |
| return samples_in_encoder_ / audio_encoder_->GetSamplesPerFrame(); |
| } |
| |
| base::TimeDelta AudioSender::GetEncoderBacklogDuration() const { |
| return ToTimeDelta(RtpTimeDelta::FromTicks(samples_in_encoder_), |
| rtp_timebase_); |
| } |
| |
| void AudioSender::OnEncodedAudioFrame( |
| std::unique_ptr<SenderEncodedFrame> encoded_frame, |
| int samples_skipped) { |
| DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| |
| samples_in_encoder_ -= audio_encoder_->GetSamplesPerFrame() + samples_skipped; |
| DCHECK_GE(samples_in_encoder_, 0); |
| |
| const RtpTimeTicks rtp_timestamp = encoded_frame->rtp_timestamp; |
| const CastStreamingFrameDropReason reason = |
| frame_sender_->EnqueueFrame(std::move(encoded_frame)); |
| if (reason != CastStreamingFrameDropReason::kNotDropped) { |
| number_of_frames_dropped_++; |
| base::UmaHistogramEnumeration(kHistogramFrameDropped, reason); |
| TRACE_EVENT_INSTANT2("cast.stream", "Audio Frame Drop (already encoded)", |
| TRACE_EVENT_SCOPE_THREAD, "rtp_timestamp", |
| rtp_timestamp.lower_32_bits(), "reason", reason); |
| } |
| } |
| |
| } // namespace media::cast |