| // Copyright (c) 2012 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 "content/renderer/media/media_stream_impl.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/utf_string_conversions.h" |
| #include "content/renderer/media/capture_video_decoder.h" |
| #include "content/renderer/media/media_stream_dependency_factory.h" |
| #include "content/renderer/media/media_stream_dispatcher.h" |
| #include "content/renderer/media/peer_connection_handler.h" |
| #include "content/renderer/media/peer_connection_handler_jsep.h" |
| #include "content/renderer/media/video_capture_impl_manager.h" |
| #include "content/renderer/media/video_capture_module_impl.h" |
| #include "content/renderer/media/webrtc_audio_device_impl.h" |
| #include "content/renderer/p2p/ipc_network_manager.h" |
| #include "content/renderer/p2p/ipc_socket_factory.h" |
| #include "content/renderer/p2p/socket_dispatcher.h" |
| #include "jingle/glue/thread_wrapper.h" |
| #include "media/base/message_loop_factory.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamRegistry.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStreamComponent.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStreamDescriptor.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebMediaStreamSource.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebVector.h" |
| |
| namespace { |
| |
| const int kVideoCaptureWidth = 640; |
| const int kVideoCaptureHeight = 480; |
| const int kVideoCaptureFramePerSecond = 30; |
| |
| } // namespace |
| |
| // The MediaStreamMananger label for a stream is globally unique. The track |
| // session id is globally unique for the set of audio tracks and video tracks |
| // respectively. An audio track and a video track can have the same session id |
| // (without being related). Hence we create a unique track label from the stream |
| // label, track type and track session id: |
| // <MediaStreamManager-label>#{audio,video}-<session-ID>. |
| static std::string CreateTrackLabel( |
| const std::string& manager_label, |
| int session_id, |
| bool is_video) { |
| std::string track_label = manager_label; |
| if (is_video) { |
| track_label += "#video-"; |
| } else { |
| track_label += "#audio-"; |
| } |
| track_label += session_id; |
| return track_label; |
| } |
| |
| // Extracting the MediaStreamManager stream label will only work for track |
| // labels created by CreateTrackLabel. If is wasn't, the contents of the |
| // returned string is undefined. |
| static std::string ExtractManagerStreamLabel( |
| const std::string& track_label) { |
| std::string manager_label = track_label; |
| size_t pos = manager_label.rfind("#"); |
| // If # isn't found, the string is left intact. |
| manager_label = manager_label.substr(0, pos); |
| return manager_label; |
| } |
| |
| |
| int MediaStreamImpl::next_request_id_ = 0; |
| |
| MediaStreamImpl::MediaStreamImpl( |
| MediaStreamDispatcher* media_stream_dispatcher, |
| content::P2PSocketDispatcher* p2p_socket_dispatcher, |
| VideoCaptureImplManager* vc_manager, |
| MediaStreamDependencyFactory* dependency_factory) |
| : dependency_factory_(dependency_factory), |
| media_stream_dispatcher_(media_stream_dispatcher), |
| p2p_socket_dispatcher_(p2p_socket_dispatcher), |
| network_manager_(NULL), |
| vc_manager_(vc_manager), |
| signaling_thread_(NULL), |
| worker_thread_(NULL), |
| chrome_worker_thread_("Chrome_libJingle_WorkerThread") { |
| } |
| |
| MediaStreamImpl::~MediaStreamImpl() { |
| DCHECK(peer_connection_handlers_.empty()); |
| if (dependency_factory_.get()) |
| dependency_factory_->ReleasePeerConnectionFactory(); |
| if (network_manager_) { |
| // The network manager needs to free its resources on the thread they were |
| // created, which is the worked thread. |
| if (chrome_worker_thread_.IsRunning()) { |
| chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( |
| &MediaStreamImpl::DeleteIpcNetworkManager, |
| base::Unretained(this))); |
| // Stopping the thread will wait until all tasks have been |
| // processed before returning. We wait for the above task to finish before |
| // letting the destructor continue to avoid any potential race issues. |
| chrome_worker_thread_.Stop(); |
| } else { |
| NOTREACHED() << "Worker thread not running."; |
| } |
| } |
| } |
| |
| WebKit::WebPeerConnectionHandler* MediaStreamImpl::CreatePeerConnectionHandler( |
| WebKit::WebPeerConnectionHandlerClient* client) { |
| DCHECK(CalledOnValidThread()); |
| if (!EnsurePeerConnectionFactory()) |
| return NULL; |
| |
| PeerConnectionHandler* pc_handler = new PeerConnectionHandler( |
| client, |
| this, |
| dependency_factory_.get()); |
| peer_connection_handlers_.push_back(pc_handler); |
| |
| return pc_handler; |
| } |
| |
| WebKit::WebPeerConnection00Handler* |
| MediaStreamImpl::CreatePeerConnectionHandlerJsep( |
| WebKit::WebPeerConnection00HandlerClient* client) { |
| DCHECK(CalledOnValidThread()); |
| if (!EnsurePeerConnectionFactory()) |
| return NULL; |
| |
| PeerConnectionHandlerJsep* pc_handler = new PeerConnectionHandlerJsep( |
| client, |
| this, |
| dependency_factory_.get()); |
| peer_connection_handlers_.push_back(pc_handler); |
| |
| return pc_handler; |
| } |
| |
| void MediaStreamImpl::ClosePeerConnection( |
| PeerConnectionHandlerBase* pc_handler) { |
| DCHECK(CalledOnValidThread()); |
| peer_connection_handlers_.remove(pc_handler); |
| } |
| |
| webrtc::MediaStreamTrackInterface* MediaStreamImpl::GetLocalMediaStreamTrack( |
| const std::string& label) { |
| DCHECK(CalledOnValidThread()); |
| MediaStreamTrackPtrMap::iterator it = local_tracks_.find(label); |
| if (it == local_tracks_.end()) |
| return NULL; |
| MediaStreamTrackPtr track = it->second; |
| return track.get(); |
| } |
| |
| void MediaStreamImpl::requestUserMedia( |
| const WebKit::WebUserMediaRequest& user_media_request, |
| const WebKit::WebVector<WebKit::WebMediaStreamSource>& audio_sources, |
| const WebKit::WebVector<WebKit::WebMediaStreamSource>& video_sources) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!user_media_request.isNull()); |
| |
| int request_id = next_request_id_++; |
| |
| bool audio = user_media_request.audio(); |
| media_stream::StreamOptions::VideoOption video_option = |
| media_stream::StreamOptions::kNoCamera; |
| if (user_media_request.video()) |
| video_option = media_stream::StreamOptions::kFacingBoth; |
| |
| std::string security_origin = UTF16ToUTF8( |
| user_media_request.securityOrigin().toString()); |
| |
| DVLOG(1) << "MediaStreamImpl::generateStream(" << request_id << ", [ " |
| << (audio ? "audio" : "") |
| << (user_media_request.video() ? " video" : "") << "], " |
| << security_origin << ")"; |
| |
| user_media_requests_.insert( |
| std::pair<int, WebKit::WebUserMediaRequest>( |
| request_id, user_media_request)); |
| |
| media_stream_dispatcher_->GenerateStream( |
| request_id, |
| AsWeakPtr(), |
| media_stream::StreamOptions(audio, video_option), |
| security_origin); |
| } |
| |
| void MediaStreamImpl::cancelUserMediaRequest( |
| const WebKit::WebUserMediaRequest& user_media_request) { |
| DCHECK(CalledOnValidThread()); |
| // TODO(grunell): Implement. |
| NOTIMPLEMENTED(); |
| } |
| |
| scoped_refptr<media::VideoDecoder> MediaStreamImpl::GetVideoDecoder( |
| const GURL& url, |
| media::MessageLoopFactory* message_loop_factory) { |
| DCHECK(CalledOnValidThread()); |
| WebKit::WebMediaStreamDescriptor descriptor( |
| WebKit::WebMediaStreamRegistry::lookupMediaStreamDescriptor(url)); |
| if (descriptor.isNull()) |
| return NULL; // This is not a valid stream. |
| |
| WebKit::WebVector<WebKit::WebMediaStreamComponent> components_vector; |
| descriptor.videoSources(components_vector); |
| |
| // TODO(perkj): Implement track selection. For now, render the first |
| // available source. |
| |
| for (size_t i = 0; i < components_vector.size(); ++i) { |
| const WebKit::WebMediaStreamSource& source = components_vector[i].source(); |
| std::string source_id = UTF16ToUTF8(source.id()); |
| if (IsLocalSource(source_id)) { |
| return CreateLocalVideoDecoder(source_id, message_loop_factory); |
| } |
| PeerConnectionHandlerBase* pc_handler = |
| FindPeerConnectionBySource(source_id); |
| if (pc_handler != NULL) { |
| return CreateRemoteVideoDecoder(source_id, |
| pc_handler, |
| url, |
| message_loop_factory); |
| } |
| } |
| |
| // WebKit claim this is MediaStream url but there is no valid source. |
| NOTREACHED(); |
| return NULL; |
| } |
| |
| void MediaStreamImpl::OnStreamGenerated( |
| int request_id, |
| const std::string& label, |
| const media_stream::StreamDeviceInfoArray& audio_array, |
| const media_stream::StreamDeviceInfoArray& video_array) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Creating the peer connection factory can fail if for example the audio |
| // (input or output) or video device cannot be opened. Handling such cases |
| // better is a higher level design discussion which involves the media |
| // manager, webrtc and libjingle. We should still continue and fire a |
| // succeeded callback here, to maintain the same states in WebKit as in the |
| // media manager in terms of streams and tracks. We cannot create any native |
| // track objects however, so we'll just have to skip that. Furthermore, |
| // creating a peer connection later on will fail if we don't have a factory. |
| EnsurePeerConnectionFactory(); |
| |
| // Add audio tracks. |
| WebKit::WebVector<WebKit::WebMediaStreamSource> audio_source_vector( |
| audio_array.size()); |
| std::string track_label; |
| for (size_t i = 0; i < audio_array.size(); ++i) { |
| track_label = CreateTrackLabel(label, audio_array[i].session_id, false); |
| if (dependency_factory_->PeerConnectionFactoryCreated()) { |
| MediaStreamTrackPtr audio_track( |
| dependency_factory_->CreateLocalAudioTrack(audio_array[i].name, |
| NULL)); |
| local_tracks_.insert( |
| std::pair<std::string, MediaStreamTrackPtr>(track_label, |
| audio_track)); |
| } |
| audio_source_vector[i].initialize( |
| UTF8ToUTF16(track_label), |
| WebKit::WebMediaStreamSource::TypeAudio, |
| UTF8ToUTF16(audio_array[i].name)); |
| } |
| |
| // Add video tracks. |
| WebKit::WebVector<WebKit::WebMediaStreamSource> video_source_vector( |
| video_array.size()); |
| for (size_t i = 0; i < video_array.size(); ++i) { |
| track_label = CreateTrackLabel(label, video_array[i].session_id, true); |
| if (dependency_factory_->PeerConnectionFactoryCreated()) { |
| webrtc::VideoCaptureModule* vcm = new VideoCaptureModuleImpl( |
| video_array[i].session_id, |
| vc_manager_.get()); |
| MediaStreamTrackPtr video_track( |
| dependency_factory_->CreateLocalVideoTrack( |
| video_array[i].name, |
| // The video capturer takes ownership of |vcm|. |
| webrtc::CreateVideoCapturer(vcm))); |
| local_tracks_.insert( |
| std::pair<std::string, MediaStreamTrackPtr>(track_label, |
| video_track)); |
| } |
| video_source_vector[i].initialize( |
| UTF8ToUTF16(track_label), |
| WebKit::WebMediaStreamSource::TypeVideo, |
| UTF8ToUTF16(video_array[i].name)); |
| } |
| |
| // TODO(grunell): Remove tracks from the map when support to stop is |
| // added in WebKit. |
| |
| MediaRequestMap::iterator it = user_media_requests_.find(request_id); |
| if (it == user_media_requests_.end()) { |
| DVLOG(1) << "Request ID not found"; |
| return; |
| } |
| WebKit::WebUserMediaRequest user_media_request = it->second; |
| user_media_requests_.erase(it); |
| |
| user_media_request.requestSucceeded(audio_source_vector, video_source_vector); |
| } |
| |
| void MediaStreamImpl::OnStreamGenerationFailed(int request_id) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(1) << "MediaStreamImpl::OnStreamGenerationFailed(" |
| << request_id << ")"; |
| MediaRequestMap::iterator it = user_media_requests_.find(request_id); |
| if (it == user_media_requests_.end()) { |
| DVLOG(1) << "Request ID not found"; |
| return; |
| } |
| WebKit::WebUserMediaRequest user_media_request = it->second; |
| user_media_requests_.erase(it); |
| |
| user_media_request.requestFailed(); |
| } |
| |
| void MediaStreamImpl::OnVideoDeviceFailed(const std::string& label, |
| int index) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(1) << "MediaStreamImpl::OnVideoDeviceFailed(" |
| << label << ", " << index << ")"; |
| // TODO(grunell): Implement. Currently not supported in WebKit. |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::OnAudioDeviceFailed(const std::string& label, |
| int index) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(1) << "MediaStreamImpl::OnAudioDeviceFailed(" |
| << label << ", " << index << ")"; |
| // TODO(grunell): Implement. Currently not supported in WebKit. |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::OnDevicesEnumerated( |
| int request_id, |
| const media_stream::StreamDeviceInfoArray& device_array) { |
| DVLOG(1) << "MediaStreamImpl::OnDevicesEnumerated(" |
| << request_id << ")"; |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::OnDevicesEnumerationFailed(int request_id) { |
| DVLOG(1) << "MediaStreamImpl::OnDevicesEnumerationFailed(" |
| << request_id << ")"; |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::OnDeviceOpened( |
| int request_id, |
| const std::string& label, |
| const media_stream::StreamDeviceInfo& video_device) { |
| DVLOG(1) << "MediaStreamImpl::OnDeviceOpened(" |
| << request_id << ", " << label << ")"; |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::OnDeviceOpenFailed(int request_id) { |
| DVLOG(1) << "MediaStreamImpl::VideoDeviceOpenFailed(" |
| << request_id << ")"; |
| NOTIMPLEMENTED(); |
| } |
| |
| void MediaStreamImpl::InitializeWorkerThread(talk_base::Thread** thread, |
| base::WaitableEvent* event) { |
| jingle_glue::JingleThreadWrapper::EnsureForCurrentThread(); |
| jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); |
| *thread = jingle_glue::JingleThreadWrapper::current(); |
| event->Signal(); |
| } |
| |
| void MediaStreamImpl::CreateIpcNetworkManagerOnWorkerThread( |
| base::WaitableEvent* event) { |
| DCHECK_EQ(MessageLoop::current(), chrome_worker_thread_.message_loop()); |
| network_manager_ = new content::IpcNetworkManager(p2p_socket_dispatcher_); |
| event->Signal(); |
| } |
| |
| void MediaStreamImpl::DeleteIpcNetworkManager() { |
| DCHECK_EQ(MessageLoop::current(), chrome_worker_thread_.message_loop()); |
| delete network_manager_; |
| network_manager_ = NULL; |
| } |
| |
| bool MediaStreamImpl::EnsurePeerConnectionFactory() { |
| DCHECK(CalledOnValidThread()); |
| if (!signaling_thread_) { |
| jingle_glue::JingleThreadWrapper::EnsureForCurrentThread(); |
| jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); |
| signaling_thread_ = jingle_glue::JingleThreadWrapper::current(); |
| } |
| |
| if (!worker_thread_) { |
| if (!chrome_worker_thread_.IsRunning()) { |
| if (!chrome_worker_thread_.Start()) { |
| LOG(ERROR) << "Could not start worker thread"; |
| signaling_thread_ = NULL; |
| return false; |
| } |
| } |
| base::WaitableEvent event(true, false); |
| chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( |
| &MediaStreamImpl::InitializeWorkerThread, |
| this, |
| &worker_thread_, |
| &event)); |
| event.Wait(); |
| DCHECK(worker_thread_); |
| } |
| |
| if (!network_manager_) { |
| base::WaitableEvent event(true, false); |
| chrome_worker_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( |
| &MediaStreamImpl::CreateIpcNetworkManagerOnWorkerThread, |
| this, |
| &event)); |
| event.Wait(); |
| } |
| |
| if (!socket_factory_.get()) { |
| socket_factory_.reset( |
| new content::IpcPacketSocketFactory(p2p_socket_dispatcher_)); |
| } |
| |
| if (!dependency_factory_->PeerConnectionFactoryCreated()) { |
| if (!dependency_factory_->CreatePeerConnectionFactory( |
| worker_thread_, |
| signaling_thread_, |
| p2p_socket_dispatcher_, |
| network_manager_, |
| socket_factory_.get())) { |
| LOG(ERROR) << "Could not create PeerConnection factory"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool MediaStreamImpl::IsLocalSource(const std::string& source_id) { |
| std::string msm_label = ExtractManagerStreamLabel(source_id); |
| if (msm_label.empty()) |
| return false; |
| return media_stream_dispatcher_->IsStream(msm_label); |
| } |
| |
| scoped_refptr<media::VideoDecoder> MediaStreamImpl::CreateLocalVideoDecoder( |
| const std::string& source_id, |
| media::MessageLoopFactory* message_loop_factory) { |
| std::string msm_label = ExtractManagerStreamLabel(source_id); |
| if (msm_label.empty()) |
| return NULL; |
| |
| int video_session_id = |
| media_stream_dispatcher_->video_session_id(msm_label, 0); |
| media::VideoCaptureCapability capability; |
| capability.width = kVideoCaptureWidth; |
| capability.height = kVideoCaptureHeight; |
| capability.frame_rate = kVideoCaptureFramePerSecond; |
| capability.expected_capture_delay = 0; |
| capability.color = media::VideoFrame::I420; |
| capability.interlaced = false; |
| return new CaptureVideoDecoder( |
| message_loop_factory->GetMessageLoopProxy("CaptureVideoDecoderThread"), |
| video_session_id, |
| vc_manager_.get(), |
| capability); |
| } |
| |
| PeerConnectionHandlerBase* MediaStreamImpl::FindPeerConnectionBySource( |
| const std::string& source_id) { |
| std::list<PeerConnectionHandlerBase*>::iterator it; |
| for (it = peer_connection_handlers_.begin(); |
| it != peer_connection_handlers_.end(); ++it) { |
| if ((*it)->HasRemoteVideoTrack(source_id)) { |
| return *it; |
| } |
| } |
| return NULL; |
| } |
| |
| scoped_refptr<media::VideoDecoder> MediaStreamImpl::CreateRemoteVideoDecoder( |
| const std::string& source_id, |
| PeerConnectionHandlerBase* pc_handler, |
| const GURL& url, |
| media::MessageLoopFactory* message_loop_factory) { |
| RTCVideoDecoder* rtc_video_decoder = new RTCVideoDecoder( |
| message_loop_factory->GetMessageLoop("RtcVideoDecoderThread"), |
| url.spec()); |
| talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface> renderer( |
| new talk_base::RefCountedObject<VideoRendererWrapper>(rtc_video_decoder)); |
| |
| pc_handler->SetRemoteVideoRenderer(source_id, renderer); |
| return rtc_video_decoder; |
| } |
| |
| MediaStreamImpl::VideoRendererWrapper::VideoRendererWrapper( |
| RTCVideoDecoder* decoder) |
| : rtc_video_decoder_(decoder) { |
| } |
| |
| MediaStreamImpl::VideoRendererWrapper::~VideoRendererWrapper() { |
| } |