1 // libjingle
2 // Copyright 2010 Google Inc.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //  1. Redistributions of source code must retain the above copyright notice,
8 //     this list of conditions and the following disclaimer.
9 //  2. Redistributions in binary form must reproduce the above copyright notice,
10 //     this list of conditions and the following disclaimer in the documentation
11 //     and/or other materials provided with the distribution.
12 //  3. The name of the author may not be used to endorse or promote products
13 //     derived from this software without specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 //
26 // Implementartion file of class VideoCapturer.
27 
28 #include "talk/session/phone/videocapturer.h"
29 
30 #include <algorithm>
31 
32 #include "talk/base/logging.h"
33 
34 namespace cricket {
35 
36 static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63);
37 static const int64  kMinDesirableFps = static_cast<int64>(14);
38 
39 /////////////////////////////////////////////////////////////////////
40 // Implementation of struct CapturedFrame
41 /////////////////////////////////////////////////////////////////////
CapturedFrame()42 CapturedFrame::CapturedFrame()
43     : width(0),
44       height(0),
45       fourcc(0),
46       pixel_width(0),
47       pixel_height(0),
48       elapsed_time(0),
49       time_stamp(0),
50       data_size(0),
51       rotation(0),
52       data(NULL) {
53 }
54 
55 // TODO: Remove this function once lmimediaengine stops using it.
GetDataSize(uint32 * size) const56 bool CapturedFrame::GetDataSize(uint32* size) const {
57   if (!size || data_size == CapturedFrame::kUnknownDataSize) {
58     return false;
59   }
60   *size = data_size;
61   return true;
62 }
63 
64 /////////////////////////////////////////////////////////////////////
65 // Implementation of class VideoCapturer
66 /////////////////////////////////////////////////////////////////////
SetSupportedFormats(const std::vector<VideoFormat> & formats)67 void VideoCapturer::SetSupportedFormats(
68     const std::vector<VideoFormat>& formats) {
69   if (!supported_formats_.get()) {
70     supported_formats_.reset(new std::vector<VideoFormat>);
71   }
72   *(supported_formats_.get()) = formats;
73 }
74 
GetBestCaptureFormat(const VideoFormat & desired,VideoFormat * best_format)75 bool VideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
76                                          VideoFormat* best_format) {
77   if (!supported_formats_.get()) {
78     return false;
79   }
80 
81   VideoFormat format = desired;
82   // If the application requests 16x9 and the camera does not support 16x9 HD
83   // or the application requests 16x10, change the request to 4x3. Otherwise,
84   // keep the request.
85   if (format.width * 9 == format.height * 16 &&
86       !Includes16x9HD(*supported_formats_.get())) {
87     format.height = format.width * 3 / 4;
88   } else if (format.width * 10 == format.height * 16) {
89     format.height = format.width * 3 / 4;
90   }
91   LOG(LS_INFO) << " Capture Desired " << desired.ToString()
92                << " Capture Requested " << format.ToString();
93   int64 best_distance = kMaxDistance;
94   std::vector<VideoFormat>::const_iterator best = supported_formats_->end();
95   std::vector<VideoFormat>::const_iterator i;
96   for (i = supported_formats_->begin(); i != supported_formats_->end(); ++i) {
97     int64 distance = GetFormatDistance(format, *i);
98     // TODO: Reduce to LS_VERBOSE if/when camera capture is
99     // relatively bug free.
100     LOG(LS_INFO) << " Supported " << i->ToString()
101                  << " distance " << distance;
102     if (distance < best_distance) {
103       best_distance = distance;
104       best = i;
105     }
106   }
107   if (supported_formats_->end() == best) {
108     LOG(LS_ERROR) << " No acceptable camera format found";
109     return false;
110   }
111 
112   if (best_format) {
113     best_format->width = best->width;
114     best_format->height = best->height;
115     best_format->fourcc = best->fourcc;
116     best_format->interval = talk_base::_max(format.interval, best->interval);
117     LOG(LS_INFO) << " Best " << best_format->ToString()
118                  << " distance " << best_distance;
119   }
120   return true;
121 }
122 
123 // Get the distance between the supported and desired formats.
124 // Prioritization is done according to this algorithm:
125 // 1) Width closeness. If not same, we prefer wider.
126 // 2) Height closeness. If not same, we prefer higher.
127 // 3) Framerate closeness. If not same, we prefer faster.
128 // 4) Compression. If desired format has a specific fourcc, we need exact match;
129 //                otherwise, we use preference.
GetFormatDistance(const VideoFormat & desired,const VideoFormat & supported)130 int64 VideoCapturer::GetFormatDistance(const VideoFormat& desired,
131                                        const VideoFormat& supported) {
132   int64 distance = kMaxDistance;
133 
134   // Check fourcc.
135   uint32 supported_fourcc = CanonicalFourCC(supported.fourcc);
136   int64 delta_fourcc = kMaxDistance;
137   if (FOURCC_ANY == desired.fourcc) {
138     // Any fourcc is OK for the desired. Use preference to find best fourcc.
139     std::vector<uint32> preferred_fourccs;
140     if (!GetPreferredFourccs(&preferred_fourccs)) {
141       return distance;
142     }
143 
144     for (size_t i = 0; i < preferred_fourccs.size(); ++i) {
145       if (supported_fourcc == CanonicalFourCC(preferred_fourccs[i])) {
146         delta_fourcc = i;
147         break;
148       }
149     }
150   } else if (supported_fourcc == CanonicalFourCC(desired.fourcc)) {
151     delta_fourcc = 0;  // Need exact match.
152   }
153 
154   if (kMaxDistance == delta_fourcc) {
155     // Failed to match fourcc.
156     return distance;
157   }
158 
159   // Check resolution and fps.
160   int desired_width = desired.width;
161   int desired_height = desired.height;
162 #ifdef OSX
163   // QVGA on OSX is not well supported.  For 16x10, if 320x240 is used, it has
164   // 15x11 pixel aspect ratio on logitech B910/C260 and others.  ComputeCrop
165   // in mediaengine does not crop, so we keep 320x240, which magiccam on Mac
166   // can not display.  Some other viewers can display 320x240, but do not
167   // support pixel aspect ratio and appear distorted.
168   // This code below bumps the preferred resolution to VGA, maintaining aspect
169   // ratio. ie 320x200 -> 640x400.  VGA on logitech and most cameras is 1x1
170   // pixel aspect ratio.  The camera will capture 640x480, ComputeCrop will
171   // crop to 640x400, and the adapter will scale down to QVGA due to JUP view
172   // request.
173   static const int kMinWidth = 640;
174   if (desired_width > 0 && desired_width < kMinWidth) {
175     int new_desired_height = desired_height * kMinWidth / desired_width;
176     LOG(LS_VERBOSE) << " Changed desired from "
177                     << desired_width << "x" << desired_height
178                     << " To "
179                     << kMinWidth << "x" << new_desired_height;
180     desired_width = kMinWidth;
181     desired_height = new_desired_height;
182   }
183 #endif
184   int64 delta_w = supported.width - desired_width;
185   int64 supported_fps = VideoFormat::IntervalToFps(supported.interval);
186   int64 delta_fps = supported_fps -
187       VideoFormat::IntervalToFps(desired.interval);
188   // Check height of supported height compared to height we would like it to be.
189   int64 aspect_h = desired_width ?
190       supported.width * desired_height / desired_width : desired_height;
191   int64 delta_h = supported.height - aspect_h;
192 
193   distance = 0;
194   // Set high penalty if the supported format is lower than the desired format.
195   // 3x means we would prefer down to down to 3/4, than up to double.
196   // But we'd prefer up to double than down to 1/2.  This is conservative,
197   // strongly avoiding going down in resolution, similar to
198   // the old method, but not completely ruling it out in extreme situations.
199   // It also ignores framerate, which is often very low at high resolutions.
200   // TODO: Improve logic to use weighted factors.
201   static const int kDownPenalty = -3;
202   if (delta_w < 0) {
203     delta_w = delta_w * kDownPenalty;
204   }
205   if (delta_h < 0) {
206     delta_h = delta_h * kDownPenalty;
207   }
208   if (delta_fps < 0) {
209     // For same resolution, prefer higher framerate but accept lower.
210     // Otherwise prefer higher resolution.
211     delta_fps = -delta_fps;
212     if (supported_fps < kMinDesirableFps) {
213       distance |= static_cast<int64>(1) << 62;
214     } else {
215       distance |= static_cast<int64>(1) << 15;
216     }
217   }
218 
219   // 12 bits for width and height and 8 bits for fps and fourcc.
220   distance |= (delta_w << 28) | (delta_h << 16) |
221       (delta_fps << 8) | delta_fourcc;
222 
223   return distance;
224 }
225 
Includes16x9HD(const std::vector<VideoFormat> & formats)226 bool VideoCapturer::Includes16x9HD(const std::vector<VideoFormat>& formats) {
227   std::vector<VideoFormat>::const_iterator i;
228   for (i = formats.begin(); i != formats.end(); ++i) {
229     if ((i->height >= 720) && (i->width * 9 == i->height * 16)) {
230       return true;
231     }
232   }
233   return false;
234 }
235 
236 }  // namespace cricket
237