/*
* libjingle
* Copyright 2012 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/media/base/capturemanager.h"
#include <algorithm>
#include "talk/media/base/videocapturer.h"
#include "talk/media/base/videorenderer.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
namespace cricket {
// CaptureManager helper class.
class VideoCapturerState {
public:
static const VideoFormatPod kDefaultCaptureFormat;
static VideoCapturerState* Create(VideoCapturer* video_capturer);
~VideoCapturerState() {}
void AddCaptureResolution(const VideoFormat& desired_format);
bool RemoveCaptureResolution(const VideoFormat& format);
VideoFormat GetHighestFormat(VideoCapturer* video_capturer) const;
int IncCaptureStartRef();
int DecCaptureStartRef();
CaptureRenderAdapter* adapter() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
return adapter_.get();
}
VideoCapturer* GetVideoCapturer() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
return adapter()->video_capturer();
}
int start_count() const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
return start_count_;
}
private:
struct CaptureResolutionInfo {
VideoFormat video_format;
int format_ref_count;
};
typedef std::vector<CaptureResolutionInfo> CaptureFormats;
explicit VideoCapturerState(CaptureRenderAdapter* adapter);
rtc::ThreadChecker thread_checker_;
rtc::scoped_ptr<CaptureRenderAdapter> adapter_;
int start_count_;
CaptureFormats capture_formats_;
};
const VideoFormatPod VideoCapturerState::kDefaultCaptureFormat = {
640, 360, FPS_TO_INTERVAL(30), FOURCC_ANY
};
VideoCapturerState::VideoCapturerState(CaptureRenderAdapter* adapter)
: adapter_(adapter), start_count_(1) {}
// static
VideoCapturerState* VideoCapturerState::Create(VideoCapturer* video_capturer) {
CaptureRenderAdapter* adapter = CaptureRenderAdapter::Create(video_capturer);
if (!adapter) {
return NULL;
}
return new VideoCapturerState(adapter);
}
void VideoCapturerState::AddCaptureResolution(
const VideoFormat& desired_format) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
for (CaptureFormats::iterator iter = capture_formats_.begin();
iter != capture_formats_.end(); ++iter) {
if (desired_format == iter->video_format) {
++(iter->format_ref_count);
return;
}
}
CaptureResolutionInfo capture_resolution = { desired_format, 1 };
capture_formats_.push_back(capture_resolution);
}
bool VideoCapturerState::RemoveCaptureResolution(const VideoFormat& format) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
for (CaptureFormats::iterator iter = capture_formats_.begin();
iter != capture_formats_.end(); ++iter) {
if (format == iter->video_format) {
--(iter->format_ref_count);
if (iter->format_ref_count == 0) {
capture_formats_.erase(iter);
}
return true;
}
}
return false;
}
VideoFormat VideoCapturerState::GetHighestFormat(
VideoCapturer* video_capturer) const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoFormat highest_format(0, 0, VideoFormat::FpsToInterval(1), FOURCC_ANY);
if (capture_formats_.empty()) {
VideoFormat default_format(kDefaultCaptureFormat);
return default_format;
}
for (CaptureFormats::const_iterator iter = capture_formats_.begin();
iter != capture_formats_.end(); ++iter) {
if (iter->video_format.width > highest_format.width) {
highest_format.width = iter->video_format.width;
}
if (iter->video_format.height > highest_format.height) {
highest_format.height = iter->video_format.height;
}
if (iter->video_format.interval < highest_format.interval) {
highest_format.interval = iter->video_format.interval;
}
}
return highest_format;
}
int VideoCapturerState::IncCaptureStartRef() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
return ++start_count_;
}
int VideoCapturerState::DecCaptureStartRef() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (start_count_ > 0) {
// Start count may be 0 if a capturer was added but never started.
--start_count_;
}
return start_count_;
}
CaptureManager::CaptureManager() {
// Allowing construction of manager in any thread as long as subsequent calls
// are all from the same thread.
thread_checker_.DetachFromThread();
}
CaptureManager::~CaptureManager() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
// Since we don't own any of the capturers, all capturers should have been
// cleaned up before we get here. In fact, in the normal shutdown sequence,
// all capturers *will* be shut down by now, so trying to stop them here
// will crash. If we're still tracking any, it's a dangling pointer.
// TODO(hbos): RTC_DCHECK instead of RTC_CHECK until we figure out why
// capture_states_ is not always empty here.
RTC_DCHECK(capture_states_.empty());
}
bool CaptureManager::StartVideoCapture(VideoCapturer* video_capturer,
const VideoFormat& desired_format) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (desired_format.width == 0 || desired_format.height == 0) {
return false;
}
if (!video_capturer) {
return false;
}
VideoCapturerState* capture_state = GetCaptureState(video_capturer);
if (capture_state) {
const int ref_count = capture_state->IncCaptureStartRef();
if (ref_count < 1) {
ASSERT(false);
}
// VideoCapturer has already been started. Don't start listening to
// callbacks since that has already been done.
capture_state->AddCaptureResolution(desired_format);
return true;
}
if (!RegisterVideoCapturer(video_capturer)) {
return false;
}
capture_state = GetCaptureState(video_capturer);
ASSERT(capture_state != NULL);
capture_state->AddCaptureResolution(desired_format);
if (!StartWithBestCaptureFormat(capture_state, video_capturer)) {
UnregisterVideoCapturer(capture_state);
return false;
}
return true;
}
bool CaptureManager::StopVideoCapture(VideoCapturer* video_capturer,
const VideoFormat& format) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoCapturerState* capture_state = GetCaptureState(video_capturer);
if (!capture_state) {
return false;
}
if (!capture_state->RemoveCaptureResolution(format)) {
return false;
}
if (capture_state->DecCaptureStartRef() == 0) {
// Unregistering cannot fail as capture_state is not NULL.
UnregisterVideoCapturer(capture_state);
}
return true;
}
bool CaptureManager::RestartVideoCapture(
VideoCapturer* video_capturer,
const VideoFormat& previous_format,
const VideoFormat& desired_format,
CaptureManager::RestartOptions options) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!IsCapturerRegistered(video_capturer)) {
LOG(LS_ERROR) << "RestartVideoCapture: video_capturer is not registered.";
return false;
}
// Start the new format first. This keeps the capturer running.
if (!StartVideoCapture(video_capturer, desired_format)) {
LOG(LS_ERROR) << "RestartVideoCapture: unable to start video capture with "
"desired_format=" << desired_format.ToString();
return false;
}
// Stop the old format.
if (!StopVideoCapture(video_capturer, previous_format)) {
LOG(LS_ERROR) << "RestartVideoCapture: unable to stop video capture with "
"previous_format=" << previous_format.ToString();
// Undo the start request we just performed.
StopVideoCapture(video_capturer, desired_format);
return false;
}
switch (options) {
case kForceRestart: {
VideoCapturerState* capture_state = GetCaptureState(video_capturer);
ASSERT(capture_state && capture_state->start_count() > 0);
// Try a restart using the new best resolution.
VideoFormat highest_asked_format =
capture_state->GetHighestFormat(video_capturer);
VideoFormat capture_format;
if (video_capturer->GetBestCaptureFormat(highest_asked_format,
&capture_format)) {
if (!video_capturer->Restart(capture_format)) {
LOG(LS_ERROR) << "RestartVideoCapture: Restart failed.";
}
} else {
LOG(LS_WARNING)
<< "RestartVideoCapture: Couldn't find a best capture format for "
<< highest_asked_format.ToString();
}
break;
}
case kRequestRestart:
// TODO(ryanpetrie): Support restart requests. Should this
// to-be-implemented logic be used for {Start,Stop}VideoCapture as well?
break;
default:
LOG(LS_ERROR) << "Unknown/unimplemented RestartOption";
break;
}
return true;
}
bool CaptureManager::AddVideoRenderer(VideoCapturer* video_capturer,
VideoRenderer* video_renderer) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!video_capturer || !video_renderer) {
return false;
}
CaptureRenderAdapter* adapter = GetAdapter(video_capturer);
if (!adapter) {
return false;
}
return adapter->AddRenderer(video_renderer);
}
bool CaptureManager::RemoveVideoRenderer(VideoCapturer* video_capturer,
VideoRenderer* video_renderer) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!video_capturer || !video_renderer) {
return false;
}
CaptureRenderAdapter* adapter = GetAdapter(video_capturer);
if (!adapter) {
return false;
}
return adapter->RemoveRenderer(video_renderer);
}
bool CaptureManager::IsCapturerRegistered(VideoCapturer* video_capturer) const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
return GetCaptureState(video_capturer) != NULL;
}
bool CaptureManager::RegisterVideoCapturer(VideoCapturer* video_capturer) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoCapturerState* capture_state =
VideoCapturerState::Create(video_capturer);
if (!capture_state) {
return false;
}
capture_states_[video_capturer] = capture_state;
SignalCapturerStateChange.repeat(video_capturer->SignalStateChange);
return true;
}
void CaptureManager::UnregisterVideoCapturer(
VideoCapturerState* capture_state) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoCapturer* video_capturer = capture_state->GetVideoCapturer();
capture_states_.erase(video_capturer);
delete capture_state;
// When unregistering a VideoCapturer, the CaptureManager needs to unregister
// from all state change callbacks from the VideoCapturer. E.g. to avoid
// problems with multiple callbacks if registering the same VideoCapturer
// multiple times. The VideoCapturer will update the capturer state. However,
// this is done through Post-calls which means it may happen at any time. If
// the CaptureManager no longer is listening to the VideoCapturer it will not
// receive those callbacks. Here it is made sure that the the callback is
// indeed sent by letting the ChannelManager do the signaling. The downside is
// that the callback may happen before the VideoCapturer is stopped. However,
// for the CaptureManager it doesn't matter as it will no longer receive any
// frames from the VideoCapturer.
SignalCapturerStateChange.stop(video_capturer->SignalStateChange);
if (video_capturer->IsRunning()) {
video_capturer->Stop();
SignalCapturerStateChange(video_capturer, CS_STOPPED);
}
}
bool CaptureManager::StartWithBestCaptureFormat(
VideoCapturerState* capture_state, VideoCapturer* video_capturer) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoFormat highest_asked_format =
capture_state->GetHighestFormat(video_capturer);
VideoFormat capture_format;
if (!video_capturer->GetBestCaptureFormat(highest_asked_format,
&capture_format)) {
LOG(LS_WARNING) << "Unsupported format:"
<< " width=" << highest_asked_format.width
<< " height=" << highest_asked_format.height
<< ". Supported formats are:";
const std::vector<VideoFormat>* formats =
video_capturer->GetSupportedFormats();
ASSERT(formats != NULL);
for (std::vector<VideoFormat>::const_iterator i = formats->begin();
i != formats->end(); ++i) {
const VideoFormat& format = *i;
LOG(LS_WARNING) << " " << GetFourccName(format.fourcc)
<< ":" << format.width << "x" << format.height << "x"
<< format.framerate();
}
return false;
}
return video_capturer->StartCapturing(capture_format);
}
VideoCapturerState* CaptureManager::GetCaptureState(
VideoCapturer* video_capturer) const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
CaptureStates::const_iterator iter = capture_states_.find(video_capturer);
if (iter == capture_states_.end()) {
return NULL;
}
return iter->second;
}
CaptureRenderAdapter* CaptureManager::GetAdapter(
VideoCapturer* video_capturer) const {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
VideoCapturerState* capture_state = GetCaptureState(video_capturer);
if (!capture_state) {
return NULL;
}
return capture_state->adapter();
}
} // namespace cricket