1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/renderer/media/cast_session_delegate.h"
7 #include "base/callback_helpers.h"
8 #include "base/lazy_instance.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/strings/stringprintf.h"
12 #include "chrome/common/chrome_version_info.h"
13 #include "chrome/renderer/media/cast_threads.h"
14 #include "chrome/renderer/media/cast_transport_sender_ipc.h"
15 #include "content/public/renderer/render_thread.h"
16 #include "media/cast/cast_config.h"
17 #include "media/cast/cast_environment.h"
18 #include "media/cast/cast_sender.h"
19 #include "media/cast/logging/log_serializer.h"
20 #include "media/cast/logging/logging_defines.h"
21 #include "media/cast/logging/proto/raw_events.pb.h"
22 #include "media/cast/logging/raw_event_subscriber_bundle.h"
23 #include "media/cast/net/cast_transport_config.h"
24 #include "media/cast/net/cast_transport_sender.h"
26 using media::cast::AudioSenderConfig
;
27 using media::cast::CastEnvironment
;
28 using media::cast::CastSender
;
29 using media::cast::VideoSenderConfig
;
31 static base::LazyInstance
<CastThreads
> g_cast_threads
=
32 LAZY_INSTANCE_INITIALIZER
;
34 CastSessionDelegate::CastSessionDelegate()
35 : io_message_loop_proxy_(
36 content::RenderThread::Get()->GetIOMessageLoopProxy()),
38 DCHECK(io_message_loop_proxy_
.get());
41 CastSessionDelegate::~CastSessionDelegate() {
42 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
45 void CastSessionDelegate::StartAudio(
46 const AudioSenderConfig
& config
,
47 const AudioFrameInputAvailableCallback
& callback
,
48 const ErrorCallback
& error_callback
) {
49 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
51 if (!cast_transport_
|| !cast_sender_
) {
52 error_callback
.Run("Destination not set.");
56 audio_frame_input_available_callback_
= callback
;
57 cast_sender_
->InitializeAudio(
59 base::Bind(&CastSessionDelegate::OnOperationalStatusChange
,
60 weak_factory_
.GetWeakPtr(), true, error_callback
));
63 void CastSessionDelegate::StartVideo(
64 const VideoSenderConfig
& config
,
65 const VideoFrameInputAvailableCallback
& callback
,
66 const ErrorCallback
& error_callback
,
67 const media::cast::CreateVideoEncodeAcceleratorCallback
& create_vea_cb
,
68 const media::cast::CreateVideoEncodeMemoryCallback
&
69 create_video_encode_mem_cb
) {
70 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
72 if (!cast_transport_
|| !cast_sender_
) {
73 error_callback
.Run("Destination not set.");
77 video_frame_input_available_callback_
= callback
;
79 cast_sender_
->InitializeVideo(
81 base::Bind(&CastSessionDelegate::OnOperationalStatusChange
,
82 weak_factory_
.GetWeakPtr(), false, error_callback
),
84 create_video_encode_mem_cb
);
87 void CastSessionDelegate::StartUDP(const net::IPEndPoint
& remote_endpoint
,
88 scoped_ptr
<base::DictionaryValue
> options
) {
89 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
91 // CastSender uses the renderer's IO thread as the main thread. This reduces
92 // thread hopping for incoming video frames and outgoing network packets.
93 cast_environment_
= new CastEnvironment(
94 scoped_ptr
<base::TickClock
>(new base::DefaultTickClock()).Pass(),
95 base::MessageLoopProxy::current(),
96 g_cast_threads
.Get().GetAudioEncodeMessageLoopProxy(),
97 g_cast_threads
.Get().GetVideoEncodeMessageLoopProxy());
99 event_subscribers_
.reset(
100 new media::cast::RawEventSubscriberBundle(cast_environment_
));
102 // Rationale for using unretained: The callback cannot be called after the
103 // destruction of CastTransportSenderIPC, and they both share the same thread.
104 cast_transport_
.reset(new CastTransportSenderIPC(
108 media::cast::PacketReceiverCallback(),
109 base::Bind(&CastSessionDelegate::StatusNotificationCB
,
110 base::Unretained(this)),
111 base::Bind(&CastSessionDelegate::LogRawEvents
, base::Unretained(this))));
113 cast_sender_
= CastSender::Create(cast_environment_
, cast_transport_
.get());
116 void CastSessionDelegate::ToggleLogging(bool is_audio
, bool enable
) {
117 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
118 if (!event_subscribers_
.get())
122 event_subscribers_
->AddEventSubscribers(is_audio
);
124 event_subscribers_
->RemoveEventSubscribers(is_audio
);
127 void CastSessionDelegate::GetEventLogsAndReset(
129 const std::string
& extra_data
,
130 const EventLogsCallback
& callback
) {
131 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
133 if (!event_subscribers_
.get()) {
134 callback
.Run(make_scoped_ptr(new base::BinaryValue
).Pass());
138 media::cast::EncodingEventSubscriber
* subscriber
=
139 event_subscribers_
->GetEncodingEventSubscriber(is_audio
);
141 callback
.Run(make_scoped_ptr(new base::BinaryValue
).Pass());
145 media::cast::proto::LogMetadata metadata
;
146 media::cast::FrameEventList frame_events
;
147 media::cast::PacketEventList packet_events
;
149 subscriber
->GetEventsAndReset(&metadata
, &frame_events
, &packet_events
);
151 if (!extra_data
.empty())
152 metadata
.set_extra_data(extra_data
);
153 media::cast::proto::GeneralDescription
* gen_desc
=
154 metadata
.mutable_general_description();
155 chrome::VersionInfo version_info
;
156 gen_desc
->set_product(version_info
.Name());
157 gen_desc
->set_product_version(version_info
.Version());
158 gen_desc
->set_os(version_info
.OSType());
160 scoped_ptr
<char[]> serialized_log(new char[media::cast::kMaxSerializedBytes
]);
162 bool success
= media::cast::SerializeEvents(metadata
,
166 media::cast::kMaxSerializedBytes
,
167 serialized_log
.get(),
171 DVLOG(2) << "Failed to serialize event log.";
172 callback
.Run(make_scoped_ptr(new base::BinaryValue
).Pass());
176 DVLOG(2) << "Serialized log length: " << output_bytes
;
178 scoped_ptr
<base::BinaryValue
> blob(
179 new base::BinaryValue(serialized_log
.Pass(), output_bytes
));
180 callback
.Run(blob
.Pass());
183 void CastSessionDelegate::GetStatsAndReset(bool is_audio
,
184 const StatsCallback
& callback
) {
185 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
187 if (!event_subscribers_
.get()) {
188 callback
.Run(make_scoped_ptr(new base::DictionaryValue
).Pass());
192 media::cast::StatsEventSubscriber
* subscriber
=
193 event_subscribers_
->GetStatsEventSubscriber(is_audio
);
195 callback
.Run(make_scoped_ptr(new base::DictionaryValue
).Pass());
199 scoped_ptr
<base::DictionaryValue
> stats
= subscriber
->GetStats();
202 callback
.Run(stats
.Pass());
205 void CastSessionDelegate::StatusNotificationCB(
206 media::cast::CastTransportStatus unused_status
) {
207 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
208 // TODO(hubbe): Call javascript UDPTransport error function.
211 void CastSessionDelegate::OnOperationalStatusChange(
213 const ErrorCallback
& error_callback
,
214 media::cast::OperationalStatus status
) {
215 DCHECK(cast_sender_
);
218 case media::cast::STATUS_UNINITIALIZED
:
219 case media::cast::STATUS_CODEC_REINIT_PENDING
:
221 // TODO(miu): As an optimization, signal the client to pause sending more
222 // frames until the state becomes STATUS_INITIALIZED again.
224 case media::cast::STATUS_INITIALIZED
:
225 // Once initialized, run the "frame input available" callback to allow the
226 // client to begin sending frames. If STATUS_INITIALIZED is encountered
227 // again, do nothing since this is only an indication that the codec has
228 // successfully re-initialized.
230 if (!audio_frame_input_available_callback_
.is_null()) {
231 base::ResetAndReturn(&audio_frame_input_available_callback_
).Run(
232 cast_sender_
->audio_frame_input());
235 if (!video_frame_input_available_callback_
.is_null()) {
236 base::ResetAndReturn(&video_frame_input_available_callback_
).Run(
237 cast_sender_
->video_frame_input());
241 case media::cast::STATUS_INVALID_CONFIGURATION
:
242 error_callback
.Run(base::StringPrintf("Invalid %s configuration.",
243 is_for_audio
? "audio" : "video"));
245 case media::cast::STATUS_UNSUPPORTED_CODEC
:
246 error_callback
.Run(base::StringPrintf("%s codec not supported.",
247 is_for_audio
? "Audio" : "Video"));
249 case media::cast::STATUS_CODEC_INIT_FAILED
:
250 error_callback
.Run(base::StringPrintf("%s codec initialization failed.",
251 is_for_audio
? "Audio" : "Video"));
253 case media::cast::STATUS_CODEC_RUNTIME_ERROR
:
254 error_callback
.Run(base::StringPrintf("%s codec runtime error.",
255 is_for_audio
? "Audio" : "Video"));
260 void CastSessionDelegate::LogRawEvents(
261 const std::vector
<media::cast::PacketEvent
>& packet_events
,
262 const std::vector
<media::cast::FrameEvent
>& frame_events
) {
263 DCHECK(io_message_loop_proxy_
->BelongsToCurrentThread());
265 for (std::vector
<media::cast::PacketEvent
>::const_iterator it
=
266 packet_events
.begin();
267 it
!= packet_events
.end();
269 cast_environment_
->Logging()->InsertPacketEvent(it
->timestamp
,
278 for (std::vector
<media::cast::FrameEvent
>::const_iterator it
=
279 frame_events
.begin();
280 it
!= frame_events
.end();
282 if (it
->type
== media::cast::FRAME_PLAYOUT
) {
283 cast_environment_
->Logging()->InsertFrameEventWithDelay(
291 cast_environment_
->Logging()->InsertFrameEvent(