/* * libjingle * Copyright 2015 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/app/webrtc/androidvideocapturer.h" #include "talk/media/webrtc/webrtcvideoframe.h" #include "webrtc/base/common.h" #include "webrtc/base/json.h" #include "webrtc/base/timeutils.h" namespace webrtc { // A hack for avoiding deep frame copies in // cricket::VideoCapturer.SignalFrameCaptured() using a custom FrameFactory. // A frame is injected using UpdateCapturedFrame(), and converted into a // cricket::VideoFrame with CreateAliasedFrame(). UpdateCapturedFrame() should // be called before CreateAliasedFrame() for every frame. // TODO(magjed): Add an interface cricket::VideoCapturer::OnFrameCaptured() // for ref counted I420 frames instead of this hack. class AndroidVideoCapturer::FrameFactory : public cricket::VideoFrameFactory { public: FrameFactory(const rtc::scoped_refptr& delegate) : delegate_(delegate) { // Create a CapturedFrame that only contains header information, not the // actual pixel data. captured_frame_.pixel_height = 1; captured_frame_.pixel_width = 1; captured_frame_.data = nullptr; captured_frame_.data_size = cricket::CapturedFrame::kUnknownDataSize; captured_frame_.fourcc = static_cast(cricket::FOURCC_ANY); } void UpdateCapturedFrame( const rtc::scoped_refptr& buffer, int rotation, int64_t time_stamp_in_ns) { buffer_ = buffer; captured_frame_.width = buffer->width(); captured_frame_.height = buffer->height(); captured_frame_.time_stamp = time_stamp_in_ns; captured_frame_.rotation = rotation; } void ClearCapturedFrame() { buffer_ = nullptr; captured_frame_.width = 0; captured_frame_.height = 0; captured_frame_.time_stamp = 0; } const cricket::CapturedFrame* GetCapturedFrame() const { return &captured_frame_; } cricket::VideoFrame* CreateAliasedFrame( const cricket::CapturedFrame* captured_frame, int dst_width, int dst_height) const override { // Check that captured_frame is actually our frame. RTC_CHECK(captured_frame == &captured_frame_); RTC_CHECK(buffer_->native_handle() == nullptr); rtc::scoped_ptr frame(new cricket::WebRtcVideoFrame( ShallowCenterCrop(buffer_, dst_width, dst_height), captured_frame->time_stamp, captured_frame->GetRotation())); // Caller takes ownership. // TODO(magjed): Change CreateAliasedFrame() to return a rtc::scoped_ptr. return apply_rotation_ ? frame->GetCopyWithRotationApplied()->Copy() : frame.release(); } cricket::VideoFrame* CreateAliasedFrame( const cricket::CapturedFrame* input_frame, int cropped_input_width, int cropped_input_height, int output_width, int output_height) const override { if (buffer_->native_handle() != nullptr) { // TODO(perkj): Implement CreateAliasedFrame properly for textures. rtc::scoped_ptr frame(new cricket::WebRtcVideoFrame( buffer_, input_frame->time_stamp, input_frame->GetRotation())); return frame.release(); } return VideoFrameFactory::CreateAliasedFrame(input_frame, cropped_input_width, cropped_input_height, output_width, output_height); } private: rtc::scoped_refptr buffer_; cricket::CapturedFrame captured_frame_; rtc::scoped_refptr delegate_; }; AndroidVideoCapturer::AndroidVideoCapturer( const rtc::scoped_refptr& delegate) : running_(false), delegate_(delegate), frame_factory_(NULL), current_state_(cricket::CS_STOPPED) { thread_checker_.DetachFromThread(); std::string json_string = delegate_->GetSupportedFormats(); LOG(LS_INFO) << json_string; Json::Value json_values; Json::Reader reader(Json::Features::strictMode()); if (!reader.parse(json_string, json_values)) { LOG(LS_ERROR) << "Failed to parse formats."; } std::vector formats; for (Json::ArrayIndex i = 0; i < json_values.size(); ++i) { const Json::Value& json_value = json_values[i]; RTC_CHECK(!json_value["width"].isNull() && !json_value["height"].isNull() && !json_value["framerate"].isNull()); cricket::VideoFormat format( json_value["width"].asInt(), json_value["height"].asInt(), cricket::VideoFormat::FpsToInterval(json_value["framerate"].asInt()), cricket::FOURCC_YV12); formats.push_back(format); } SetSupportedFormats(formats); // Do not apply frame rotation by default. SetApplyRotation(false); } AndroidVideoCapturer::~AndroidVideoCapturer() { RTC_CHECK(!running_); } cricket::CaptureState AndroidVideoCapturer::Start( const cricket::VideoFormat& capture_format) { RTC_CHECK(thread_checker_.CalledOnValidThread()); RTC_CHECK(!running_); const int fps = cricket::VideoFormat::IntervalToFps(capture_format.interval); LOG(LS_INFO) << " AndroidVideoCapturer::Start " << capture_format.width << "x" << capture_format.height << "@" << fps; frame_factory_ = new AndroidVideoCapturer::FrameFactory(delegate_.get()); set_frame_factory(frame_factory_); running_ = true; delegate_->Start(capture_format.width, capture_format.height, fps, this); SetCaptureFormat(&capture_format); current_state_ = cricket::CS_STARTING; return current_state_; } void AndroidVideoCapturer::Stop() { LOG(LS_INFO) << " AndroidVideoCapturer::Stop "; RTC_CHECK(thread_checker_.CalledOnValidThread()); RTC_CHECK(running_); running_ = false; SetCaptureFormat(NULL); delegate_->Stop(); current_state_ = cricket::CS_STOPPED; SignalStateChange(this, current_state_); } bool AndroidVideoCapturer::IsRunning() { RTC_CHECK(thread_checker_.CalledOnValidThread()); return running_; } bool AndroidVideoCapturer::GetPreferredFourccs(std::vector* fourccs) { RTC_CHECK(thread_checker_.CalledOnValidThread()); fourccs->push_back(cricket::FOURCC_YV12); return true; } void AndroidVideoCapturer::OnCapturerStarted(bool success) { RTC_CHECK(thread_checker_.CalledOnValidThread()); cricket::CaptureState new_state = success ? cricket::CS_RUNNING : cricket::CS_FAILED; if (new_state == current_state_) return; current_state_ = new_state; // TODO(perkj): SetCaptureState can not be used since it posts to |thread_|. // But |thread_ | is currently just the thread that happened to create the // cricket::VideoCapturer. SignalStateChange(this, new_state); } void AndroidVideoCapturer::OnIncomingFrame( const rtc::scoped_refptr& buffer, int rotation, int64_t time_stamp) { RTC_CHECK(thread_checker_.CalledOnValidThread()); frame_factory_->UpdateCapturedFrame(buffer, rotation, time_stamp); SignalFrameCaptured(this, frame_factory_->GetCapturedFrame()); frame_factory_->ClearCapturedFrame(); } void AndroidVideoCapturer::OnOutputFormatRequest( int width, int height, int fps) { RTC_CHECK(thread_checker_.CalledOnValidThread()); const cricket::VideoFormat& current = video_adapter()->output_format(); cricket::VideoFormat format( width, height, cricket::VideoFormat::FpsToInterval(fps), current.fourcc); video_adapter()->OnOutputFormatRequest(format); } bool AndroidVideoCapturer::GetBestCaptureFormat( const cricket::VideoFormat& desired, cricket::VideoFormat* best_format) { // Delegate this choice to VideoCapturerAndroid.startCapture(). *best_format = desired; return true; } } // namespace webrtc