#include "chrome/renderer/media/cast_session_delegate.h"
+#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
-#include "content/public/renderer/p2p_socket_client.h"
+#include "chrome/renderer/media/cast_threads.h"
+#include "chrome/renderer/media/cast_transport_sender_ipc.h"
#include "content/public/renderer/render_thread.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/cast_sender.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/raw_event_subscriber_bundle.h"
#include "media/cast/transport/cast_transport_config.h"
#include "media/cast/transport/cast_transport_sender.h"
using media::cast::CastSender;
using media::cast::VideoSenderConfig;
-namespace {
-
-// This is a dummy class that does nothing. This is needed temporarily
-// to enable tests for cast.streaming extension APIs.
-// The real implementation of CastTransportSender is to use IPC to send
-// data to the browser process.
-// See crbug.com/327482 for more details.
-class DummyTransport : public media::cast::transport::CastTransportSender {
- public:
- DummyTransport() {}
- virtual ~DummyTransport() {}
-
- // CastTransportSender implementations.
- virtual void SetPacketReceiver(
- scoped_refptr<
- media::cast::transport::PacketReceiver> packet_receiver) OVERRIDE {}
- virtual void InsertCodedAudioFrame(
- const media::cast::transport::EncodedAudioFrame* audio_frame,
- const base::TimeTicks& recorded_time) OVERRIDE {}
- virtual void InsertCodedVideoFrame(
- const media::cast::transport::EncodedVideoFrame* video_frame,
- const base::TimeTicks& capture_time) OVERRIDE {}
- virtual void SendRtcpFromRtpSender(
- uint32 packet_type_flags,
- const media::cast::transport::RtcpSenderInfo& sender_info,
- const media::cast::transport::RtcpDlrrReportBlock& dlrr,
- const media::cast::transport::RtcpSenderLogMessage& sender_log,
- uint32 sending_ssrc,
- const std::string& c_name) OVERRIDE {}
- virtual void ResendPackets(
- bool is_audio,
- const media::cast::transport::MissingFramesAndPacketsMap& missing_packets)
- OVERRIDE {}
- virtual void RtpAudioStatistics(
- const base::TimeTicks& now,
- media::cast::transport::RtcpSenderInfo* sender_info) OVERRIDE {}
- virtual void RtpVideoStatistics(
- const base::TimeTicks& now,
- media::cast::transport::RtcpSenderInfo* sender_info) OVERRIDE {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DummyTransport);
-};
-
-} // namespace
+static base::LazyInstance<CastThreads> g_cast_threads =
+ LAZY_INSTANCE_INITIALIZER;
CastSessionDelegate::CastSessionDelegate()
- : audio_encode_thread_("CastAudioEncodeThread"),
- video_encode_thread_("CastVideoEncodeThread"),
- audio_configured_(false),
- video_configured_(false),
- io_message_loop_proxy_(
- content::RenderThread::Get()->GetIOMessageLoopProxy()) {
+ : io_message_loop_proxy_(
+ content::RenderThread::Get()->GetIOMessageLoopProxy()),
+ weak_factory_(this) {
+ DCHECK(io_message_loop_proxy_);
}
CastSessionDelegate::~CastSessionDelegate() {
void CastSessionDelegate::StartAudio(
const AudioSenderConfig& config,
- const FrameInputAvailableCallback& callback) {
+ const AudioFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback) {
DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
- audio_configured_ = true;
- audio_config_ = config;
- frame_input_available_callbacks_.push_back(callback);
- StartSendingInternal();
+ if (!cast_transport_ || !cast_sender_) {
+ error_callback.Run("Destination not set.");
+ return;
+ }
+
+ audio_frame_input_available_callback_ = callback;
+ cast_sender_->InitializeAudio(
+ config,
+ base::Bind(&CastSessionDelegate::InitializationResultCB,
+ weak_factory_.GetWeakPtr()));
}
void CastSessionDelegate::StartVideo(
const VideoSenderConfig& config,
- const FrameInputAvailableCallback& callback) {
+ const VideoFrameInputAvailableCallback& callback,
+ const ErrorCallback& error_callback,
+ const media::cast::CreateVideoEncodeAcceleratorCallback& create_vea_cb,
+ const media::cast::CreateVideoEncodeMemoryCallback&
+ create_video_encode_mem_cb) {
DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
- video_configured_ = true;
- video_config_ = config;
- frame_input_available_callbacks_.push_back(callback);
- StartSendingInternal();
-}
+ if (!cast_transport_ || !cast_sender_) {
+ error_callback.Run("Destination not set.");
+ return;
+ }
-void CastSessionDelegate::StartSendingInternal() {
- DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+ video_frame_input_available_callback_ = callback;
- if (cast_environment_)
- return;
- if (!audio_configured_ || !video_configured_)
- return;
+ cast_sender_->InitializeVideo(
+ config,
+ base::Bind(&CastSessionDelegate::InitializationResultCB,
+ weak_factory_.GetWeakPtr()),
+ create_vea_cb,
+ create_video_encode_mem_cb);
+}
- cast_transport_.reset(new DummyTransport());
- audio_encode_thread_.Start();
- video_encode_thread_.Start();
+void CastSessionDelegate::StartUDP(const net::IPEndPoint& remote_endpoint) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
// CastSender uses the renderer's IO thread as the main thread. This reduces
// thread hopping for incoming video frames and outgoing network packets.
- // There's no need to decode so no thread assigned for decoding.
- // Get default logging: All disabled.
cast_environment_ = new CastEnvironment(
scoped_ptr<base::TickClock>(new base::DefaultTickClock()).Pass(),
base::MessageLoopProxy::current(),
- audio_encode_thread_.message_loop_proxy(),
- NULL,
- video_encode_thread_.message_loop_proxy(),
- NULL,
- base::MessageLoopProxy::current(),
- media::cast::GetDefaultCastSenderLoggingConfig());
-
- cast_sender_.reset(CastSender::CreateCastSender(
- cast_environment_,
- audio_config_,
- video_config_,
- NULL,
- cast_transport_.get()));
-
- for (size_t i = 0; i < frame_input_available_callbacks_.size(); ++i) {
- frame_input_available_callbacks_[i].Run(
- cast_sender_->frame_input());
+ g_cast_threads.Get().GetAudioEncodeMessageLoopProxy(),
+ g_cast_threads.Get().GetVideoEncodeMessageLoopProxy());
+
+ event_subscribers_.reset(
+ new media::cast::RawEventSubscriberBundle(cast_environment_));
+
+ // Rationale for using unretained: The callback cannot be called after the
+ // destruction of CastTransportSenderIPC, and they both share the same thread.
+ cast_transport_.reset(new CastTransportSenderIPC(
+ remote_endpoint,
+ base::Bind(&CastSessionDelegate::StatusNotificationCB,
+ base::Unretained(this)),
+ base::Bind(&CastSessionDelegate::LogRawEvents, base::Unretained(this))));
+
+ cast_sender_ = CastSender::Create(cast_environment_, cast_transport_.get());
+ cast_transport_->SetPacketReceiver(cast_sender_->packet_receiver());
+}
+
+void CastSessionDelegate::ToggleLogging(bool is_audio, bool enable) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+ if (!event_subscribers_.get())
+ return;
+
+ if (enable)
+ event_subscribers_->AddEventSubscribers(is_audio);
+ else
+ event_subscribers_->RemoveEventSubscribers(is_audio);
+}
+
+void CastSessionDelegate::GetEventLogsAndReset(
+ bool is_audio,
+ const EventLogsCallback& callback) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+
+ if (!event_subscribers_.get()) {
+ callback.Run(make_scoped_ptr(new base::BinaryValue).Pass());
+ return;
+ }
+
+ media::cast::EncodingEventSubscriber* subscriber =
+ event_subscribers_->GetEncodingEventSubscriber(is_audio);
+ if (!subscriber) {
+ callback.Run(make_scoped_ptr(new base::BinaryValue).Pass());
+ return;
+ }
+
+ media::cast::proto::LogMetadata metadata;
+ media::cast::FrameEventList frame_events;
+ media::cast::PacketEventList packet_events;
+
+ subscriber->GetEventsAndReset(&metadata, &frame_events, &packet_events);
+
+ scoped_ptr<char[]> serialized_log(new char[media::cast::kMaxSerializedBytes]);
+ int output_bytes;
+ bool success = media::cast::SerializeEvents(metadata,
+ frame_events,
+ packet_events,
+ true,
+ media::cast::kMaxSerializedBytes,
+ serialized_log.get(),
+ &output_bytes);
+
+ if (!success) {
+ VLOG(2) << "Failed to serialize event log.";
+ callback.Run(make_scoped_ptr(new base::BinaryValue).Pass());
+ return;
+ }
+
+ DVLOG(2) << "Serialized log length: " << output_bytes;
+
+ scoped_ptr<base::BinaryValue> blob(
+ new base::BinaryValue(serialized_log.Pass(), output_bytes));
+ callback.Run(blob.Pass());
+}
+
+void CastSessionDelegate::GetStatsAndReset(bool is_audio,
+ const StatsCallback& callback) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+
+ if (!event_subscribers_.get()) {
+ callback.Run(make_scoped_ptr(new base::DictionaryValue).Pass());
+ return;
+ }
+
+ media::cast::StatsEventSubscriber* subscriber =
+ event_subscribers_->GetStatsEventSubscriber(is_audio);
+ if (!subscriber) {
+ callback.Run(make_scoped_ptr(new base::DictionaryValue).Pass());
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> stats = subscriber->GetStats();
+ subscriber->Reset();
+
+ callback.Run(stats.Pass());
+}
+
+void CastSessionDelegate::StatusNotificationCB(
+ media::cast::transport::CastTransportStatus unused_status) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+ // TODO(hubbe): Call javascript UDPTransport error function.
+}
+
+void CastSessionDelegate::InitializationResultCB(
+ media::cast::CastInitializationStatus result) const {
+ DCHECK(cast_sender_);
+
+ // TODO(pwestin): handle the error codes.
+ if (result == media::cast::STATUS_AUDIO_INITIALIZED) {
+ audio_frame_input_available_callback_.Run(
+ cast_sender_->audio_frame_input());
+ } else if (result == media::cast::STATUS_VIDEO_INITIALIZED) {
+ video_frame_input_available_callback_.Run(
+ cast_sender_->video_frame_input());
+ }
+}
+
+void CastSessionDelegate::LogRawEvents(
+ const std::vector<media::cast::PacketEvent>& packet_events) {
+ DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
+
+ 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->rtp_timestamp,
+ it->frame_id,
+ it->packet_id,
+ it->max_packet_id,
+ it->size);
}
- frame_input_available_callbacks_.clear();
}