Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/renderer_host/media/video_capture_device_client.h" | 5 #include "content/browser/renderer_host/media/video_capture_device_client.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/strings/stringprintf.h" | 8 #include "base/strings/stringprintf.h" |
| 9 #include "base/trace_event/trace_event.h" | 9 #include "base/trace_event/trace_event.h" |
| 10 #include "content/browser/gpu/browser_gpu_channel_host_factory.h" | |
| 10 #include "content/browser/renderer_host/media/video_capture_buffer_pool.h" | 11 #include "content/browser/renderer_host/media/video_capture_buffer_pool.h" |
| 11 #include "content/browser/renderer_host/media/video_capture_controller.h" | 12 #include "content/browser/renderer_host/media/video_capture_controller.h" |
| 13 #include "content/common/gpu/client/gpu_jpeg_decode_accelerator_host.h" | |
| 12 #include "content/public/browser/browser_thread.h" | 14 #include "content/public/browser/browser_thread.h" |
| 13 #include "media/base/bind_to_current_loop.h" | 15 #include "media/base/bind_to_current_loop.h" |
| 14 #include "media/base/video_capture_types.h" | 16 #include "media/base/video_capture_types.h" |
| 15 #include "media/base/video_frame.h" | 17 #include "media/base/video_frame.h" |
| 16 #include "third_party/libyuv/include/libyuv.h" | 18 #include "third_party/libyuv/include/libyuv.h" |
| 17 | 19 |
| 18 using media::VideoCaptureFormat; | 20 using media::VideoCaptureFormat; |
| 19 using media::VideoFrame; | 21 using media::VideoFrame; |
| 20 | 22 |
| 21 namespace content { | 23 namespace content { |
| 22 | 24 |
| 25 struct CapturedData { | |
| 26 uint8* data; | |
| 27 int length; | |
| 28 media::VideoCaptureFormat frame_format; | |
| 29 int rotation; | |
| 30 base::TimeTicks timestamp; | |
| 31 }; | |
| 32 | |
| 33 // This class may be accessed by device thread and IO thread. The access is | |
| 34 // exclusive: device thread can only access members if |decoding_| is false. | |
| 35 // IO thread can only access if |decoding_| is true. | |
| 36 class JpegDecodeData { | |
| 37 public: | |
| 38 typedef media::VideoCaptureDevice::Client::Buffer ClientBuffer; | |
| 39 JpegDecodeData() | |
| 40 : decoding_(false), | |
| 41 next_bitstream_buffer_id_(0), | |
| 42 in_buffer_(media::JpegDecodeAccelerator::kInvalidBitstreamBufferId, | |
| 43 base::SharedMemory::NULLHandle(), | |
| 44 0) {} | |
| 45 | |
| 46 bool decoding() { | |
| 47 base::AutoLock lock(lock_); | |
| 48 return decoding_; | |
| 49 } | |
| 50 void set_decoding(bool decoding) { | |
| 51 base::AutoLock lock(lock_); | |
| 52 DCHECK(decoding_ != decoding); | |
| 53 decoding_ = decoding; | |
| 54 } | |
| 55 | |
| 56 media::BitstreamBuffer in_buffer() { return in_buffer_; } | |
| 57 const CapturedData& captured_data() { return captured_data_; } | |
| 58 void set_captured_data(const CapturedData& data) { | |
| 59 DCHECK(!decoding()); | |
| 60 captured_data_ = data; | |
| 61 } | |
| 62 scoped_refptr<ClientBuffer> out_buffer() { | |
| 63 DCHECK(decoding()); | |
| 64 return out_buffer_; | |
| 65 } | |
| 66 scoped_refptr<media::VideoFrame> out_frame() { return out_frame_; } | |
| 67 | |
| 68 // Adapt |captured_data| and |out_buffer| to BitstreamBuffer and | |
| 69 // VideoFrame, which JDA expects. | |
| 70 bool PrepareDecode(const CapturedData& captured_data, | |
| 71 const scoped_refptr<ClientBuffer> out_buffer); | |
| 72 void DecodeDone(); | |
| 73 | |
| 74 private: | |
| 75 base::Lock lock_; | |
| 76 bool decoding_; // protected by lock_; | |
| 77 | |
| 78 CapturedData captured_data_; | |
| 79 int32 next_bitstream_buffer_id_; | |
| 80 | |
| 81 scoped_ptr<base::SharedMemory> in_shared_memory_; | |
| 82 // |in_buffer_| is backed by |in_shared_memory_|. | |
| 83 media::BitstreamBuffer in_buffer_; | |
| 84 | |
| 85 scoped_refptr<ClientBuffer> out_buffer_; | |
| 86 // |out_frame_| is backed by |out_buffer_|. | |
| 87 scoped_refptr<media::VideoFrame> out_frame_; | |
| 88 | |
| 89 DISALLOW_COPY_AND_ASSIGN(JpegDecodeData); | |
| 90 }; | |
| 91 | |
| 92 bool JpegDecodeData::PrepareDecode( | |
| 93 const CapturedData& captured_data, | |
| 94 const scoped_refptr<ClientBuffer> out_buffer) { | |
| 95 DVLOG(3) << __func__; | |
| 96 DCHECK(!decoding()); | |
| 97 | |
| 98 captured_data_ = captured_data; | |
| 99 | |
| 100 // Enlarge input buffer if necessary. | |
| 101 size_t in_buffer_size = captured_data.length; | |
| 102 if (!in_shared_memory_.get() || | |
| 103 in_buffer_size > in_shared_memory_->mapped_size()) { | |
| 104 in_shared_memory_.reset(new base::SharedMemory); | |
| 105 | |
| 106 if (!in_shared_memory_->CreateAnonymous(in_buffer_size)) { | |
| 107 DLOG(ERROR) << "CreateAnonymous failed"; | |
| 108 return false; | |
| 109 } | |
| 110 | |
| 111 if (!in_shared_memory_->Map(in_buffer_size)) { | |
| 112 DLOG(ERROR) << "Map failed"; | |
| 113 return false; | |
| 114 } | |
| 115 | |
| 116 captured_data_.data = reinterpret_cast<uint8*>(in_shared_memory_->memory()); | |
| 117 } | |
| 118 | |
| 119 in_buffer_ = media::BitstreamBuffer( | |
| 120 next_bitstream_buffer_id_, in_shared_memory_->handle(), in_buffer_size); | |
| 121 // Mask against 30 bits, to avoid (undefined) wraparound on signed integer. | |
| 122 next_bitstream_buffer_id_ = (next_bitstream_buffer_id_ + 1) & 0x3FFFFFFF; | |
| 123 | |
| 124 gfx::Size dimensions = captured_data.frame_format.frame_size; | |
| 125 | |
| 126 scoped_refptr<media::VideoFrame> out_frame = | |
| 127 media::VideoFrame::WrapExternalPackedMemory( | |
| 128 media::VideoFrame::I420, | |
| 129 dimensions, | |
| 130 gfx::Rect(dimensions), | |
| 131 dimensions, | |
| 132 reinterpret_cast<uint8*>(out_buffer->data()), | |
| 133 out_buffer->size(), | |
| 134 out_buffer->handle(), | |
| 135 0, | |
| 136 base::TimeDelta(), | |
| 137 base::Closure()); | |
| 138 DCHECK(out_frame.get()); | |
| 139 | |
| 140 out_frame->metadata()->SetDouble(media::VideoFrameMetadata::FRAME_RATE, | |
| 141 captured_data.frame_format.frame_rate); | |
| 142 | |
| 143 out_frame_ = out_frame; | |
| 144 out_buffer_ = out_buffer; | |
| 145 | |
| 146 memcpy(in_shared_memory_->memory(), captured_data.data, captured_data.length); | |
| 147 | |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 void JpegDecodeData::DecodeDone() { | |
| 152 base::AutoLock lock(lock_); | |
| 153 DCHECK(decoding_); | |
| 154 out_frame_ = nullptr; | |
| 155 out_buffer_ = nullptr; | |
| 156 decoding_ = false; | |
| 157 } | |
| 158 | |
| 23 // Class combining a Client::Buffer interface implementation and a pool buffer | 159 // Class combining a Client::Buffer interface implementation and a pool buffer |
| 24 // implementation to guarantee proper cleanup on destruction on our side. | 160 // implementation to guarantee proper cleanup on destruction on our side. |
| 25 class AutoReleaseBuffer : public media::VideoCaptureDevice::Client::Buffer { | 161 class AutoReleaseBuffer : public media::VideoCaptureDevice::Client::Buffer { |
| 26 public: | 162 public: |
| 27 AutoReleaseBuffer(const scoped_refptr<VideoCaptureBufferPool>& pool, | 163 AutoReleaseBuffer(const scoped_refptr<VideoCaptureBufferPool>& pool, |
| 28 int buffer_id, | 164 int buffer_id, |
| 29 void* data, | 165 void* data, |
| 30 size_t size) | 166 size_t size, |
| 31 : pool_(pool), | 167 base::SharedMemoryHandle handle) |
| 32 id_(buffer_id), | 168 : pool_(pool), id_(buffer_id), data_(data), size_(size), handle_(handle) { |
| 33 data_(data), | |
| 34 size_(size) { | |
| 35 DCHECK(pool_.get()); | 169 DCHECK(pool_.get()); |
| 36 } | 170 } |
| 37 int id() const override { return id_; } | 171 int id() const override { return id_; } |
| 38 void* data() const override { return data_; } | 172 void* data() const override { return data_; } |
| 39 size_t size() const override { return size_; } | 173 size_t size() const override { return size_; } |
| 174 base::SharedMemoryHandle handle() const override { return handle_; } | |
| 40 | 175 |
| 41 private: | 176 private: |
| 42 ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); } | 177 ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); } |
| 43 | 178 |
| 44 const scoped_refptr<VideoCaptureBufferPool> pool_; | 179 const scoped_refptr<VideoCaptureBufferPool> pool_; |
| 45 const int id_; | 180 const int id_; |
| 46 void* const data_; | 181 void* const data_; |
| 47 const size_t size_; | 182 const size_t size_; |
| 183 const base::SharedMemoryHandle handle_; | |
| 48 }; | 184 }; |
| 49 | 185 |
| 50 VideoCaptureDeviceClient::VideoCaptureDeviceClient( | 186 VideoCaptureDeviceClient::VideoCaptureDeviceClient( |
| 51 const base::WeakPtr<VideoCaptureController>& controller, | 187 const base::WeakPtr<VideoCaptureController>& controller, |
| 52 const scoped_refptr<VideoCaptureBufferPool>& buffer_pool) | 188 const scoped_refptr<VideoCaptureBufferPool>& buffer_pool) |
| 53 : controller_(controller), | 189 : controller_(controller), |
| 190 jpeg_failed_(false), | |
| 54 buffer_pool_(buffer_pool), | 191 buffer_pool_(buffer_pool), |
| 55 last_captured_pixel_format_(media::PIXEL_FORMAT_UNKNOWN) {} | 192 last_captured_pixel_format_(media::PIXEL_FORMAT_UNKNOWN) { |
| 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 194 } | |
| 56 | 195 |
| 57 VideoCaptureDeviceClient::~VideoCaptureDeviceClient() {} | 196 VideoCaptureDeviceClient::~VideoCaptureDeviceClient() { |
| 197 } | |
| 198 | |
| 199 bool VideoCaptureDeviceClient::InitializeJpegDecoder() { | |
| 200 // called from device thread | |
| 201 DVLOG(3) << __func__; | |
| 202 GpuChannelHost* const host = | |
| 203 BrowserGpuChannelHostFactory::instance()->GetGpuChannel(); | |
| 204 jpeg_decoder_ = host->CreateJpegDecoder(); | |
| 205 if (!jpeg_decoder_->Initialize(this)) { | |
| 206 jpeg_decoder_.reset(); | |
| 207 return false; | |
| 208 } | |
| 209 return true; | |
| 210 } | |
| 211 | |
| 212 void VideoCaptureDeviceClient::VideoFrameReady(int32_t bitstream_buffer_id) { | |
| 213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 214 DVLOG(3) << __func__; | |
| 215 | |
| 216 // Verify parameters from GPU thread. | |
|
wuchengli
2015/04/15 07:11:57
s/thread/process/
kcwu
2015/04/16 14:38:27
Done.
| |
| 217 if (!jpeg_decode_data_ || !jpeg_decode_data_->decoding()) { | |
| 218 DLOG(ERROR) << "got VideoFrameReady call but not decoding"; | |
| 219 return; | |
| 220 } | |
| 221 if (bitstream_buffer_id != jpeg_decode_data_->in_buffer().id()) { | |
| 222 DLOG(ERROR) << "bitstream_buffer_id mismatched, expected " | |
| 223 << jpeg_decode_data_->in_buffer().id() << ", but got " | |
| 224 << bitstream_buffer_id; | |
| 225 return; | |
| 226 } | |
| 227 | |
| 228 const auto& captured_data = jpeg_decode_data_->captured_data(); | |
| 229 BrowserThread::PostTask( | |
| 230 BrowserThread::IO, FROM_HERE, | |
| 231 base::Bind( | |
| 232 &VideoCaptureController::DoIncomingCapturedVideoFrameOnIOThread, | |
| 233 controller_, jpeg_decode_data_->out_buffer(), | |
| 234 jpeg_decode_data_->out_frame(), captured_data.timestamp)); | |
| 235 | |
| 236 jpeg_decode_data_->DecodeDone(); | |
| 237 } | |
| 238 | |
| 239 void VideoCaptureDeviceClient::NotifyError( | |
| 240 int32_t bitstream_buffer_id, | |
| 241 media::JpegDecodeAccelerator::Error error) { | |
| 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
| 243 DVLOG(3) << __func__; | |
| 244 | |
| 245 // Verify parameters from GPU thread. | |
| 246 if (!jpeg_decode_data_->decoding()) { | |
| 247 DLOG(ERROR) << "got VideoFrameReady call but not decoding"; | |
| 248 return; | |
| 249 } | |
| 250 if (bitstream_buffer_id != | |
| 251 media::JpegDecodeAccelerator::kInvalidBitstreamBufferId && | |
| 252 bitstream_buffer_id != jpeg_decode_data_->in_buffer().id()) { | |
| 253 DLOG(ERROR) << "invalid bitstream_buffer_id " << bitstream_buffer_id; | |
| 254 return; | |
| 255 } | |
| 256 | |
| 257 OnIncomingCapturedData2(jpeg_decode_data_->captured_data()); | |
| 258 | |
| 259 jpeg_decode_data_->DecodeDone(); | |
| 260 } | |
| 58 | 261 |
| 59 void VideoCaptureDeviceClient::OnIncomingCapturedData( | 262 void VideoCaptureDeviceClient::OnIncomingCapturedData( |
| 60 const uint8* data, | 263 const uint8* data, |
| 61 int length, | 264 int length, |
| 62 const VideoCaptureFormat& frame_format, | 265 const VideoCaptureFormat& frame_format, |
| 63 int rotation, | 266 int rotation, |
| 64 const base::TimeTicks& timestamp) { | 267 const base::TimeTicks& timestamp) { |
| 65 TRACE_EVENT0("video", "VideoCaptureDeviceClient::OnIncomingCapturedData"); | 268 TRACE_EVENT0("video", "VideoCaptureDeviceClient::OnIncomingCapturedData"); |
| 269 DVLOG(3) << __func__; | |
| 270 | |
| 271 CapturedData captured_data; | |
| 272 captured_data.data = const_cast<uint8*>(data); | |
| 273 captured_data.length = length; | |
| 274 captured_data.frame_format = frame_format; | |
| 275 captured_data.rotation = rotation; | |
| 276 captured_data.timestamp = timestamp; | |
| 66 | 277 |
| 67 if (last_captured_pixel_format_ != frame_format.pixel_format) { | 278 if (last_captured_pixel_format_ != frame_format.pixel_format) { |
| 68 OnLog("Pixel format: " + media::VideoCaptureFormat::PixelFormatToString( | 279 OnLog("Pixel format: " + media::VideoCaptureFormat::PixelFormatToString( |
| 69 frame_format.pixel_format)); | 280 frame_format.pixel_format)); |
| 70 last_captured_pixel_format_ = frame_format.pixel_format; | 281 last_captured_pixel_format_ = frame_format.pixel_format; |
| 71 } | 282 } |
| 72 | 283 |
| 73 if (!frame_format.IsValid()) | 284 if (!frame_format.IsValid()) |
| 74 return; | 285 return; |
| 75 | 286 |
| 287 const gfx::Size size = frame_format.frame_size; | |
| 288 if (rotation != 0 || frame_format.pixel_format != media::PIXEL_FORMAT_MJPEG || | |
| 289 size.width() % 2 != 0 || size.height() % 2 != 0 || jpeg_failed_) { | |
| 290 OnIncomingCapturedData2(captured_data); | |
| 291 return; | |
| 292 } | |
| 293 | |
| 294 if (!jpeg_decoder_) { | |
| 295 if (!InitializeJpegDecoder()) { | |
| 296 // TODO(kcwu): fallback to software decode | |
| 297 jpeg_failed_ = true; | |
| 298 return; | |
| 299 } | |
| 300 jpeg_decode_data_.reset(new JpegDecodeData); | |
| 301 } | |
| 302 | |
| 303 gfx::Size dimensions = frame_format.frame_size; | |
| 304 scoped_refptr<Buffer> out_buffer = | |
| 305 ReserveOutputBuffer(media::PIXEL_FORMAT_I420, dimensions); | |
| 306 if (!out_buffer.get()) { | |
| 307 DVLOG(1) << "Drop captured frame. No available buffers" | |
| 308 << " (all buffers are still queued to display)"; | |
| 309 return; | |
| 310 } | |
| 311 | |
| 312 if (jpeg_decode_data_->decoding()) { | |
| 313 DVLOG(1) << "Drop captured frame. Previous jpeg frame is still decoding"; | |
| 314 return; | |
| 315 } | |
| 316 | |
| 317 if (!jpeg_decode_data_->PrepareDecode(captured_data, out_buffer)) { | |
| 318 // TODO(kcwu): fallback to software decode | |
| 319 return; | |
| 320 } | |
| 321 | |
| 322 jpeg_decode_data_->set_decoding(true); | |
| 323 jpeg_decoder_->Decode(jpeg_decode_data_->in_buffer(), | |
| 324 jpeg_decode_data_->out_frame()); | |
| 325 } | |
| 326 | |
| 327 // TODO(kcwu): need better name | |
| 328 void VideoCaptureDeviceClient::OnIncomingCapturedData2( | |
| 329 const CapturedData& captured_data) { | |
| 330 DVLOG(3) << __func__; | |
| 331 | |
| 332 const uint8* data = captured_data.data; | |
| 333 int length = captured_data.length; | |
| 334 const VideoCaptureFormat& frame_format = captured_data.frame_format; | |
| 335 int rotation = captured_data.rotation; | |
| 336 base::TimeTicks timestamp = captured_data.timestamp; | |
| 337 | |
| 338 if (last_captured_pixel_format_ != frame_format.pixel_format) { | |
| 339 OnLog("Pixel format: " + media::VideoCaptureFormat::PixelFormatToString( | |
| 340 frame_format.pixel_format)); | |
| 341 last_captured_pixel_format_ = frame_format.pixel_format; | |
| 342 } | |
| 343 | |
| 76 // |chopped_{width,height} and |new_unrotated_{width,height}| are the lowest | 344 // |chopped_{width,height} and |new_unrotated_{width,height}| are the lowest |
| 77 // bit decomposition of {width, height}, grabbing the odd and even parts. | 345 // bit decomposition of {width, height}, grabbing the odd and even parts. |
| 78 const int chopped_width = frame_format.frame_size.width() & 1; | 346 const int chopped_width = frame_format.frame_size.width() & 1; |
| 79 const int chopped_height = frame_format.frame_size.height() & 1; | 347 const int chopped_height = frame_format.frame_size.height() & 1; |
| 80 const int new_unrotated_width = frame_format.frame_size.width() & ~1; | 348 const int new_unrotated_width = frame_format.frame_size.width() & ~1; |
| 81 const int new_unrotated_height = frame_format.frame_size.height() & ~1; | 349 const int new_unrotated_height = frame_format.frame_size.height() & ~1; |
| 82 | 350 |
| 83 int destination_width = new_unrotated_width; | 351 int destination_width = new_unrotated_width; |
| 84 int destination_height = new_unrotated_height; | 352 int destination_height = new_unrotated_height; |
| 85 if (rotation == 90 || rotation == 270) { | 353 if (rotation == 90 || rotation == 270) { |
| (...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 309 DCHECK_GT(dimensions.width(), 0); | 577 DCHECK_GT(dimensions.width(), 0); |
| 310 DCHECK_GT(dimensions.height(), 0); | 578 DCHECK_GT(dimensions.height(), 0); |
| 311 | 579 |
| 312 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; | 580 int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; |
| 313 const int buffer_id = | 581 const int buffer_id = |
| 314 buffer_pool_->ReserveForProducer(format, dimensions, &buffer_id_to_drop); | 582 buffer_pool_->ReserveForProducer(format, dimensions, &buffer_id_to_drop); |
| 315 if (buffer_id == VideoCaptureBufferPool::kInvalidId) | 583 if (buffer_id == VideoCaptureBufferPool::kInvalidId) |
| 316 return NULL; | 584 return NULL; |
| 317 void* data; | 585 void* data; |
| 318 size_t size; | 586 size_t size; |
| 319 buffer_pool_->GetBufferInfo(buffer_id, &data, &size); | 587 base::SharedMemoryHandle handle; |
| 588 buffer_pool_->GetBufferInfo(buffer_id, &data, &size, &handle); | |
| 320 | 589 |
| 321 scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer( | 590 scoped_refptr<media::VideoCaptureDevice::Client::Buffer> output_buffer( |
| 322 new AutoReleaseBuffer(buffer_pool_, buffer_id, data, size)); | 591 new AutoReleaseBuffer(buffer_pool_, buffer_id, data, size, handle)); |
| 323 | 592 |
| 324 if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) { | 593 if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) { |
| 325 BrowserThread::PostTask(BrowserThread::IO, | 594 BrowserThread::PostTask(BrowserThread::IO, |
| 326 FROM_HERE, | 595 FROM_HERE, |
| 327 base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread, | 596 base::Bind(&VideoCaptureController::DoBufferDestroyedOnIOThread, |
| 328 controller_, buffer_id_to_drop)); | 597 controller_, buffer_id_to_drop)); |
| 329 } | 598 } |
| 330 | 599 |
| 331 return output_buffer; | 600 return output_buffer; |
| 332 } | 601 } |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 362 } | 631 } |
| 363 | 632 |
| 364 void VideoCaptureDeviceClient::OnLog( | 633 void VideoCaptureDeviceClient::OnLog( |
| 365 const std::string& message) { | 634 const std::string& message) { |
| 366 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | 635 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
| 367 base::Bind(&VideoCaptureController::DoLogOnIOThread, | 636 base::Bind(&VideoCaptureController::DoLogOnIOThread, |
| 368 controller_, message)); | 637 controller_, message)); |
| 369 } | 638 } |
| 370 | 639 |
| 371 } // namespace content | 640 } // namespace content |
| OLD | NEW |