// Copyright 2013 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. // Test application that simulates a cast sender - Data can be either generated // or read from a file. #include <queue> #include "base/at_exit.h" #include "base/base_paths.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/files/memory_mapped_file.h" #include "base/files/scoped_file.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/threading/thread.h" #include "base/time/default_tick_clock.h" #include "base/values.h" #include "media/audio/audio_parameters.h" #include "media/base/audio_buffer.h" #include "media/base/audio_bus.h" #include "media/base/audio_fifo.h" #include "media/base/audio_timestamp_helper.h" #include "media/base/media.h" #include "media/base/multi_channel_resampler.h" #include "media/base/video_frame.h" #include "media/base/video_util.h" #include "media/cast/cast_config.h" #include "media/cast/cast_environment.h" #include "media/cast/cast_sender.h" #include "media/cast/logging/encoding_event_subscriber.h" #include "media/cast/logging/log_serializer.h" #include "media/cast/logging/logging_defines.h" #include "media/cast/logging/proto/raw_events.pb.h" #include "media/cast/logging/receiver_time_offset_estimator_impl.h" #include "media/cast/logging/stats_event_subscriber.h" #include "media/cast/test/utility/audio_utility.h" #include "media/cast/test/utility/default_config.h" #include "media/cast/test/utility/input_builder.h" #include "media/cast/test/utility/video_utility.h" #include "media/cast/transport/cast_transport_defines.h" #include "media/cast/transport/cast_transport_sender.h" #include "media/cast/transport/transport/udp_transport.h" #include "media/ffmpeg/ffmpeg_common.h" #include "media/ffmpeg/ffmpeg_deleters.h" #include "media/filters/audio_renderer_algorithm.h" #include "media/filters/ffmpeg_demuxer.h" #include "media/filters/ffmpeg_glue.h" #include "media/filters/in_memory_url_protocol.h" #include "ui/gfx/size.h" namespace { static const int kAudioChannels = 2; static const int kAudioSamplingFrequency = 48000; static const int kSoundFrequency = 1234; // Frequency of sinusoid wave. static const float kSoundVolume = 0.5f; static const int kAudioFrameMs = 10; // Each audio frame is exactly 10ms. static const int kAudioPacketsPerSecond = 1000 / kAudioFrameMs; // The max allowed size of serialized log. const int kMaxSerializedLogBytes = 10 * 1000 * 1000; // Flags for this program: // // --address=xx.xx.xx.xx // IP address of receiver. // // --port=xxxx // Port number of receiver. // // --source-file=xxx.webm // WebM file as source of video frames. // // --fps=xx // Override framerate of the video stream. const char kSwitchAddress[] = "address"; const char kSwitchPort[] = "port"; const char kSwitchSourceFile[] = "source-file"; const char kSwitchFps[] = "fps"; } // namespace namespace media { namespace cast { AudioSenderConfig GetAudioSenderConfig() { AudioSenderConfig audio_config; audio_config.rtcp_c_name = "audio_sender@a.b.c.d"; audio_config.use_external_encoder = false; audio_config.frequency = kAudioSamplingFrequency; audio_config.channels = kAudioChannels; audio_config.bitrate = 64000; audio_config.codec = transport::kOpus; audio_config.rtp_config.ssrc = 1; audio_config.incoming_feedback_ssrc = 2; audio_config.rtp_config.payload_type = 127; audio_config.rtp_config.max_delay_ms = 300; return audio_config; } VideoSenderConfig GetVideoSenderConfig() { VideoSenderConfig video_config; video_config.rtcp_c_name = "video_sender@a.b.c.d"; video_config.use_external_encoder = false; // Resolution. video_config.width = 1280; video_config.height = 720; video_config.max_frame_rate = 30; // Bitrates. video_config.max_bitrate = 2500000; video_config.min_bitrate = 100000; video_config.start_bitrate = video_config.min_bitrate; // Codec. video_config.codec = transport::kVp8; video_config.max_number_of_video_buffers_used = 1; video_config.number_of_encode_threads = 2; // Quality options. video_config.min_qp = 4; video_config.max_qp = 40; // SSRCs and payload type. Don't change them. video_config.rtp_config.ssrc = 11; video_config.incoming_feedback_ssrc = 12; video_config.rtp_config.payload_type = 96; video_config.rtp_config.max_delay_ms = 300; return video_config; } void AVFreeFrame(AVFrame* frame) { av_frame_free(&frame); } class SendProcess { public: SendProcess(scoped_refptr<base::SingleThreadTaskRunner> thread_proxy, base::TickClock* clock, const VideoSenderConfig& video_config) : test_app_thread_proxy_(thread_proxy), video_config_(video_config), synthetic_count_(0), clock_(clock), audio_frame_count_(0), video_frame_count_(0), weak_factory_(this), av_format_context_(NULL), audio_stream_index_(-1), playback_rate_(1.0), video_stream_index_(-1), video_frame_rate_numerator_(video_config.max_frame_rate), video_frame_rate_denominator_(1), video_first_pts_(0), video_first_pts_set_(false) { audio_bus_factory_.reset(new TestAudioBusFactory(kAudioChannels, kAudioSamplingFrequency, kSoundFrequency, kSoundVolume)); const CommandLine* cmd = CommandLine::ForCurrentProcess(); int override_fps = 0; if (base::StringToInt(cmd->GetSwitchValueASCII(kSwitchFps), &override_fps)) { video_config_.max_frame_rate = override_fps; video_frame_rate_numerator_ = override_fps; } // Load source file and prepare FFmpeg demuxer. base::FilePath source_path = cmd->GetSwitchValuePath(kSwitchSourceFile); if (source_path.empty()) return; LOG(INFO) << "Source: " << source_path.value(); if (!file_data_.Initialize(source_path)) { LOG(ERROR) << "Cannot load file."; return; } protocol_.reset( new InMemoryUrlProtocol(file_data_.data(), file_data_.length(), false)); glue_.reset(new FFmpegGlue(protocol_.get())); if (!glue_->OpenContext()) { LOG(ERROR) << "Cannot open file."; return; } // AVFormatContext is owned by the glue. av_format_context_ = glue_->format_context(); if (avformat_find_stream_info(av_format_context_, NULL) < 0) { LOG(ERROR) << "Cannot find stream information."; return; } // Prepare FFmpeg decoders. for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) { AVStream* av_stream = av_format_context_->streams[i]; AVCodecContext* av_codec_context = av_stream->codec; AVCodec* av_codec = avcodec_find_decoder(av_codec_context->codec_id); if (!av_codec) { LOG(ERROR) << "Cannot find decoder for the codec: " << av_codec_context->codec_id; continue; } // Number of threads for decoding. av_codec_context->thread_count = 2; av_codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; av_codec_context->request_sample_fmt = AV_SAMPLE_FMT_S16; if (avcodec_open2(av_codec_context, av_codec, NULL) < 0) { LOG(ERROR) << "Cannot open AVCodecContext for the codec: " << av_codec_context->codec_id; return; } if (av_codec->type == AVMEDIA_TYPE_AUDIO) { if (av_codec_context->sample_fmt == AV_SAMPLE_FMT_S16P) { LOG(ERROR) << "Audio format not supported."; continue; } ChannelLayout layout = ChannelLayoutToChromeChannelLayout( av_codec_context->channel_layout, av_codec_context->channels); if (layout == CHANNEL_LAYOUT_UNSUPPORTED) { LOG(ERROR) << "Unsupported audio channels layout."; continue; } if (audio_stream_index_ != -1) { LOG(WARNING) << "Found multiple audio streams."; } audio_stream_index_ = static_cast<int>(i); audio_params_.Reset( AudioParameters::AUDIO_PCM_LINEAR, layout, av_codec_context->channels, av_codec_context->channels, av_codec_context->sample_rate, 8 * av_get_bytes_per_sample(av_codec_context->sample_fmt), av_codec_context->sample_rate / kAudioPacketsPerSecond); LOG(INFO) << "Source file has audio."; } else if (av_codec->type == AVMEDIA_TYPE_VIDEO) { VideoFrame::Format format = PixelFormatToVideoFormat(av_codec_context->pix_fmt); if (format != VideoFrame::YV12) { LOG(ERROR) << "Cannot handle non YV12 video format: " << format; continue; } if (video_stream_index_ != -1) { LOG(WARNING) << "Found multiple video streams."; } video_stream_index_ = static_cast<int>(i); if (!override_fps) { video_frame_rate_numerator_ = av_stream->r_frame_rate.num; video_frame_rate_denominator_ = av_stream->r_frame_rate.den; // Max frame rate is rounded up. video_config_.max_frame_rate = video_frame_rate_denominator_ + video_frame_rate_numerator_ - 1; video_config_.max_frame_rate /= video_frame_rate_denominator_; } else { // If video is played at a manual speed audio needs to match. playback_rate_ = 1.0 * override_fps * av_stream->r_frame_rate.den / av_stream->r_frame_rate.num; } LOG(INFO) << "Source file has video."; } else { LOG(ERROR) << "Unknown stream type; ignore."; } } Rewind(); } ~SendProcess() { } void Start(scoped_refptr<AudioFrameInput> audio_frame_input, scoped_refptr<VideoFrameInput> video_frame_input) { audio_frame_input_ = audio_frame_input; video_frame_input_ = video_frame_input; LOG(INFO) << "Max Frame rate: " << video_config_.max_frame_rate; LOG(INFO) << "Real Frame rate: " << video_frame_rate_numerator_ << "/" << video_frame_rate_denominator_ << " fps."; LOG(INFO) << "Audio playback rate: " << playback_rate_; if (!is_transcoding_audio() && !is_transcoding_video()) { // Send fake patterns. test_app_thread_proxy_->PostTask( FROM_HERE, base::Bind( &SendProcess::SendNextFakeFrame, base::Unretained(this))); return; } // Send transcoding streams. audio_algo_.Initialize(playback_rate_, audio_params_); audio_algo_.FlushBuffers(); audio_fifo_input_bus_ = AudioBus::Create( audio_params_.channels(), audio_params_.frames_per_buffer()); // Audio FIFO can carry all data fron AudioRendererAlgorithm. audio_fifo_.reset( new AudioFifo(audio_params_.channels(), audio_algo_.QueueCapacity())); audio_resampler_.reset(new media::MultiChannelResampler( audio_params_.channels(), static_cast<double>(audio_params_.sample_rate()) / kAudioSamplingFrequency, audio_params_.frames_per_buffer(), base::Bind(&SendProcess::ProvideData, base::Unretained(this)))); test_app_thread_proxy_->PostTask( FROM_HERE, base::Bind( &SendProcess::SendNextFrame, base::Unretained(this))); } void SendNextFakeFrame() { gfx::Size size(video_config_.width, video_config_.height); scoped_refptr<VideoFrame> video_frame = VideoFrame::CreateBlackFrame(size); PopulateVideoFrame(video_frame, synthetic_count_); ++synthetic_count_; base::TimeTicks now = clock_->NowTicks(); if (start_time_.is_null()) start_time_ = now; base::TimeDelta video_time = VideoFrameTime(video_frame_count_); video_frame->set_timestamp(video_time); video_frame_input_->InsertRawVideoFrame(video_frame, start_time_ + video_time); // Send just enough audio data to match next video frame's time. base::TimeDelta audio_time = AudioFrameTime(audio_frame_count_); while (audio_time < video_time) { if (is_transcoding_audio()) { Decode(true); CHECK(!audio_bus_queue_.empty()) << "No audio decoded."; scoped_ptr<AudioBus> bus(audio_bus_queue_.front()); audio_bus_queue_.pop(); audio_frame_input_->InsertAudio( bus.Pass(), start_time_ + audio_time); } else { audio_frame_input_->InsertAudio( audio_bus_factory_->NextAudioBus( base::TimeDelta::FromMilliseconds(kAudioFrameMs)), start_time_ + audio_time); } audio_time = AudioFrameTime(++audio_frame_count_); } // This is the time since the stream started. const base::TimeDelta elapsed_time = now - start_time_; // Handle the case when frame generation cannot keep up. // Move the time ahead to match the next frame. while (video_time < elapsed_time) { LOG(WARNING) << "Skipping one frame."; video_time = VideoFrameTime(++video_frame_count_); } test_app_thread_proxy_->PostDelayedTask( FROM_HERE, base::Bind(&SendProcess::SendNextFakeFrame, weak_factory_.GetWeakPtr()), video_time - elapsed_time); } // Return true if a frame was sent. bool SendNextTranscodedVideo(base::TimeDelta elapsed_time) { if (!is_transcoding_video()) return false; Decode(false); if (video_frame_queue_.empty()) return false; scoped_refptr<VideoFrame> decoded_frame = video_frame_queue_.front(); if (elapsed_time < decoded_frame->timestamp()) return false; gfx::Size size(video_config_.width, video_config_.height); scoped_refptr<VideoFrame> video_frame = VideoFrame::CreateBlackFrame(size); video_frame_queue_.pop(); media::CopyPlane(VideoFrame::kYPlane, decoded_frame->data(VideoFrame::kYPlane), decoded_frame->stride(VideoFrame::kYPlane), decoded_frame->rows(VideoFrame::kYPlane), video_frame); media::CopyPlane(VideoFrame::kUPlane, decoded_frame->data(VideoFrame::kUPlane), decoded_frame->stride(VideoFrame::kUPlane), decoded_frame->rows(VideoFrame::kUPlane), video_frame); media::CopyPlane(VideoFrame::kVPlane, decoded_frame->data(VideoFrame::kVPlane), decoded_frame->stride(VideoFrame::kVPlane), decoded_frame->rows(VideoFrame::kVPlane), video_frame); base::TimeDelta video_time; // Use the timestamp from the file if we're transcoding. video_time = ScaleTimestamp(decoded_frame->timestamp()); video_frame_input_->InsertRawVideoFrame( video_frame, start_time_ + video_time); // Make sure queue is not empty. Decode(false); return true; } // Return true if a frame was sent. bool SendNextTranscodedAudio(base::TimeDelta elapsed_time) { if (!is_transcoding_audio()) return false; Decode(true); if (audio_bus_queue_.empty()) return false; base::TimeDelta audio_time = audio_sent_ts_->GetTimestamp(); if (elapsed_time < audio_time) return false; scoped_ptr<AudioBus> bus(audio_bus_queue_.front()); audio_bus_queue_.pop(); audio_sent_ts_->AddFrames(bus->frames()); audio_frame_input_->InsertAudio( bus.Pass(), start_time_ + audio_time); // Make sure queue is not empty. Decode(true); return true; } void SendNextFrame() { if (start_time_.is_null()) start_time_ = clock_->NowTicks(); if (start_time_.is_null()) start_time_ = clock_->NowTicks(); // Send as much as possible. Audio is sent according to // system time. while (SendNextTranscodedAudio(clock_->NowTicks() - start_time_)); // Video is sync'ed to audio. while (SendNextTranscodedVideo(audio_sent_ts_->GetTimestamp())); if (audio_bus_queue_.empty() && video_frame_queue_.empty()) { // Both queues are empty can only mean that we have reached // the end of the stream. LOG(INFO) << "Rewind."; Rewind(); start_time_ = base::TimeTicks(); audio_sent_ts_.reset(); video_first_pts_set_ = false; } // Send next send. test_app_thread_proxy_->PostDelayedTask( FROM_HERE, base::Bind( &SendProcess::SendNextFrame, base::Unretained(this)), base::TimeDelta::FromMilliseconds(kAudioFrameMs)); } const VideoSenderConfig& get_video_config() const { return video_config_; } private: bool is_transcoding_audio() { return audio_stream_index_ >= 0; } bool is_transcoding_video() { return video_stream_index_ >= 0; } // Helper methods to compute timestamps for the frame number specified. base::TimeDelta VideoFrameTime(int frame_number) { return frame_number * base::TimeDelta::FromSeconds(1) * video_frame_rate_denominator_ / video_frame_rate_numerator_; } base::TimeDelta ScaleTimestamp(base::TimeDelta timestamp) { return base::TimeDelta::FromMicroseconds( timestamp.InMicroseconds() / playback_rate_); } base::TimeDelta AudioFrameTime(int frame_number) { return frame_number * base::TimeDelta::FromMilliseconds(kAudioFrameMs); } // Go to the beginning of the stream. void Rewind() { CHECK(av_seek_frame(av_format_context_, -1, 0, AVSEEK_FLAG_BACKWARD) >= 0) << "Failed to rewind to the beginning."; } // Call FFmpeg to fetch one packet. ScopedAVPacket DemuxOnePacket(bool* audio) { ScopedAVPacket packet(new AVPacket()); if (av_read_frame(av_format_context_, packet.get()) < 0) { LOG(ERROR) << "Failed to read one AVPacket."; packet.reset(); return packet.Pass(); } int stream_index = static_cast<int>(packet->stream_index); if (stream_index == audio_stream_index_) { *audio = true; } else if (stream_index == video_stream_index_) { *audio = false; } else { // Ignore unknown packet. LOG(INFO) << "Unknown packet."; packet.reset(); } return packet.Pass(); } void DecodeAudio(ScopedAVPacket packet) { // Audio. AVFrame* avframe = av_frame_alloc(); // Make a shallow copy of packet so we can slide packet.data as frames are // decoded from the packet; otherwise av_free_packet() will corrupt memory. AVPacket packet_temp = *packet.get(); do { int frame_decoded = 0; int result = avcodec_decode_audio4( av_audio_context(), avframe, &frame_decoded, &packet_temp); CHECK(result >= 0) << "Failed to decode audio."; packet_temp.size -= result; packet_temp.data += result; if (!frame_decoded) continue; int frames_read = avframe->nb_samples; if (frames_read < 0) break; if (!audio_sent_ts_) { // Initialize the base time to the first packet in the file. // This is set to the frequency we send to the receiver. // Not the frequency of the source file. This is because we // increment the frame count by samples we sent. audio_sent_ts_.reset( new AudioTimestampHelper(kAudioSamplingFrequency)); // For some files this is an invalid value. base::TimeDelta base_ts; audio_sent_ts_->SetBaseTimestamp(base_ts); } scoped_refptr<AudioBuffer> buffer = AudioBuffer::CopyFrom( AVSampleFormatToSampleFormat( av_audio_context()->sample_fmt), ChannelLayoutToChromeChannelLayout( av_audio_context()->channel_layout, av_audio_context()->channels), av_audio_context()->channels, av_audio_context()->sample_rate, frames_read, &avframe->data[0], // Note: Not all files have correct values for pkt_pts. base::TimeDelta::FromMilliseconds(avframe->pkt_pts)); audio_algo_.EnqueueBuffer(buffer); av_frame_unref(avframe); } while (packet_temp.size > 0); av_frame_free(&avframe); const int frames_needed_to_scale = playback_rate_ * av_audio_context()->sample_rate / kAudioPacketsPerSecond; while (frames_needed_to_scale <= audio_algo_.frames_buffered()) { if (!audio_algo_.FillBuffer(audio_fifo_input_bus_.get(), audio_fifo_input_bus_->frames())) { // Nothing can be scaled. Decode some more. return; } // Prevent overflow of audio data in the FIFO. if (audio_fifo_input_bus_->frames() + audio_fifo_->frames() <= audio_fifo_->max_frames()) { audio_fifo_->Push(audio_fifo_input_bus_.get()); } else { LOG(WARNING) << "Audio FIFO full; dropping samples."; } // Make sure there's enough data to resample audio. if (audio_fifo_->frames() < 2 * audio_params_.sample_rate() / kAudioPacketsPerSecond) { continue; } scoped_ptr<media::AudioBus> resampled_bus( media::AudioBus::Create( audio_params_.channels(), kAudioSamplingFrequency / kAudioPacketsPerSecond)); audio_resampler_->Resample(resampled_bus->frames(), resampled_bus.get()); audio_bus_queue_.push(resampled_bus.release()); } } void DecodeVideo(ScopedAVPacket packet) { // Video. int got_picture; AVFrame* avframe = av_frame_alloc(); // Tell the decoder to reorder for us. avframe->reordered_opaque = av_video_context()->reordered_opaque = packet->pts; CHECK(avcodec_decode_video2( av_video_context(), avframe, &got_picture, packet.get()) >= 0) << "Video decode error."; if (!got_picture) { av_frame_free(&avframe); return; } gfx::Size size(av_video_context()->width, av_video_context()->height); if (!video_first_pts_set_ || avframe->reordered_opaque < video_first_pts_) { video_first_pts_set_ = true; video_first_pts_ = avframe->reordered_opaque; } int64 pts = avframe->reordered_opaque - video_first_pts_; video_frame_queue_.push( VideoFrame::WrapExternalYuvData( media::VideoFrame::YV12, size, gfx::Rect(size), size, avframe->linesize[0], avframe->linesize[1], avframe->linesize[2], avframe->data[0], avframe->data[1], avframe->data[2], base::TimeDelta::FromMilliseconds(pts), base::Bind(&AVFreeFrame, avframe))); } void Decode(bool decode_audio) { // Read the stream until one video frame can be decoded. while (true) { if (decode_audio && !audio_bus_queue_.empty()) return; if (!decode_audio && !video_frame_queue_.empty()) return; bool audio_packet = false; ScopedAVPacket packet = DemuxOnePacket(&audio_packet); if (!packet) { LOG(INFO) << "End of stream."; return; } if (audio_packet) DecodeAudio(packet.Pass()); else DecodeVideo(packet.Pass()); } } void ProvideData(int frame_delay, media::AudioBus* output_bus) { if (audio_fifo_->frames() >= output_bus->frames()) { audio_fifo_->Consume(output_bus, 0, output_bus->frames()); } else { LOG(WARNING) << "Not enough audio data for resampling."; output_bus->Zero(); } } AVStream* av_audio_stream() { return av_format_context_->streams[audio_stream_index_]; } AVStream* av_video_stream() { return av_format_context_->streams[video_stream_index_]; } AVCodecContext* av_audio_context() { return av_audio_stream()->codec; } AVCodecContext* av_video_context() { return av_video_stream()->codec; } scoped_refptr<base::SingleThreadTaskRunner> test_app_thread_proxy_; VideoSenderConfig video_config_; scoped_refptr<AudioFrameInput> audio_frame_input_; scoped_refptr<VideoFrameInput> video_frame_input_; uint8 synthetic_count_; base::TickClock* const clock_; // Not owned by this class. // Time when the stream starts. base::TimeTicks start_time_; // The following three members are used only for fake frames. int audio_frame_count_; // Each audio frame is exactly 10ms. int video_frame_count_; scoped_ptr<TestAudioBusFactory> audio_bus_factory_; // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<SendProcess> weak_factory_; base::MemoryMappedFile file_data_; scoped_ptr<InMemoryUrlProtocol> protocol_; scoped_ptr<FFmpegGlue> glue_; AVFormatContext* av_format_context_; int audio_stream_index_; AudioParameters audio_params_; double playback_rate_; int video_stream_index_; int video_frame_rate_numerator_; int video_frame_rate_denominator_; // These are used for audio resampling. scoped_ptr<media::MultiChannelResampler> audio_resampler_; scoped_ptr<media::AudioFifo> audio_fifo_; scoped_ptr<media::AudioBus> audio_fifo_input_bus_; media::AudioRendererAlgorithm audio_algo_; // Track the timestamp of audio sent to the receiver. scoped_ptr<media::AudioTimestampHelper> audio_sent_ts_; std::queue<scoped_refptr<VideoFrame> > video_frame_queue_; int64 video_first_pts_; bool video_first_pts_set_; std::queue<AudioBus*> audio_bus_queue_; DISALLOW_COPY_AND_ASSIGN(SendProcess); }; } // namespace cast } // namespace media namespace { void UpdateCastTransportStatus( media::cast::transport::CastTransportStatus status) { VLOG(1) << "Transport status: " << status; } void LogRawEvents( const scoped_refptr<media::cast::CastEnvironment>& cast_environment, const std::vector<media::cast::PacketEvent>& packet_events) { VLOG(1) << "Got packet events from transport, size: " << packet_events.size(); for (std::vector<media::cast::PacketEvent>::const_iterator it = packet_events.begin(); it != packet_events.end(); ++it) { cast_environment->Logging()->InsertPacketEvent(it->timestamp, it->type, it->media_type, it->rtp_timestamp, it->frame_id, it->packet_id, it->max_packet_id, it->size); } } void InitializationResult(media::cast::CastInitializationStatus result) { bool end_result = result == media::cast::STATUS_AUDIO_INITIALIZED || result == media::cast::STATUS_VIDEO_INITIALIZED; CHECK(end_result) << "Cast sender uninitialized"; } net::IPEndPoint CreateUDPAddress(std::string ip_str, int port) { net::IPAddressNumber ip_number; CHECK(net::ParseIPLiteralToNumber(ip_str, &ip_number)); return net::IPEndPoint(ip_number, port); } void DumpLoggingData(const media::cast::proto::LogMetadata& log_metadata, const media::cast::FrameEventList& frame_events, const media::cast::PacketEventList& packet_events, base::ScopedFILE log_file) { VLOG(0) << "Frame map size: " << frame_events.size(); VLOG(0) << "Packet map size: " << packet_events.size(); scoped_ptr<char[]> event_log(new char[kMaxSerializedLogBytes]); int event_log_bytes; if (!media::cast::SerializeEvents(log_metadata, frame_events, packet_events, true, kMaxSerializedLogBytes, event_log.get(), &event_log_bytes)) { VLOG(0) << "Failed to serialize events."; return; } VLOG(0) << "Events serialized length: " << event_log_bytes; int ret = fwrite(event_log.get(), 1, event_log_bytes, log_file.get()); if (ret != event_log_bytes) VLOG(0) << "Failed to write logs to file."; } void WriteLogsToFileAndDestroySubscribers( const scoped_refptr<media::cast::CastEnvironment>& cast_environment, scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber, scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber, base::ScopedFILE video_log_file, base::ScopedFILE audio_log_file) { cast_environment->Logging()->RemoveRawEventSubscriber( video_event_subscriber.get()); cast_environment->Logging()->RemoveRawEventSubscriber( audio_event_subscriber.get()); VLOG(0) << "Dumping logging data for video stream."; media::cast::proto::LogMetadata log_metadata; media::cast::FrameEventList frame_events; media::cast::PacketEventList packet_events; video_event_subscriber->GetEventsAndReset( &log_metadata, &frame_events, &packet_events); DumpLoggingData(log_metadata, frame_events, packet_events, video_log_file.Pass()); VLOG(0) << "Dumping logging data for audio stream."; audio_event_subscriber->GetEventsAndReset( &log_metadata, &frame_events, &packet_events); DumpLoggingData(log_metadata, frame_events, packet_events, audio_log_file.Pass()); } void WriteStatsAndDestroySubscribers( const scoped_refptr<media::cast::CastEnvironment>& cast_environment, scoped_ptr<media::cast::StatsEventSubscriber> video_event_subscriber, scoped_ptr<media::cast::StatsEventSubscriber> audio_event_subscriber, scoped_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl> estimator) { cast_environment->Logging()->RemoveRawEventSubscriber( video_event_subscriber.get()); cast_environment->Logging()->RemoveRawEventSubscriber( audio_event_subscriber.get()); cast_environment->Logging()->RemoveRawEventSubscriber(estimator.get()); scoped_ptr<base::DictionaryValue> stats = video_event_subscriber->GetStats(); std::string json; base::JSONWriter::WriteWithOptions( stats.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); VLOG(0) << "Video stats: " << json; stats = audio_event_subscriber->GetStats(); json.clear(); base::JSONWriter::WriteWithOptions( stats.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); VLOG(0) << "Audio stats: " << json; } } // namespace int main(int argc, char** argv) { base::AtExitManager at_exit; CommandLine::Init(argc, argv); InitLogging(logging::LoggingSettings()); // Load the media module for FFmpeg decoding. base::FilePath path; PathService::Get(base::DIR_MODULE, &path); if (!media::InitializeMediaLibrary(path)) { LOG(ERROR) << "Could not initialize media library."; return 1; } base::Thread test_thread("Cast sender test app thread"); base::Thread audio_thread("Cast audio encoder thread"); base::Thread video_thread("Cast video encoder thread"); test_thread.Start(); audio_thread.Start(); video_thread.Start(); base::MessageLoopForIO io_message_loop; // Default parameters. CommandLine* cmd = CommandLine::ForCurrentProcess(); std::string remote_ip_address = cmd->GetSwitchValueASCII(kSwitchAddress); if (remote_ip_address.empty()) remote_ip_address = "127.0.0.1"; int remote_port = 0; if (!base::StringToInt(cmd->GetSwitchValueASCII(kSwitchPort), &remote_port)) { remote_port = 2344; } LOG(INFO) << "Sending to " << remote_ip_address << ":" << remote_port << "."; media::cast::AudioSenderConfig audio_config = media::cast::GetAudioSenderConfig(); media::cast::VideoSenderConfig video_config = media::cast::GetVideoSenderConfig(); // Running transport on the main thread. // Setting up transport config. net::IPEndPoint remote_endpoint = CreateUDPAddress(remote_ip_address, remote_port); // Enable raw event and stats logging. // Running transport on the main thread. scoped_refptr<media::cast::CastEnvironment> cast_environment( new media::cast::CastEnvironment( make_scoped_ptr<base::TickClock>(new base::DefaultTickClock()), io_message_loop.message_loop_proxy(), audio_thread.message_loop_proxy(), video_thread.message_loop_proxy())); // SendProcess initialization. scoped_ptr<media::cast::SendProcess> send_process( new media::cast::SendProcess(test_thread.message_loop_proxy(), cast_environment->Clock(), video_config)); // CastTransportSender initialization. scoped_ptr<media::cast::transport::CastTransportSender> transport_sender = media::cast::transport::CastTransportSender::Create( NULL, // net log. cast_environment->Clock(), remote_endpoint, base::Bind(&UpdateCastTransportStatus), base::Bind(&LogRawEvents, cast_environment), base::TimeDelta::FromSeconds(1), io_message_loop.message_loop_proxy()); // CastSender initialization. scoped_ptr<media::cast::CastSender> cast_sender = media::cast::CastSender::Create(cast_environment, transport_sender.get()); cast_sender->InitializeVideo( send_process->get_video_config(), base::Bind(&InitializationResult), media::cast::CreateDefaultVideoEncodeAcceleratorCallback(), media::cast::CreateDefaultVideoEncodeMemoryCallback()); cast_sender->InitializeAudio(audio_config, base::Bind(&InitializationResult)); transport_sender->SetPacketReceiver(cast_sender->packet_receiver()); // Set up event subscribers. scoped_ptr<media::cast::EncodingEventSubscriber> video_event_subscriber; scoped_ptr<media::cast::EncodingEventSubscriber> audio_event_subscriber; std::string video_log_file_name("/tmp/video_events.log.gz"); std::string audio_log_file_name("/tmp/audio_events.log.gz"); LOG(INFO) << "Logging audio events to: " << audio_log_file_name; LOG(INFO) << "Logging video events to: " << video_log_file_name; video_event_subscriber.reset(new media::cast::EncodingEventSubscriber( media::cast::VIDEO_EVENT, 10000)); audio_event_subscriber.reset(new media::cast::EncodingEventSubscriber( media::cast::AUDIO_EVENT, 10000)); cast_environment->Logging()->AddRawEventSubscriber( video_event_subscriber.get()); cast_environment->Logging()->AddRawEventSubscriber( audio_event_subscriber.get()); // Subscribers for stats. scoped_ptr<media::cast::ReceiverTimeOffsetEstimatorImpl> offset_estimator( new media::cast::ReceiverTimeOffsetEstimatorImpl); cast_environment->Logging()->AddRawEventSubscriber(offset_estimator.get()); scoped_ptr<media::cast::StatsEventSubscriber> video_stats_subscriber( new media::cast::StatsEventSubscriber(media::cast::VIDEO_EVENT, cast_environment->Clock(), offset_estimator.get())); scoped_ptr<media::cast::StatsEventSubscriber> audio_stats_subscriber( new media::cast::StatsEventSubscriber(media::cast::AUDIO_EVENT, cast_environment->Clock(), offset_estimator.get())); cast_environment->Logging()->AddRawEventSubscriber( video_stats_subscriber.get()); cast_environment->Logging()->AddRawEventSubscriber( audio_stats_subscriber.get()); base::ScopedFILE video_log_file(fopen(video_log_file_name.c_str(), "w")); if (!video_log_file) { VLOG(1) << "Failed to open video log file for writing."; exit(-1); } base::ScopedFILE audio_log_file(fopen(audio_log_file_name.c_str(), "w")); if (!audio_log_file) { VLOG(1) << "Failed to open audio log file for writing."; exit(-1); } const int logging_duration_seconds = 10; io_message_loop.message_loop_proxy()->PostDelayedTask( FROM_HERE, base::Bind(&WriteLogsToFileAndDestroySubscribers, cast_environment, base::Passed(&video_event_subscriber), base::Passed(&audio_event_subscriber), base::Passed(&video_log_file), base::Passed(&audio_log_file)), base::TimeDelta::FromSeconds(logging_duration_seconds)); io_message_loop.message_loop_proxy()->PostDelayedTask( FROM_HERE, base::Bind(&WriteStatsAndDestroySubscribers, cast_environment, base::Passed(&video_stats_subscriber), base::Passed(&audio_stats_subscriber), base::Passed(&offset_estimator)), base::TimeDelta::FromSeconds(logging_duration_seconds)); send_process->Start(cast_sender->audio_frame_input(), cast_sender->video_frame_input()); io_message_loop.Run(); return 0; }