| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cast/standalone_sender/simulated_capturer.h" |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <ratio> |
| #include <sstream> |
| #include <thread> |
| |
| #include "cast/streaming/environment.h" |
| #include "util/osp_logging.h" |
| |
| namespace openscreen { |
| namespace cast { |
| |
| using openscreen::operator<<; // To pretty-print chrono values. |
| |
| namespace { |
| // Threshold at which a warning about media pausing should be logged. |
| constexpr std::chrono::seconds kPauseWarningThreshold{3}; |
| } // namespace |
| |
| SimulatedCapturer::Observer::~Observer() = default; |
| |
| SimulatedCapturer::SimulatedCapturer(Environment* environment, |
| const char* path, |
| AVMediaType media_type, |
| Clock::time_point start_time, |
| Observer* observer) |
| : format_context_(MakeUniqueAVFormatContext(path)), |
| media_type_(media_type), |
| start_time_(start_time), |
| observer_(observer), |
| packet_(MakeUniqueAVPacket()), |
| decoded_frame_(MakeUniqueAVFrame()), |
| next_task_(environment->now_function(), environment->task_runner()) { |
| OSP_DCHECK(observer_); |
| |
| if (!format_context_) { |
| OnError("MakeUniqueAVFormatContext", AVERROR_UNKNOWN); |
| return; // Capturer is halted (unable to start). |
| } |
| |
| AVCodec* codec; |
| const int stream_result = av_find_best_stream(format_context_.get(), |
| media_type_, -1, -1, &codec, 0); |
| if (stream_result < 0) { |
| OnError("av_find_best_stream", stream_result); |
| return; // Capturer is halted (unable to start). |
| } |
| |
| stream_index_ = stream_result; |
| decoder_context_ = MakeUniqueAVCodecContext(codec); |
| if (!decoder_context_) { |
| OnError("MakeUniqueAVCodecContext", AVERROR_BUG); |
| return; // Capturer is halted (unable to start). |
| } |
| // This should also be 16 or less, since the encoder implementations emit |
| // warnings about too many encode threads. FFMPEG's VP8 implementation |
| // actually silently freezes if this is 10 or more. Thus, 8 is used for the |
| // max here, just to be safe. |
| decoder_context_->thread_count = |
| std::min(std::max<int>(std::thread::hardware_concurrency(), 1), 8); |
| const int params_result = avcodec_parameters_to_context( |
| decoder_context_.get(), |
| format_context_->streams[stream_index_]->codecpar); |
| if (params_result < 0) { |
| OnError("avcodec_parameters_to_context", params_result); |
| return; // Capturer is halted (unable to start). |
| } |
| SetAdditionalDecoderParameters(decoder_context_.get()); |
| |
| const int open_result = avcodec_open2(decoder_context_.get(), codec, nullptr); |
| if (open_result < 0) { |
| OnError("avcodec_open2", open_result); |
| return; // Capturer is halted (unable to start). |
| } |
| |
| next_task_.Schedule([this] { StartDecodingNextFrame(); }, |
| Alarm::kImmediately); |
| } |
| |
| SimulatedCapturer::~SimulatedCapturer() = default; |
| |
| void SimulatedCapturer::SetAdditionalDecoderParameters( |
| AVCodecContext* decoder_context) {} |
| |
| absl::optional<Clock::duration> SimulatedCapturer::ProcessDecodedFrame( |
| const AVFrame& frame) { |
| return Clock::duration::zero(); |
| } |
| |
| void SimulatedCapturer::OnError(const char* function_name, int av_errnum) { |
| // Make a human-readable string from the libavcodec error. |
| std::ostringstream error; |
| error << "For " << av_get_media_type_string(media_type_) << ", " |
| << function_name << " returned error: " << av_err2str(av_errnum); |
| |
| // Deliver the error notification in a separate task since this method might |
| // have been called from the constructor. |
| next_task_.Schedule( |
| [this, error_string = error.str()] { |
| observer_->OnError(this, error_string); |
| // Capturer is now halted. |
| }, |
| Alarm::kImmediately); |
| } |
| |
| // static |
| Clock::duration SimulatedCapturer::ToApproximateClockDuration( |
| int64_t ticks, |
| const AVRational& time_base) { |
| return Clock::duration(av_rescale_q( |
| ticks, time_base, |
| AVRational{Clock::duration::period::num, Clock::duration::period::den})); |
| } |
| |
| void SimulatedCapturer::StartDecodingNextFrame() { |
| const int read_frame_result = |
| av_read_frame(format_context_.get(), packet_.get()); |
| if (read_frame_result < 0) { |
| if (read_frame_result == AVERROR_EOF) { |
| // Insert a "flush request" into the decoder's pipeline, which will |
| // signal an EOF in ConsumeNextDecodedFrame() later. |
| avcodec_send_packet(decoder_context_.get(), nullptr); |
| next_task_.Schedule([this] { ConsumeNextDecodedFrame(); }, |
| Alarm::kImmediately); |
| } else { |
| // All other error codes are fatal. |
| OnError("av_read_frame", read_frame_result); |
| // Capturer is now halted. |
| } |
| return; |
| } |
| |
| if (packet_->stream_index != stream_index_) { |
| av_packet_unref(packet_.get()); |
| next_task_.Schedule([this] { StartDecodingNextFrame(); }, |
| Alarm::kImmediately); |
| return; |
| } |
| |
| const int send_packet_result = |
| avcodec_send_packet(decoder_context_.get(), packet_.get()); |
| av_packet_unref(packet_.get()); |
| if (send_packet_result < 0) { |
| // Note: AVERROR(EAGAIN) is also treated as fatal here because |
| // avcodec_receive_frame() will be called repeatedly until its result code |
| // indicates avcodec_send_packet() must be called again. |
| OnError("avcodec_send_packet", send_packet_result); |
| return; // Capturer is now halted. |
| } |
| |
| next_task_.Schedule([this] { ConsumeNextDecodedFrame(); }, |
| Alarm::kImmediately); |
| } |
| |
| void SimulatedCapturer::ConsumeNextDecodedFrame() { |
| const int receive_frame_result = |
| avcodec_receive_frame(decoder_context_.get(), decoded_frame_.get()); |
| if (receive_frame_result < 0) { |
| switch (receive_frame_result) { |
| case AVERROR(EAGAIN): |
| // This result code, according to libavcodec documentation, means more |
| // data should be fed into the decoder (e.g., interframe dependencies). |
| next_task_.Schedule([this] { StartDecodingNextFrame(); }, |
| Alarm::kImmediately); |
| return; |
| case AVERROR_EOF: |
| observer_->OnEndOfFile(this); |
| return; // Capturer is now halted. |
| default: |
| OnError("avcodec_receive_frame", receive_frame_result); |
| return; // Capturer is now halted. |
| } |
| } |
| |
| const Clock::duration frame_timestamp = ToApproximateClockDuration( |
| decoded_frame_->best_effort_timestamp, |
| format_context_->streams[stream_index_]->time_base); |
| if (last_frame_timestamp_) { |
| const Clock::duration delta = frame_timestamp - *last_frame_timestamp_; |
| if (delta <= Clock::duration::zero()) { |
| OSP_LOG_WARN << "Dropping " << av_get_media_type_string(media_type_) |
| << " frame with illegal timestamp (delta from last frame: " |
| << delta << "). Bad media file!"; |
| av_frame_unref(decoded_frame_.get()); |
| next_task_.Schedule([this] { ConsumeNextDecodedFrame(); }, |
| Alarm::kImmediately); |
| return; |
| } else if (delta >= kPauseWarningThreshold) { |
| OSP_LOG_INFO << "For " << av_get_media_type_string(media_type_) |
| << ", encountered a media pause (" << delta |
| << ") in the file."; |
| } |
| } |
| last_frame_timestamp_ = frame_timestamp; |
| |
| Clock::time_point capture_time = start_time_ + frame_timestamp; |
| const auto delay_adjustment_or_null = ProcessDecodedFrame(*decoded_frame_); |
| if (!delay_adjustment_or_null) { |
| av_frame_unref(decoded_frame_.get()); |
| return; // Stop. Fatal error occurred. |
| } |
| capture_time += *delay_adjustment_or_null; |
| |
| next_task_.Schedule( |
| [this, capture_time] { |
| DeliverDataToClient(*decoded_frame_, capture_time); |
| av_frame_unref(decoded_frame_.get()); |
| ConsumeNextDecodedFrame(); |
| }, |
| capture_time); |
| } |
| |
| SimulatedAudioCapturer::Client::~Client() = default; |
| |
| SimulatedAudioCapturer::SimulatedAudioCapturer(Environment* environment, |
| const char* path, |
| int num_channels, |
| int sample_rate, |
| Clock::time_point start_time, |
| Client* client) |
| : SimulatedCapturer(environment, |
| path, |
| AVMEDIA_TYPE_AUDIO, |
| start_time, |
| client), |
| num_channels_(num_channels), |
| sample_rate_(sample_rate), |
| client_(client), |
| resampler_(MakeUniqueSwrContext()) { |
| OSP_DCHECK_GT(num_channels_, 0); |
| OSP_DCHECK_GT(sample_rate_, 0); |
| } |
| |
| SimulatedAudioCapturer::~SimulatedAudioCapturer() { |
| if (swr_is_initialized(resampler_.get())) { |
| swr_close(resampler_.get()); |
| } |
| } |
| |
| bool SimulatedAudioCapturer::EnsureResamplerIsInitializedFor( |
| const AVFrame& frame) { |
| if (swr_is_initialized(resampler_.get())) { |
| if (input_sample_format_ == static_cast<AVSampleFormat>(frame.format) && |
| input_sample_rate_ == frame.sample_rate && |
| input_channel_layout_ == frame.channel_layout) { |
| return true; |
| } |
| |
| // Note: Usually, the resampler should be flushed before being destroyed. |
| // However, because of the way SimulatedAudioCapturer uses the API, only one |
| // audio sample should be dropped in the worst case. Log what's being |
| // dropped, just in case libswresample is behaving differently than |
| // expected. |
| const std::chrono::microseconds amount( |
| swr_get_delay(resampler_.get(), std::micro::den)); |
| OSP_LOG_INFO << "Discarding " << amount |
| << " of audio from the resampler before re-init."; |
| } |
| |
| input_sample_format_ = AV_SAMPLE_FMT_NONE; |
| |
| // Create a fake output frame to hold the output audio parameters, because the |
| // resampler API is weird that way. |
| const auto fake_output_frame = MakeUniqueAVFrame(); |
| fake_output_frame->channel_layout = |
| av_get_default_channel_layout(num_channels_); |
| fake_output_frame->format = AV_SAMPLE_FMT_FLT; |
| fake_output_frame->sample_rate = sample_rate_; |
| const int config_result = |
| swr_config_frame(resampler_.get(), fake_output_frame.get(), &frame); |
| if (config_result < 0) { |
| OnError("swr_config_frame", config_result); |
| return false; // Capturer is now halted. |
| } |
| |
| const int init_result = swr_init(resampler_.get()); |
| if (init_result < 0) { |
| OnError("swr_init", init_result); |
| return false; // Capturer is now halted. |
| } |
| |
| input_sample_format_ = static_cast<AVSampleFormat>(frame.format); |
| input_sample_rate_ = frame.sample_rate; |
| input_channel_layout_ = frame.channel_layout; |
| return true; |
| } |
| |
| absl::optional<Clock::duration> SimulatedAudioCapturer::ProcessDecodedFrame( |
| const AVFrame& frame) { |
| if (!EnsureResamplerIsInitializedFor(frame)) { |
| return absl::nullopt; |
| } |
| |
| const int64_t num_leftover_input_samples = |
| swr_get_delay(resampler_.get(), input_sample_rate_); |
| OSP_DCHECK_GE(num_leftover_input_samples, 0); |
| const Clock::duration capture_time_adjustment = -ToApproximateClockDuration( |
| num_leftover_input_samples, AVRational{1, input_sample_rate_}); |
| |
| const int64_t num_output_samples_desired = |
| av_rescale_rnd(num_leftover_input_samples + frame.nb_samples, |
| sample_rate_, input_sample_rate_, AV_ROUND_ZERO); |
| OSP_DCHECK_GE(num_output_samples_desired, 0); |
| resampled_audio_.resize(num_channels_ * num_output_samples_desired); |
| uint8_t* output_argument[1] = { |
| reinterpret_cast<uint8_t*>(resampled_audio_.data())}; |
| const int num_samples_converted_or_error = swr_convert( |
| resampler_.get(), output_argument, num_output_samples_desired, |
| const_cast<const uint8_t**>(frame.extended_data), frame.nb_samples); |
| if (num_samples_converted_or_error < 0) { |
| resampled_audio_.clear(); |
| swr_close(resampler_.get()); |
| OnError("swr_convert", num_samples_converted_or_error); |
| return absl::nullopt; // Capturer is now halted. |
| } |
| resampled_audio_.resize(num_channels_ * num_samples_converted_or_error); |
| |
| return capture_time_adjustment; |
| } |
| |
| void SimulatedAudioCapturer::DeliverDataToClient( |
| const AVFrame& unused, |
| Clock::time_point capture_time) { |
| if (resampled_audio_.empty()) { |
| return; |
| } |
| client_->OnAudioData(resampled_audio_.data(), |
| resampled_audio_.size() / num_channels_, capture_time); |
| resampled_audio_.clear(); |
| } |
| |
| SimulatedVideoCapturer::Client::~Client() = default; |
| |
| SimulatedVideoCapturer::SimulatedVideoCapturer(Environment* environment, |
| const char* path, |
| Clock::time_point start_time, |
| Client* client) |
| : SimulatedCapturer(environment, |
| path, |
| AVMEDIA_TYPE_VIDEO, |
| start_time, |
| client), |
| client_(client) {} |
| |
| SimulatedVideoCapturer::~SimulatedVideoCapturer() = default; |
| |
| void SimulatedVideoCapturer::SetAdditionalDecoderParameters( |
| AVCodecContext* decoder_context) { |
| // Require the I420 planar format for video. |
| decoder_context->get_format = [](struct AVCodecContext* s, |
| const enum AVPixelFormat* formats) { |
| // Return AV_PIX_FMT_YUV420P if it's in the provided list of supported |
| // formats. Otherwise, return AV_PIX_FMT_NONE. |
| // |
| // |formats| is a NONE-terminated array. |
| for (; *formats != AV_PIX_FMT_NONE; ++formats) { |
| if (*formats == AV_PIX_FMT_YUV420P) { |
| break; |
| } |
| } |
| return *formats; |
| }; |
| } |
| |
| void SimulatedVideoCapturer::DeliverDataToClient( |
| const AVFrame& frame, |
| Clock::time_point capture_time) { |
| client_->OnVideoFrame(frame, capture_time); |
| } |
| |
| } // namespace cast |
| } // namespace openscreen |