1/*
2 * libjingle
3 * Copyright 2011 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/media/webrtc/webrtcvideoframe.h"
29
30#include "libyuv/convert.h"
31#include "libyuv/convert_from.h"
32#include "libyuv/planar_functions.h"
33#include "talk/media/base/videocapturer.h"
34#include "talk/media/base/videocommon.h"
35#include "webrtc/base/logging.h"
36
37namespace cricket {
38
39// Class that wraps ownerhip semantics of a buffer passed to it.
40// * Buffers passed using Attach() become owned by this FrameBuffer and will be
41//   destroyed on FrameBuffer destruction.
42// * Buffers passed using Alias() are not owned and will not be destroyed on
43//   FrameBuffer destruction,  The buffer then must outlive the FrameBuffer.
44class WebRtcVideoFrame::FrameBuffer {
45 public:
46  FrameBuffer();
47  explicit FrameBuffer(size_t length);
48  ~FrameBuffer();
49
50  void Attach(uint8* data, size_t length);
51  void Alias(uint8* data, size_t length);
52  uint8* data();
53  size_t length() const;
54
55  webrtc::VideoFrame* frame();
56  const webrtc::VideoFrame* frame() const;
57
58 private:
59  rtc::scoped_ptr<uint8[]> owned_data_;
60  webrtc::VideoFrame video_frame_;
61};
62
63WebRtcVideoFrame::FrameBuffer::FrameBuffer() {}
64
65WebRtcVideoFrame::FrameBuffer::FrameBuffer(size_t length) {
66  uint8* buffer = new uint8[length];
67  Attach(buffer, length);
68}
69
70WebRtcVideoFrame::FrameBuffer::~FrameBuffer() {
71  // Make sure that |video_frame_| doesn't delete the buffer, as |owned_data_|
72  // will release the buffer if this FrameBuffer owns it.
73  uint8_t* new_memory = NULL;
74  uint32_t new_length = 0;
75  uint32_t new_size = 0;
76  video_frame_.Swap(new_memory, new_length, new_size);
77}
78
79void WebRtcVideoFrame::FrameBuffer::Attach(uint8* data, size_t length) {
80  Alias(data, length);
81  owned_data_.reset(data);
82}
83
84void WebRtcVideoFrame::FrameBuffer::Alias(uint8* data, size_t length) {
85  owned_data_.reset();
86  uint8_t* new_memory = reinterpret_cast<uint8_t*>(data);
87  uint32_t new_length = static_cast<uint32_t>(length);
88  uint32_t new_size = static_cast<uint32_t>(length);
89  video_frame_.Swap(new_memory, new_length, new_size);
90}
91
92uint8* WebRtcVideoFrame::FrameBuffer::data() {
93  return video_frame_.Buffer();
94}
95
96size_t WebRtcVideoFrame::FrameBuffer::length() const {
97  return video_frame_.Length();
98}
99
100webrtc::VideoFrame* WebRtcVideoFrame::FrameBuffer::frame() {
101  return &video_frame_;
102}
103
104const webrtc::VideoFrame* WebRtcVideoFrame::FrameBuffer::frame() const {
105  return &video_frame_;
106}
107
108WebRtcVideoFrame::WebRtcVideoFrame()
109    : video_buffer_(new RefCountedBuffer()), is_black_(false) {}
110
111WebRtcVideoFrame::~WebRtcVideoFrame() {}
112
113bool WebRtcVideoFrame::Init(
114    uint32 format, int w, int h, int dw, int dh, uint8* sample,
115    size_t sample_size, size_t pixel_width, size_t pixel_height,
116    int64 elapsed_time, int64 time_stamp, int rotation) {
117  return Reset(format, w, h, dw, dh, sample, sample_size, pixel_width,
118               pixel_height, elapsed_time, time_stamp, rotation);
119}
120
121bool WebRtcVideoFrame::Init(const CapturedFrame* frame, int dw, int dh) {
122  return Reset(frame->fourcc, frame->width, frame->height, dw, dh,
123               static_cast<uint8*>(frame->data), frame->data_size,
124               frame->pixel_width, frame->pixel_height, frame->elapsed_time,
125               frame->time_stamp, frame->rotation);
126}
127
128bool WebRtcVideoFrame::Alias(const CapturedFrame* frame, int dw, int dh) {
129  if (CanonicalFourCC(frame->fourcc) != FOURCC_I420 || frame->rotation != 0 ||
130      frame->width != dw || frame->height != dh) {
131    // TODO(fbarchard): Enable aliasing of more formats.
132    return Init(frame, dw, dh);
133  } else {
134    Alias(static_cast<uint8*>(frame->data),
135          frame->data_size,
136          frame->width,
137          frame->height,
138          frame->pixel_width,
139          frame->pixel_height,
140          frame->elapsed_time,
141          frame->time_stamp,
142          frame->rotation);
143    return true;
144  }
145}
146
147bool WebRtcVideoFrame::InitToBlack(int w, int h, size_t pixel_width,
148                                   size_t pixel_height, int64 elapsed_time,
149                                   int64 time_stamp) {
150  InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
151  if (!is_black_) {
152    return SetToBlack();
153  }
154  return true;
155}
156
157void WebRtcVideoFrame::Alias(
158    uint8* buffer, size_t buffer_size, int w, int h, size_t pixel_width,
159    size_t pixel_height, int64 elapsed_time, int64 time_stamp, int rotation) {
160  rtc::scoped_refptr<RefCountedBuffer> video_buffer(
161      new RefCountedBuffer());
162  video_buffer->Alias(buffer, buffer_size);
163  Attach(video_buffer.get(), buffer_size, w, h, pixel_width, pixel_height,
164         elapsed_time, time_stamp, rotation);
165}
166
167size_t WebRtcVideoFrame::GetWidth() const { return frame()->Width(); }
168
169size_t WebRtcVideoFrame::GetHeight() const { return frame()->Height(); }
170
171const uint8* WebRtcVideoFrame::GetYPlane() const {
172  uint8_t* buffer = frame()->Buffer();
173  return buffer;
174}
175
176const uint8* WebRtcVideoFrame::GetUPlane() const {
177  uint8_t* buffer = frame()->Buffer();
178  if (buffer) {
179    buffer += (frame()->Width() * frame()->Height());
180  }
181  return buffer;
182}
183
184const uint8* WebRtcVideoFrame::GetVPlane() const {
185  uint8_t* buffer = frame()->Buffer();
186  if (buffer) {
187    int uv_size = static_cast<int>(GetChromaSize());
188    buffer += frame()->Width() * frame()->Height() + uv_size;
189  }
190  return buffer;
191}
192
193uint8* WebRtcVideoFrame::GetYPlane() {
194  uint8_t* buffer = frame()->Buffer();
195  return buffer;
196}
197
198uint8* WebRtcVideoFrame::GetUPlane() {
199  uint8_t* buffer = frame()->Buffer();
200  if (buffer) {
201    buffer += (frame()->Width() * frame()->Height());
202  }
203  return buffer;
204}
205
206uint8* WebRtcVideoFrame::GetVPlane() {
207  uint8_t* buffer = frame()->Buffer();
208  if (buffer) {
209    int uv_size = static_cast<int>(GetChromaSize());
210    buffer += frame()->Width() * frame()->Height() + uv_size;
211  }
212  return buffer;
213}
214
215VideoFrame* WebRtcVideoFrame::Copy() const {
216  uint8* old_buffer = video_buffer_->data();
217  if (!old_buffer)
218    return NULL;
219  size_t new_buffer_size = video_buffer_->length();
220
221  WebRtcVideoFrame* ret_val = new WebRtcVideoFrame();
222  ret_val->Attach(video_buffer_.get(), new_buffer_size, frame()->Width(),
223                  frame()->Height(), pixel_width_, pixel_height_, elapsed_time_,
224                  time_stamp_, rotation_);
225  return ret_val;
226}
227
228bool WebRtcVideoFrame::MakeExclusive() {
229  const size_t length = video_buffer_->length();
230  RefCountedBuffer* exclusive_buffer = new RefCountedBuffer(length);
231  memcpy(exclusive_buffer->data(), video_buffer_->data(), length);
232  Attach(exclusive_buffer, length, frame()->Width(), frame()->Height(),
233         pixel_width_, pixel_height_, elapsed_time_, time_stamp_, rotation_);
234  return true;
235}
236
237size_t WebRtcVideoFrame::CopyToBuffer(uint8* buffer, size_t size) const {
238  if (!frame()->Buffer()) {
239    return 0;
240  }
241
242  size_t needed = frame()->Length();
243  if (needed <= size) {
244    memcpy(buffer, frame()->Buffer(), needed);
245  }
246  return needed;
247}
248
249// TODO(fbarchard): Refactor into base class and share with lmi
250size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
251                                            size_t size, int stride_rgb) const {
252  if (!frame()->Buffer()) {
253    return 0;
254  }
255  size_t width = frame()->Width();
256  size_t height = frame()->Height();
257  size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height;
258  if (size < needed) {
259    LOG(LS_WARNING) << "RGB buffer is not large enough";
260    return needed;
261  }
262
263  if (libyuv::ConvertFromI420(GetYPlane(), GetYPitch(), GetUPlane(),
264                              GetUPitch(), GetVPlane(), GetVPitch(), buffer,
265                              stride_rgb,
266                              static_cast<int>(width),
267                              static_cast<int>(height),
268                              to_fourcc)) {
269    LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
270    return 0;  // 0 indicates error
271  }
272  return needed;
273}
274
275void WebRtcVideoFrame::Attach(
276    RefCountedBuffer* video_buffer, size_t buffer_size, int w, int h,
277    size_t pixel_width, size_t pixel_height, int64 elapsed_time,
278    int64 time_stamp, int rotation) {
279  if (video_buffer_.get() == video_buffer) {
280    return;
281  }
282  is_black_ = false;
283  video_buffer_ = video_buffer;
284  frame()->SetWidth(w);
285  frame()->SetHeight(h);
286  pixel_width_ = pixel_width;
287  pixel_height_ = pixel_height;
288  elapsed_time_ = elapsed_time;
289  time_stamp_ = time_stamp;
290  rotation_ = rotation;
291}
292
293webrtc::VideoFrame* WebRtcVideoFrame::frame() {
294  return video_buffer_->frame();
295}
296
297const webrtc::VideoFrame* WebRtcVideoFrame::frame() const {
298  return video_buffer_->frame();
299}
300
301bool WebRtcVideoFrame::Reset(
302    uint32 format, int w, int h, int dw, int dh, uint8* sample,
303    size_t sample_size, size_t pixel_width, size_t pixel_height,
304    int64 elapsed_time, int64 time_stamp, int rotation) {
305  if (!Validate(format, w, h, sample, sample_size)) {
306    return false;
307  }
308  // Translate aliases to standard enums (e.g., IYUV -> I420).
309  format = CanonicalFourCC(format);
310
311  // Round display width and height down to multiple of 4, to avoid webrtc
312  // size calculation error on odd sizes.
313  // TODO(Ronghua): Remove this once the webrtc allocator is fixed.
314  dw = (dw > 4) ? (dw & ~3) : dw;
315  dh = (dh > 4) ? (dh & ~3) : dh;
316
317  // Set up a new buffer.
318  // TODO(fbarchard): Support lazy allocation.
319  int new_width = dw;
320  int new_height = dh;
321  if (rotation == 90 || rotation == 270) {  // If rotated swap width, height.
322    new_width = dh;
323    new_height = dw;
324  }
325
326  size_t desired_size = SizeOf(new_width, new_height);
327  rtc::scoped_refptr<RefCountedBuffer> video_buffer(
328      new RefCountedBuffer(desired_size));
329  // Since the libyuv::ConvertToI420 will handle the rotation, so the
330  // new frame's rotation should always be 0.
331  Attach(video_buffer.get(), desired_size, new_width, new_height, pixel_width,
332         pixel_height, elapsed_time, time_stamp, 0);
333
334  int horiz_crop = ((w - dw) / 2) & ~1;
335  // ARGB on Windows has negative height.
336  // The sample's layout in memory is normal, so just correct crop.
337  int vert_crop = ((abs(h) - dh) / 2) & ~1;
338  // Conversion functions expect negative height to flip the image.
339  int idh = (h < 0) ? -dh : dh;
340  uint8* y = GetYPlane();
341  int y_stride = GetYPitch();
342  uint8* u = GetUPlane();
343  int u_stride = GetUPitch();
344  uint8* v = GetVPlane();
345  int v_stride = GetVPitch();
346  int r = libyuv::ConvertToI420(
347      sample, sample_size, y, y_stride, u, u_stride, v, v_stride, horiz_crop,
348      vert_crop, w, h, dw, idh, static_cast<libyuv::RotationMode>(rotation),
349      format);
350  if (r) {
351    LOG(LS_ERROR) << "Error parsing format: " << GetFourccName(format)
352                  << " return code : " << r;
353    return false;
354  }
355  return true;
356}
357
358VideoFrame* WebRtcVideoFrame::CreateEmptyFrame(
359    int w, int h, size_t pixel_width, size_t pixel_height, int64 elapsed_time,
360    int64 time_stamp) const {
361  WebRtcVideoFrame* frame = new WebRtcVideoFrame();
362  frame->InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time,
363                           time_stamp);
364  return frame;
365}
366
367void WebRtcVideoFrame::InitToEmptyBuffer(int w, int h, size_t pixel_width,
368                                         size_t pixel_height,
369                                         int64 elapsed_time, int64 time_stamp) {
370  size_t buffer_size = VideoFrame::SizeOf(w, h);
371  rtc::scoped_refptr<RefCountedBuffer> video_buffer(
372      new RefCountedBuffer(buffer_size));
373  Attach(video_buffer.get(), buffer_size, w, h, pixel_width, pixel_height,
374         elapsed_time, time_stamp, 0);
375}
376
377}  // namespace cricket
378