blob: d076d8837adb036d943aeb15f9024858f76facbb [file] [log] [blame]
// 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.
#include "chrome/renderer/extensions/cast_streaming_native_handler.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/common/extensions/api/cast_streaming_receiver_session.h"
#include "chrome/common/extensions/api/cast_streaming_rtp_stream.h"
#include "chrome/common/extensions/api/cast_streaming_udp_transport.h"
#include "chrome/renderer/media/cast_receiver_session.h"
#include "chrome/renderer/media/cast_rtp_stream.h"
#include "chrome/renderer/media/cast_session.h"
#include "chrome/renderer/media/cast_udp_transport.h"
#include "content/public/child/v8_value_converter.h"
#include "content/public/renderer/media_stream_utils.h"
#include "extensions/common/extension.h"
#include "extensions/renderer/script_context.h"
#include "media/base/audio_parameters.h"
#include "media/base/limits.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "third_party/WebKit/public/platform/WebMediaStream.h"
#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h"
#include "third_party/WebKit/public/web/WebMediaStreamRegistry.h"
#include "url/gurl.h"
using content::V8ValueConverter;
using media::cast::FrameSenderConfig;
// Extension types.
using extensions::api::cast_streaming_receiver_session::RtpReceiverParams;
using extensions::api::cast_streaming_rtp_stream::RtpParams;
using extensions::api::cast_streaming_rtp_stream::RtpPayloadParams;
using extensions::api::cast_streaming_udp_transport::IPEndPoint;
namespace extensions {
namespace {
constexpr char kInvalidAesIvMask[] = "Invalid value for AES IV mask";
constexpr char kInvalidAesKey[] = "Invalid value for AES key";
constexpr char kInvalidAudioParams[] = "Invalid audio params";
constexpr char kInvalidDestination[] = "Invalid destination";
constexpr char kInvalidFPS[] = "Invalid FPS";
constexpr char kInvalidMediaStreamURL[] = "Invalid MediaStream URL";
constexpr char kInvalidRtpParams[] = "Invalid value for RTP params";
constexpr char kInvalidLatency[] = "Invalid value for max_latency. (0-1000)";
constexpr char kInvalidRtpTimebase[] = "Invalid rtp_timebase. (1000-1000000)";
constexpr char kInvalidStreamArgs[] = "Invalid stream arguments";
constexpr char kRtpStreamNotFound[] = "The RTP stream cannot be found";
constexpr char kUdpTransportNotFound[] = "The UDP transport cannot be found";
constexpr char kUnableToConvertArgs[] = "Unable to convert arguments";
constexpr char kUnableToConvertParams[] = "Unable to convert params";
constexpr char kCodecNameOpus[] = "OPUS";
constexpr char kCodecNameVp8[] = "VP8";
constexpr char kCodecNameH264[] = "H264";
constexpr char kCodecNameRemoteAudio[] = "REMOTE_AUDIO";
constexpr char kCodecNameRemoteVideo[] = "REMOTE_VIDEO";
// To convert from kilobits per second to bits per second.
constexpr int kBitsPerKilobit = 1000;
bool HexDecode(const std::string& input, std::string* output) {
std::vector<uint8_t> bytes;
if (!base::HexStringToBytes(input, &bytes))
return false;
output->assign(reinterpret_cast<const char*>(&bytes[0]), bytes.size());
return true;
}
int NumberOfEncodeThreads() {
// Do not saturate CPU utilization just for encoding. On a lower-end system
// with only 1 or 2 cores, use only one thread for encoding. On systems with
// more cores, allow half of the cores to be used for encoding.
return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
}
bool ToFrameSenderConfigOrThrow(v8::Isolate* isolate,
const RtpPayloadParams& ext_params,
FrameSenderConfig* config) {
config->sender_ssrc = ext_params.ssrc;
config->receiver_ssrc = ext_params.feedback_ssrc;
if (config->sender_ssrc == config->receiver_ssrc) {
DVLOG(1) << "sender_ssrc " << config->sender_ssrc
<< " cannot be equal to receiver_ssrc";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
config->min_playout_delay = base::TimeDelta::FromMilliseconds(
ext_params.min_latency ? *ext_params.min_latency
: ext_params.max_latency);
config->max_playout_delay =
base::TimeDelta::FromMilliseconds(ext_params.max_latency);
config->animated_playout_delay = base::TimeDelta::FromMilliseconds(
ext_params.animated_latency ? *ext_params.animated_latency
: ext_params.max_latency);
if (config->min_playout_delay <= base::TimeDelta()) {
DVLOG(1) << "min_playout_delay " << config->min_playout_delay
<< " must be greater than zero";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
if (config->min_playout_delay > config->max_playout_delay) {
DVLOG(1) << "min_playout_delay " << config->min_playout_delay
<< " must be less than or equal to max_palyout_delay";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
if (config->animated_playout_delay < config->min_playout_delay ||
config->animated_playout_delay > config->max_playout_delay) {
DVLOG(1) << "animated_playout_delay " << config->animated_playout_delay
<< " must be between (inclusive) the min and max playout delay";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
if (ext_params.codec_name == kCodecNameOpus) {
config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_OPUS;
config->use_external_encoder = false;
config->rtp_timebase = ext_params.clock_rate
? *ext_params.clock_rate
: media::cast::kDefaultAudioSamplingRate;
// Sampling rate must be one of the Opus-supported values.
switch (config->rtp_timebase) {
case 48000:
case 24000:
case 16000:
case 12000:
case 8000:
break;
default:
DVLOG(1) << "rtp_timebase " << config->rtp_timebase << " is invalid";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
config->channels = ext_params.channels ? *ext_params.channels : 2;
if (config->channels != 1 && config->channels != 2) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
DVLOG(1) << "channels " << config->channels << " is invalid";
return false;
}
config->min_bitrate = config->start_bitrate = config->max_bitrate =
ext_params.max_bitrate ? (*ext_params.max_bitrate) * kBitsPerKilobit
: media::cast::kDefaultAudioEncoderBitrate;
config->max_frame_rate = 100; // 10ms audio frames.
config->codec = media::cast::CODEC_AUDIO_OPUS;
} else if (ext_params.codec_name == kCodecNameVp8 ||
ext_params.codec_name == kCodecNameH264) {
config->rtp_timebase = media::cast::kVideoFrequency;
config->channels = ext_params.channels ? *ext_params.channels : 1;
if (config->channels != 1) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
DVLOG(1) << "channels " << config->channels << " is invalid";
return false;
}
config->min_bitrate = ext_params.min_bitrate
? (*ext_params.min_bitrate) * kBitsPerKilobit
: media::cast::kDefaultMinVideoBitrate;
config->max_bitrate = ext_params.max_bitrate
? (*ext_params.max_bitrate) * kBitsPerKilobit
: media::cast::kDefaultMaxVideoBitrate;
if (config->min_bitrate > config->max_bitrate) {
DVLOG(1) << "min_bitrate " << config->min_bitrate << " is larger than "
<< "max_bitrate " << config->max_bitrate;
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
config->start_bitrate = config->min_bitrate;
config->max_frame_rate = std::max(
1.0, ext_params.max_frame_rate ? *ext_params.max_frame_rate : 0.0);
if (config->max_frame_rate > media::limits::kMaxFramesPerSecond) {
DVLOG(1) << "max_frame_rate " << config->max_frame_rate << " is invalid";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
if (ext_params.codec_name == kCodecNameVp8) {
config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_VP8;
config->codec = media::cast::CODEC_VIDEO_VP8;
config->use_external_encoder =
CastRtpStream::IsHardwareVP8EncodingSupported();
} else {
config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_H264;
config->codec = media::cast::CODEC_VIDEO_H264;
config->use_external_encoder =
CastRtpStream::IsHardwareH264EncodingSupported();
}
if (!config->use_external_encoder)
config->video_codec_params.number_of_encode_threads =
NumberOfEncodeThreads();
} else if (ext_params.codec_name == kCodecNameRemoteAudio) {
config->rtp_payload_type = media::cast::RtpPayloadType::REMOTE_AUDIO;
config->codec = media::cast::CODEC_AUDIO_REMOTE;
} else if (ext_params.codec_name == kCodecNameRemoteVideo) {
config->rtp_payload_type = media::cast::RtpPayloadType::REMOTE_VIDEO;
config->codec = media::cast::CODEC_VIDEO_REMOTE;
} else {
DVLOG(1) << "codec_name " << ext_params.codec_name << " is invalid";
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
if (ext_params.aes_key && !HexDecode(*ext_params.aes_key, &config->aes_key)) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidAesKey)));
return false;
}
if (ext_params.aes_iv_mask &&
!HexDecode(*ext_params.aes_iv_mask, &config->aes_iv_mask)) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidAesIvMask)));
return false;
}
return true;
}
void FromFrameSenderConfig(const FrameSenderConfig& config,
RtpPayloadParams* ext_params) {
ext_params->payload_type = static_cast<int>(config.rtp_payload_type);
ext_params->max_latency = config.max_playout_delay.InMilliseconds();
ext_params->min_latency.reset(
new int(config.min_playout_delay.InMilliseconds()));
ext_params->animated_latency.reset(
new int(config.animated_playout_delay.InMilliseconds()));
switch (config.codec) {
case media::cast::CODEC_AUDIO_OPUS:
ext_params->codec_name = kCodecNameOpus;
break;
case media::cast::CODEC_VIDEO_VP8:
ext_params->codec_name = kCodecNameVp8;
break;
case media::cast::CODEC_VIDEO_H264:
ext_params->codec_name = kCodecNameH264;
break;
case media::cast::CODEC_AUDIO_REMOTE:
ext_params->codec_name = kCodecNameRemoteAudio;
break;
case media::cast::CODEC_VIDEO_REMOTE:
ext_params->codec_name = kCodecNameRemoteVideo;
break;
default:
NOTREACHED();
}
ext_params->ssrc = config.sender_ssrc;
ext_params->feedback_ssrc = config.receiver_ssrc;
if (config.rtp_timebase)
ext_params->clock_rate.reset(new int(config.rtp_timebase));
if (config.min_bitrate)
ext_params->min_bitrate.reset(
new int(config.min_bitrate / kBitsPerKilobit));
if (config.max_bitrate)
ext_params->max_bitrate.reset(
new int(config.max_bitrate / kBitsPerKilobit));
if (config.channels)
ext_params->channels.reset(new int(config.channels));
if (config.max_frame_rate > 0.0)
ext_params->max_frame_rate.reset(new double(config.max_frame_rate));
}
} // namespace
// |last_transport_id_| is the identifier for the next created RTP stream. To
// create globally unique IDs used for referring to RTP stream objects in
// browser process, we set its higher 16 bits as HASH(extension_id)&0x7fff, and
// lower 16 bits as the sequence number of the RTP stream created in the same
// extension. Collision will happen when the first RTP stream keeps alive after
// creating another 64k-1 RTP streams in the same extension, which is very
// unlikely to happen in normal use cases.
CastStreamingNativeHandler::CastStreamingNativeHandler(ScriptContext* context)
: ObjectBackedNativeHandler(context),
last_transport_id_(
context->extension()
? (((base::Hash(context->extension()->id()) & 0x7fff) << 16) + 1)
: 1),
weak_factory_(this) {
RouteFunction("CreateSession", "cast.streaming.session",
base::Bind(&CastStreamingNativeHandler::CreateCastSession,
weak_factory_.GetWeakPtr()));
RouteFunction("DestroyCastRtpStream", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::DestroyCastRtpStream,
weak_factory_.GetWeakPtr()));
RouteFunction(
"GetSupportedParamsCastRtpStream", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::GetSupportedParamsCastRtpStream,
weak_factory_.GetWeakPtr()));
RouteFunction("StartCastRtpStream", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::StartCastRtpStream,
weak_factory_.GetWeakPtr()));
RouteFunction("StopCastRtpStream", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::StopCastRtpStream,
weak_factory_.GetWeakPtr()));
RouteFunction("DestroyCastUdpTransport", "cast.streaming.udpTransport",
base::Bind(&CastStreamingNativeHandler::DestroyCastUdpTransport,
weak_factory_.GetWeakPtr()));
RouteFunction(
"SetDestinationCastUdpTransport", "cast.streaming.udpTransport",
base::Bind(&CastStreamingNativeHandler::SetDestinationCastUdpTransport,
weak_factory_.GetWeakPtr()));
RouteFunction(
"SetOptionsCastUdpTransport", "cast.streaming.udpTransport",
base::Bind(&CastStreamingNativeHandler::SetOptionsCastUdpTransport,
weak_factory_.GetWeakPtr()));
RouteFunction("ToggleLogging", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::ToggleLogging,
weak_factory_.GetWeakPtr()));
RouteFunction("GetRawEvents", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::GetRawEvents,
weak_factory_.GetWeakPtr()));
RouteFunction("GetStats", "cast.streaming.rtpStream",
base::Bind(&CastStreamingNativeHandler::GetStats,
weak_factory_.GetWeakPtr()));
RouteFunction("StartCastRtpReceiver", "cast.streaming.receiverSession",
base::Bind(&CastStreamingNativeHandler::StartCastRtpReceiver,
weak_factory_.GetWeakPtr()));
}
CastStreamingNativeHandler::~CastStreamingNativeHandler() {
// Note: A superclass's destructor will call Invalidate(), but Invalidate()
// may also be called at any time before destruction.
}
void CastStreamingNativeHandler::Invalidate() {
// Cancel all function call routing and callbacks.
weak_factory_.InvalidateWeakPtrs();
// Clear all references to V8 and Cast objects, which will trigger
// auto-destructions (effectively stopping all sessions).
get_stats_callbacks_.clear();
get_raw_events_callbacks_.clear();
create_callback_.Reset();
udp_transport_map_.clear();
rtp_stream_map_.clear();
ObjectBackedNativeHandler::Invalidate();
}
void CastStreamingNativeHandler::CreateCastSession(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(3, args.Length());
CHECK(args[2]->IsFunction());
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
scoped_refptr<CastSession> session(new CastSession());
std::unique_ptr<CastRtpStream> stream1, stream2;
if ((args[0]->IsNull() || args[0]->IsUndefined()) &&
(args[1]->IsNull() || args[1]->IsUndefined())) {
DVLOG(3) << "CreateCastSession for remoting.";
// Creates audio/video RTP streams for media remoting.
stream1.reset(new CastRtpStream(true, session));
stream2.reset(new CastRtpStream(false, session));
} else {
// Creates RTP streams that consume from an audio and/or a video
// MediaStreamTrack.
if (!args[0]->IsNull() && !args[0]->IsUndefined()) {
CHECK(args[0]->IsObject());
blink::WebDOMMediaStreamTrack track =
blink::WebDOMMediaStreamTrack::fromV8Value(args[0]);
if (track.isNull()) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
return;
}
stream1.reset(new CastRtpStream(track.component(), session));
}
if (!args[1]->IsNull() && !args[1]->IsUndefined()) {
CHECK(args[1]->IsObject());
blink::WebDOMMediaStreamTrack track =
blink::WebDOMMediaStreamTrack::fromV8Value(args[1]);
if (track.isNull()) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidStreamArgs)));
return;
}
stream2.reset(new CastRtpStream(track.component(), session));
}
}
std::unique_ptr<CastUdpTransport> udp_transport(
new CastUdpTransport(session));
create_callback_.Reset(isolate, args[2].As<v8::Function>());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&CastStreamingNativeHandler::CallCreateCallback,
weak_factory_.GetWeakPtr(), base::Passed(&stream1),
base::Passed(&stream2), base::Passed(&udp_transport)));
}
void CastStreamingNativeHandler::CallCreateCallback(
std::unique_ptr<CastRtpStream> stream1,
std::unique_ptr<CastRtpStream> stream2,
std::unique_ptr<CastUdpTransport> udp_transport) {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
v8::Local<v8::Value> callback_args[3];
callback_args[0] = v8::Null(isolate);
callback_args[1] = v8::Null(isolate);
if (stream1) {
const int stream1_id = last_transport_id_++;
callback_args[0] = v8::Integer::New(isolate, stream1_id);
rtp_stream_map_[stream1_id] = std::move(stream1);
}
if (stream2) {
const int stream2_id = last_transport_id_++;
callback_args[1] = v8::Integer::New(isolate, stream2_id);
rtp_stream_map_[stream2_id] = std::move(stream2);
}
const int udp_id = last_transport_id_++;
udp_transport_map_[udp_id] = std::move(udp_transport);
callback_args[2] = v8::Integer::New(isolate, udp_id);
context()->SafeCallFunction(
v8::Local<v8::Function>::New(isolate, create_callback_), 3,
callback_args);
create_callback_.Reset();
}
void CastStreamingNativeHandler::CallStartCallback(int stream_id) const {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
v8::Local<v8::Array> event_args = v8::Array::New(isolate, 1);
event_args->Set(0, v8::Integer::New(isolate, stream_id));
context()->DispatchEvent("cast.streaming.rtpStream.onStarted", event_args);
}
void CastStreamingNativeHandler::CallStopCallback(int stream_id) const {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
v8::Local<v8::Array> event_args = v8::Array::New(isolate, 1);
event_args->Set(0, v8::Integer::New(isolate, stream_id));
context()->DispatchEvent("cast.streaming.rtpStream.onStopped", event_args);
}
void CastStreamingNativeHandler::CallErrorCallback(
int stream_id,
const std::string& message) const {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
v8::Local<v8::Array> event_args = v8::Array::New(isolate, 2);
event_args->Set(0, v8::Integer::New(isolate, stream_id));
event_args->Set(
1,
v8::String::NewFromUtf8(
isolate, message.data(), v8::String::kNormalString, message.size()));
context()->DispatchEvent("cast.streaming.rtpStream.onError", event_args);
}
void CastStreamingNativeHandler::DestroyCastRtpStream(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(1, args.Length());
CHECK(args[0]->IsInt32());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
if (!GetRtpStreamOrThrow(transport_id))
return;
rtp_stream_map_.erase(transport_id);
}
void CastStreamingNativeHandler::GetSupportedParamsCastRtpStream(
const v8::FunctionCallbackInfo<v8::Value>& args) const {
CHECK_EQ(1, args.Length());
CHECK(args[0]->IsInt32());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
if (!transport)
return;
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::vector<FrameSenderConfig> configs = transport->GetSupportedConfigs();
v8::Local<v8::Array> result =
v8::Array::New(args.GetIsolate(), static_cast<int>(configs.size()));
for (size_t i = 0; i < configs.size(); ++i) {
RtpParams params;
FromFrameSenderConfig(configs[i], &params.payload);
std::unique_ptr<base::DictionaryValue> params_value = params.ToValue();
result->Set(
static_cast<int>(i),
converter->ToV8Value(params_value.get(), context()->v8_context()));
}
args.GetReturnValue().Set(result);
}
void CastStreamingNativeHandler::StartCastRtpStream(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(2, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
if (!transport)
return;
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::unique_ptr<base::Value> params_value(
converter->FromV8Value(args[1], context()->v8_context()));
if (!params_value) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertParams)));
return;
}
std::unique_ptr<RtpParams> params = RtpParams::FromValue(*params_value);
if (!params) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kInvalidRtpParams)));
return;
}
FrameSenderConfig config;
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
if (!ToFrameSenderConfigOrThrow(isolate, params->payload, &config))
return;
base::Closure start_callback =
base::Bind(&CastStreamingNativeHandler::CallStartCallback,
weak_factory_.GetWeakPtr(),
transport_id);
base::Closure stop_callback =
base::Bind(&CastStreamingNativeHandler::CallStopCallback,
weak_factory_.GetWeakPtr(),
transport_id);
CastRtpStream::ErrorCallback error_callback =
base::Bind(&CastStreamingNativeHandler::CallErrorCallback,
weak_factory_.GetWeakPtr(),
transport_id);
// |transport_id| is a globally unique identifier for the RTP stream.
transport->Start(transport_id, config, start_callback, stop_callback,
error_callback);
}
void CastStreamingNativeHandler::StopCastRtpStream(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(1, args.Length());
CHECK(args[0]->IsInt32());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
if (!transport)
return;
transport->Stop();
}
void CastStreamingNativeHandler::DestroyCastUdpTransport(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(1, args.Length());
CHECK(args[0]->IsInt32());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
if (!GetUdpTransportOrThrow(transport_id))
return;
udp_transport_map_.erase(transport_id);
}
void CastStreamingNativeHandler::SetDestinationCastUdpTransport(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(2, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id);
if (!transport)
return;
net::IPEndPoint dest;
if (!IPEndPointFromArg(args.GetIsolate(),
args[1],
&dest)) {
return;
}
transport->SetDestination(
dest,
base::Bind(&CastStreamingNativeHandler::CallErrorCallback,
weak_factory_.GetWeakPtr(),
transport_id));
}
void CastStreamingNativeHandler::SetOptionsCastUdpTransport(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(2, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsObject());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id);
if (!transport)
return;
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::unique_ptr<base::DictionaryValue> options = base::DictionaryValue::From(
converter->FromV8Value(args[1], context()->v8_context()));
if (!options) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs)));
return;
}
transport->SetOptions(std::move(options));
}
void CastStreamingNativeHandler::ToggleLogging(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(2, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsBoolean());
const int stream_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastRtpStream* stream = GetRtpStreamOrThrow(stream_id);
if (!stream)
return;
const bool enable = args[1]->ToBoolean(args.GetIsolate())->Value();
stream->ToggleLogging(enable);
}
void CastStreamingNativeHandler::GetRawEvents(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(3, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsNull() || args[1]->IsString());
CHECK(args[2]->IsFunction());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
v8::Global<v8::Function> callback(args.GetIsolate(),
args[2].As<v8::Function>());
std::string extra_data;
if (!args[1]->IsNull()) {
extra_data = *v8::String::Utf8Value(args[1]);
}
CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
if (!transport)
return;
get_raw_events_callbacks_.insert(
std::make_pair(transport_id, std::move(callback)));
transport->GetRawEvents(
base::Bind(&CastStreamingNativeHandler::CallGetRawEventsCallback,
weak_factory_.GetWeakPtr(),
transport_id),
extra_data);
}
void CastStreamingNativeHandler::GetStats(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(2, args.Length());
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsFunction());
const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value();
CastRtpStream* transport = GetRtpStreamOrThrow(transport_id);
if (!transport)
return;
v8::Global<v8::Function> callback(args.GetIsolate(),
args[1].As<v8::Function>());
get_stats_callbacks_.insert(
std::make_pair(transport_id, std::move(callback)));
transport->GetStats(
base::Bind(&CastStreamingNativeHandler::CallGetStatsCallback,
weak_factory_.GetWeakPtr(),
transport_id));
}
void CastStreamingNativeHandler::CallGetRawEventsCallback(
int transport_id,
std::unique_ptr<base::BinaryValue> raw_events) {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
RtpStreamCallbackMap::iterator it =
get_raw_events_callbacks_.find(transport_id);
if (it == get_raw_events_callbacks_.end())
return;
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
v8::Local<v8::Value> callback_args[] = {
converter->ToV8Value(raw_events.get(), context()->v8_context())};
context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, it->second),
arraysize(callback_args), callback_args);
get_raw_events_callbacks_.erase(it);
}
void CastStreamingNativeHandler::CallGetStatsCallback(
int transport_id,
std::unique_ptr<base::DictionaryValue> stats) {
v8::Isolate* isolate = context()->isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context()->v8_context());
RtpStreamCallbackMap::iterator it = get_stats_callbacks_.find(transport_id);
if (it == get_stats_callbacks_.end())
return;
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
v8::Local<v8::Value> callback_args[] = {
converter->ToV8Value(stats.get(), context()->v8_context())};
context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, it->second),
arraysize(callback_args), callback_args);
get_stats_callbacks_.erase(it);
}
CastRtpStream* CastStreamingNativeHandler::GetRtpStreamOrThrow(
int transport_id) const {
RtpStreamMap::const_iterator iter = rtp_stream_map_.find(
transport_id);
if (iter != rtp_stream_map_.end())
return iter->second.get();
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
isolate->ThrowException(v8::Exception::RangeError(v8::String::NewFromUtf8(
isolate, kRtpStreamNotFound)));
return NULL;
}
CastUdpTransport* CastStreamingNativeHandler::GetUdpTransportOrThrow(
int transport_id) const {
UdpTransportMap::const_iterator iter = udp_transport_map_.find(
transport_id);
if (iter != udp_transport_map_.end())
return iter->second.get();
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
isolate->ThrowException(v8::Exception::RangeError(
v8::String::NewFromUtf8(isolate, kUdpTransportNotFound)));
return NULL;
}
bool CastStreamingNativeHandler::FrameReceiverConfigFromArg(
v8::Isolate* isolate,
const v8::Local<v8::Value>& arg,
media::cast::FrameReceiverConfig* config) const {
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::unique_ptr<base::Value> params_value(
converter->FromV8Value(arg, context()->v8_context()));
if (!params_value) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kUnableToConvertParams)));
return false;
}
std::unique_ptr<RtpReceiverParams> params =
RtpReceiverParams::FromValue(*params_value);
if (!params) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidRtpParams)));
return false;
}
config->receiver_ssrc = params->receiver_ssrc;
config->sender_ssrc = params->sender_ssrc;
config->rtp_max_delay_ms = params->max_latency;
if (config->rtp_max_delay_ms < 0 || config->rtp_max_delay_ms > 1000) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidLatency)));
return false;
}
config->channels = 2;
if (params->codec_name == "OPUS") {
config->codec = media::cast::CODEC_AUDIO_OPUS;
config->rtp_timebase = 48000;
config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_OPUS;
} else if (params->codec_name == "PCM16") {
config->codec = media::cast::CODEC_AUDIO_PCM16;
config->rtp_timebase = 48000;
config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_PCM16;
} else if (params->codec_name == "AAC") {
config->codec = media::cast::CODEC_AUDIO_AAC;
config->rtp_timebase = 48000;
config->rtp_payload_type = media::cast::RtpPayloadType::AUDIO_AAC;
} else if (params->codec_name == "VP8") {
config->codec = media::cast::CODEC_VIDEO_VP8;
config->rtp_timebase = 90000;
config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_VP8;
} else if (params->codec_name == "H264") {
config->codec = media::cast::CODEC_VIDEO_H264;
config->rtp_timebase = 90000;
config->rtp_payload_type = media::cast::RtpPayloadType::VIDEO_H264;
}
if (params->rtp_timebase) {
config->rtp_timebase = *params->rtp_timebase;
if (config->rtp_timebase < 1000 || config->rtp_timebase > 1000000) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidRtpTimebase)));
return false;
}
}
if (params->aes_key &&
!HexDecode(*params->aes_key, &config->aes_key)) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidAesKey)));
return false;
}
if (params->aes_iv_mask &&
!HexDecode(*params->aes_iv_mask, &config->aes_iv_mask)) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, kInvalidAesIvMask)));
return false;
}
return true;
}
bool CastStreamingNativeHandler::IPEndPointFromArg(
v8::Isolate* isolate,
const v8::Local<v8::Value>& arg,
net::IPEndPoint* ip_endpoint) const {
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::unique_ptr<base::Value> destination_value(
converter->FromV8Value(arg, context()->v8_context()));
if (!destination_value) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidAesIvMask)));
return false;
}
std::unique_ptr<IPEndPoint> destination =
IPEndPoint::FromValue(*destination_value);
if (!destination) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidDestination)));
return false;
}
net::IPAddress ip;
if (!ip.AssignFromIPLiteral(destination->address)) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, kInvalidDestination)));
return false;
}
*ip_endpoint = net::IPEndPoint(ip, destination->port);
return true;
}
void CastStreamingNativeHandler::StartCastRtpReceiver(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() < 8 || args.Length() > 9 ||
!args[0]->IsObject() ||
!args[1]->IsObject() ||
!args[2]->IsObject() ||
!args[3]->IsInt32() ||
!args[4]->IsInt32() ||
!args[5]->IsNumber() ||
!args[6]->IsString()) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs)));
return;
}
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
scoped_refptr<CastReceiverSession> session(
new CastReceiverSession());
media::cast::FrameReceiverConfig audio_config;
media::cast::FrameReceiverConfig video_config;
net::IPEndPoint local_endpoint;
net::IPEndPoint remote_endpoint;
if (!FrameReceiverConfigFromArg(isolate, args[0], &audio_config) ||
!FrameReceiverConfigFromArg(isolate, args[1], &video_config) ||
!IPEndPointFromArg(isolate, args[2], &local_endpoint)) {
return;
}
const int max_width = args[3]->ToInt32(args.GetIsolate())->Value();
const int max_height = args[4]->ToInt32(args.GetIsolate())->Value();
const double fps = args[5]->NumberValue();
if (fps <= 1) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kInvalidFPS)));
return;
}
const std::string url = *v8::String::Utf8Value(args[6]);
blink::WebMediaStream stream =
blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(GURL(url));
if (stream.isNull()) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kInvalidMediaStreamURL)));
return;
}
media::VideoCaptureFormat capture_format(gfx::Size(max_width, max_height),
fps, media::PIXEL_FORMAT_I420);
video_config.target_frame_rate = fps;
audio_config.target_frame_rate = 100;
media::AudioParameters params(
media::AudioParameters::AUDIO_PCM_LINEAR,
media::GuessChannelLayout(audio_config.channels),
audio_config.rtp_timebase, // sampling rate
16, static_cast<int>(audio_config.rtp_timebase /
audio_config.target_frame_rate));
if (!params.IsValid()) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kInvalidAudioParams)));
return;
}
std::unique_ptr<base::DictionaryValue> options;
if (args.Length() >= 9) {
std::unique_ptr<V8ValueConverter> converter(V8ValueConverter::create());
std::unique_ptr<base::Value> options_value(
converter->FromV8Value(args[8], context()->v8_context()));
if (!options_value->IsType(base::Value::Type::NONE)) {
options = base::DictionaryValue::From(std::move(options_value));
if (!options) {
args.GetIsolate()->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs)));
return;
}
}
}
if (!options) {
options.reset(new base::DictionaryValue());
}
v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent error_callback;
error_callback.Reset(args.GetIsolate(),
v8::Local<v8::Function>(args[7].As<v8::Function>()));
session->Start(
audio_config, video_config, local_endpoint, remote_endpoint,
std::move(options), capture_format,
base::Bind(&CastStreamingNativeHandler::AddTracksToMediaStream,
weak_factory_.GetWeakPtr(), url, params),
base::Bind(&CastStreamingNativeHandler::CallReceiverErrorCallback,
weak_factory_.GetWeakPtr(), error_callback));
}
void CastStreamingNativeHandler::CallReceiverErrorCallback(
v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent function,
const std::string& error_message) {
v8::Isolate* isolate = context()->v8_context()->GetIsolate();
v8::Local<v8::Value> arg = v8::String::NewFromUtf8(isolate,
error_message.data(),
v8::String::kNormalString,
error_message.size());
context()->SafeCallFunction(v8::Local<v8::Function>::New(isolate, function),
1, &arg);
}
void CastStreamingNativeHandler::AddTracksToMediaStream(
const std::string& url,
const media::AudioParameters& params,
scoped_refptr<media::AudioCapturerSource> audio,
std::unique_ptr<media::VideoCapturerSource> video) {
blink::WebMediaStream web_stream =
blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(GURL(url));
if (web_stream.isNull()) {
LOG(DFATAL) << "Stream not found.";
return;
}
if (!content::AddAudioTrackToMediaStream(
audio, params.sample_rate(), params.channel_layout(),
params.frames_per_buffer(), &web_stream)) {
LOG(ERROR) << "Failed to add Cast audio track to media stream.";
}
if (!content::AddVideoTrackToMediaStream(std::move(video), &web_stream)) {
LOG(ERROR) << "Failed to add Cast video track to media stream.";
}
}
} // namespace extensions