| // libjingle |
| // Copyright 2010 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. |
| // |
| // Implementartion file of class VideoCapturer. |
| |
| #include "talk/session/phone/videocapturer.h" |
| |
| #include <algorithm> |
| |
| #include "talk/base/logging.h" |
| |
| namespace cricket { |
| |
| static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63); |
| static const int64 kMinDesirableFps = static_cast<int64>(15); |
| |
| ///////////////////////////////////////////////////////////////////// |
| // Implementation of struct CapturedFrame |
| ///////////////////////////////////////////////////////////////////// |
| CapturedFrame::CapturedFrame() |
| : width(0), |
| height(0), |
| fourcc(0), |
| pixel_width(0), |
| pixel_height(0), |
| elapsed_time(0), |
| time_stamp(0), |
| data_size(0), |
| rotation(0), |
| data(NULL) { |
| } |
| |
| // TODO: Remove this function once lmimediaengine stops using it. |
| bool CapturedFrame::GetDataSize(uint32* size) const { |
| if (!size || data_size == CapturedFrame::kUnknownDataSize) { |
| return false; |
| } |
| *size = data_size; |
| return true; |
| } |
| |
| ///////////////////////////////////////////////////////////////////// |
| // Implementation of class VideoCapturer |
| ///////////////////////////////////////////////////////////////////// |
| void VideoCapturer::SetSupportedFormats( |
| const std::vector<VideoFormat>& formats) { |
| if (!supported_formats_.get()) { |
| supported_formats_.reset(new std::vector<VideoFormat>); |
| } |
| *(supported_formats_.get()) = formats; |
| } |
| |
| bool VideoCapturer::GetBestCaptureFormat(const VideoFormat& desired, |
| VideoFormat* best_format) { |
| if (!supported_formats_.get()) { |
| return false; |
| } |
| |
| VideoFormat format = desired; |
| // If the application requests 16x9 and the camera does not support 16x9 HD |
| // or the application requests 16x10, change the request to 4x3. Otherwise, |
| // keep the request. |
| if (format.width * 9 == format.height * 16 && |
| !Includes16x9HD(*supported_formats_.get())) { |
| format.height = format.width * 3 / 4; |
| } else if (format.width * 10 == format.height * 16) { |
| format.height = format.width * 3 / 4; |
| } |
| LOG(LS_INFO) << " Capture Desired " << desired.ToString() |
| << " Capture Requested " << format.ToString(); |
| int64 best_distance = kMaxDistance; |
| std::vector<VideoFormat>::const_iterator best = supported_formats_->end(); |
| std::vector<VideoFormat>::const_iterator i; |
| for (i = supported_formats_->begin(); i != supported_formats_->end(); ++i) { |
| int64 distance = GetFormatDistance(format, *i); |
| // TODO: Reduce to LS_VERBOSE if/when camera capture is |
| // relatively bug free. |
| LOG(LS_INFO) << " Supported " << i->ToString() |
| << " distance " << distance; |
| if (distance < best_distance) { |
| best_distance = distance; |
| best = i; |
| } |
| } |
| if (supported_formats_->end() == best) { |
| LOG(LS_ERROR) << " No acceptable camera format found"; |
| return false; |
| } |
| |
| if (best_format) { |
| best_format->width = best->width; |
| best_format->height = best->height; |
| best_format->fourcc = best->fourcc; |
| best_format->interval = talk_base::_max(format.interval, best->interval); |
| LOG(LS_INFO) << " Best " << best_format->ToString() |
| << " distance " << best_distance; |
| } |
| return true; |
| } |
| |
| // Get the distance between the supported and desired formats. |
| // Prioritization is done according to this algorithm: |
| // 1) Width closeness. If not same, we prefer wider. |
| // 2) Height closeness. If not same, we prefer higher. |
| // 3) Framerate closeness. If not same, we prefer faster. |
| // 4) Compression. If desired format has a specific fourcc, we need exact match; |
| // otherwise, we use preference. |
| int64 VideoCapturer::GetFormatDistance(const VideoFormat& desired, |
| const VideoFormat& supported) { |
| int64 distance = kMaxDistance; |
| |
| // Check fourcc. |
| uint32 supported_fourcc = CanonicalFourCC(supported.fourcc); |
| int64 delta_fourcc = kMaxDistance; |
| if (FOURCC_ANY == desired.fourcc) { |
| // Any fourcc is OK for the desired. Use preference to find best fourcc. |
| std::vector<uint32> preferred_fourccs; |
| if (!GetPreferredFourccs(&preferred_fourccs)) { |
| return distance; |
| } |
| |
| for (size_t i = 0; i < preferred_fourccs.size(); ++i) { |
| if (supported_fourcc == CanonicalFourCC(preferred_fourccs[i])) { |
| delta_fourcc = i; |
| break; |
| } |
| } |
| } else if (supported_fourcc == CanonicalFourCC(desired.fourcc)) { |
| delta_fourcc = 0; // Need exact match. |
| } |
| |
| if (kMaxDistance == delta_fourcc) { |
| // Failed to match fourcc. |
| return distance; |
| } |
| |
| // Check resolution and fps. |
| int desired_width = desired.width; |
| int desired_height = desired.height; |
| #ifdef OSX |
| // QVGA on OSX is not well supported. For 16x10, if 320x240 is used, it has |
| // 15x11 pixel aspect ratio on logitech B910/C260 and others. ComputeCrop |
| // in mediaengine does not crop, so we keep 320x240, which magiccam on Mac |
| // can not display. Some other viewers can display 320x240, but do not |
| // support pixel aspect ratio and appear distorted. |
| // This code below bumps the preferred resolution to VGA, maintaining aspect |
| // ratio. ie 320x200 -> 640x400. VGA on logitech and most cameras is 1x1 |
| // pixel aspect ratio. The camera will capture 640x480, ComputeCrop will |
| // crop to 640x400, and the adapter will scale down to QVGA due to JUP view |
| // request. |
| static const int kMinWidth = 640; |
| if (desired_width > 0 && desired_width < kMinWidth) { |
| int new_desired_height = desired_height * kMinWidth / desired_width; |
| LOG(LS_VERBOSE) << " Changed desired from " |
| << desired_width << "x" << desired_height |
| << " To " |
| << kMinWidth << "x" << new_desired_height; |
| desired_width = kMinWidth; |
| desired_height = new_desired_height; |
| } |
| #endif |
| int64 delta_w = supported.width - desired_width; |
| int64 supported_fps = VideoFormat::IntervalToFps(supported.interval); |
| int64 delta_fps = supported_fps - |
| VideoFormat::IntervalToFps(desired.interval); |
| // Check height of supported height compared to height we would like it to be. |
| int64 aspect_h = desired_width ? |
| supported.width * desired_height / desired_width : desired_height; |
| int64 delta_h = supported.height - aspect_h; |
| |
| distance = 0; |
| // Set high penalty if the supported format is lower than the desired format. |
| // 3x means we would prefer down to down to 3/4, than up to double. |
| // But we'd prefer up to double than down to 1/2. This is conservative, |
| // strongly avoiding going down in resolution, similar to |
| // the old method, but not completely ruling it out in extreme situations. |
| // It also ignores framerate, which is often very low at high resolutions. |
| // TODO: Improve logic to use weighted factors. |
| static const int kDownPenalty = -3; |
| if (delta_w < 0) { |
| delta_w = delta_w * kDownPenalty; |
| } |
| if (delta_h < 0) { |
| delta_h = delta_h * kDownPenalty; |
| } |
| if (delta_fps < 0) { |
| // For same resolution, prefer higher framerate but accept lower. |
| // Otherwise prefer higher resolution. |
| delta_fps = -delta_fps; |
| if (supported_fps < kMinDesirableFps) { |
| distance |= static_cast<int64>(1) << 62; |
| } else { |
| distance |= static_cast<int64>(1) << 15; |
| } |
| } |
| |
| // 12 bits for width and height and 8 bits for fps and fourcc. |
| distance |= (delta_w << 28) | (delta_h << 16) | |
| (delta_fps << 8) | delta_fourcc; |
| |
| return distance; |
| } |
| |
| bool VideoCapturer::Includes16x9HD(const std::vector<VideoFormat>& formats) { |
| std::vector<VideoFormat>::const_iterator i; |
| for (i = formats.begin(); i != formats.end(); ++i) { |
| if ((i->height >= 720) && (i->width * 9 == i->height * 16)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace cricket |