| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 <errno.h> | 5 #include <errno.h> |
| 6 #include <fcntl.h> | 6 #include <fcntl.h> |
| 7 #include <linux/videodev2.h> | 7 #include <linux/videodev2.h> |
| 8 #include <poll.h> | 8 #include <poll.h> |
| 9 #include <string.h> | 9 #include <string.h> |
| 10 #include <sys/eventfd.h> | 10 #include <sys/eventfd.h> |
| 11 #include <sys/ioctl.h> | 11 #include <sys/ioctl.h> |
| 12 #include <sys/mman.h> | 12 #include <sys/mman.h> |
| 13 | 13 |
| 14 #include "base/bind.h" | 14 #include "base/bind.h" |
| 15 #include "base/bind_helpers.h" | 15 #include "base/bind_helpers.h" |
| 16 #include "base/callback.h" | 16 #include "base/callback.h" |
| 17 #include "base/numerics/safe_conversions.h" | 17 #include "base/numerics/safe_conversions.h" |
| 18 #include "content/common/gpu/media/v4l2_image_processor.h" | |
| 19 #include "media/base/bind_to_current_loop.h" | 18 #include "media/base/bind_to_current_loop.h" |
| 19 #include "media/gpu/v4l2_image_processor.h" |
| 20 | 20 |
| 21 #define IOCTL_OR_ERROR_RETURN_VALUE(type, arg, value, type_str) \ | 21 #define IOCTL_OR_ERROR_RETURN_VALUE(type, arg, value, type_str) \ |
| 22 do { \ | 22 do { \ |
| 23 if (device_->Ioctl(type, arg) != 0) { \ | 23 if (device_->Ioctl(type, arg) != 0) { \ |
| 24 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << type_str; \ | 24 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << type_str; \ |
| 25 return value; \ | 25 return value; \ |
| 26 } \ | 26 } \ |
| 27 } while (0) | 27 } while (0) |
| 28 | 28 |
| 29 #define IOCTL_OR_ERROR_RETURN(type, arg) \ | 29 #define IOCTL_OR_ERROR_RETURN(type, arg) \ |
| 30 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, ((void)0), #type) | 30 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, ((void)0), #type) |
| 31 | 31 |
| 32 #define IOCTL_OR_ERROR_RETURN_FALSE(type, arg) \ | 32 #define IOCTL_OR_ERROR_RETURN_FALSE(type, arg) \ |
| 33 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, false, #type) | 33 IOCTL_OR_ERROR_RETURN_VALUE(type, arg, false, #type) |
| 34 | 34 |
| 35 #define IOCTL_OR_LOG_ERROR(type, arg) \ | 35 #define IOCTL_OR_LOG_ERROR(type, arg) \ |
| 36 do { \ | 36 do { \ |
| 37 if (device_->Ioctl(type, arg) != 0) \ | 37 if (device_->Ioctl(type, arg) != 0) \ |
| 38 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << #type; \ | 38 PLOG(ERROR) << __func__ << "(): ioctl() failed: " << #type; \ |
| 39 } while (0) | 39 } while (0) |
| 40 | 40 |
| 41 namespace content { | 41 namespace media { |
| 42 | 42 |
| 43 V4L2ImageProcessor::InputRecord::InputRecord() : at_device(false) { | 43 V4L2ImageProcessor::InputRecord::InputRecord() : at_device(false) {} |
| 44 } | |
| 45 | 44 |
| 46 V4L2ImageProcessor::InputRecord::~InputRecord() { | 45 V4L2ImageProcessor::InputRecord::~InputRecord() {} |
| 47 } | |
| 48 | 46 |
| 49 V4L2ImageProcessor::OutputRecord::OutputRecord() : at_device(false) {} | 47 V4L2ImageProcessor::OutputRecord::OutputRecord() : at_device(false) {} |
| 50 | 48 |
| 51 V4L2ImageProcessor::OutputRecord::~OutputRecord() { | 49 V4L2ImageProcessor::OutputRecord::~OutputRecord() {} |
| 52 } | |
| 53 | 50 |
| 54 V4L2ImageProcessor::JobRecord::JobRecord() : output_buffer_index(-1) {} | 51 V4L2ImageProcessor::JobRecord::JobRecord() : output_buffer_index(-1) {} |
| 55 | 52 |
| 56 V4L2ImageProcessor::JobRecord::~JobRecord() { | 53 V4L2ImageProcessor::JobRecord::~JobRecord() {} |
| 57 } | |
| 58 | 54 |
| 59 V4L2ImageProcessor::V4L2ImageProcessor(const scoped_refptr<V4L2Device>& device) | 55 V4L2ImageProcessor::V4L2ImageProcessor(const scoped_refptr<V4L2Device>& device) |
| 60 : input_format_(media::PIXEL_FORMAT_UNKNOWN), | 56 : input_format_(media::PIXEL_FORMAT_UNKNOWN), |
| 61 output_format_(media::PIXEL_FORMAT_UNKNOWN), | 57 output_format_(media::PIXEL_FORMAT_UNKNOWN), |
| 62 input_format_fourcc_(0), | 58 input_format_fourcc_(0), |
| 63 output_format_fourcc_(0), | 59 output_format_fourcc_(0), |
| 64 input_planes_count_(0), | 60 input_planes_count_(0), |
| 65 output_planes_count_(0), | 61 output_planes_count_(0), |
| 66 child_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 62 child_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 67 device_(device), | 63 device_(device), |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 136 output_planes_count_ = media::VideoFrame::NumPlanes(output_format); | 132 output_planes_count_ = media::VideoFrame::NumPlanes(output_format); |
| 137 DCHECK_LE(output_planes_count_, static_cast<size_t>(VIDEO_MAX_PLANES)); | 133 DCHECK_LE(output_planes_count_, static_cast<size_t>(VIDEO_MAX_PLANES)); |
| 138 | 134 |
| 139 // Capabilities check. | 135 // Capabilities check. |
| 140 struct v4l2_capability caps; | 136 struct v4l2_capability caps; |
| 141 memset(&caps, 0, sizeof(caps)); | 137 memset(&caps, 0, sizeof(caps)); |
| 142 const __u32 kCapsRequired = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; | 138 const __u32 kCapsRequired = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; |
| 143 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYCAP, &caps); | 139 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYCAP, &caps); |
| 144 if ((caps.capabilities & kCapsRequired) != kCapsRequired) { | 140 if ((caps.capabilities & kCapsRequired) != kCapsRequired) { |
| 145 LOG(ERROR) << "Initialize(): ioctl() failed: VIDIOC_QUERYCAP: " | 141 LOG(ERROR) << "Initialize(): ioctl() failed: VIDIOC_QUERYCAP: " |
| 146 "caps check failed: 0x" << std::hex << caps.capabilities; | 142 "caps check failed: 0x" |
| 143 << std::hex << caps.capabilities; |
| 147 return false; | 144 return false; |
| 148 } | 145 } |
| 149 | 146 |
| 150 if (!CreateInputBuffers() || !CreateOutputBuffers()) | 147 if (!CreateInputBuffers() || !CreateOutputBuffers()) |
| 151 return false; | 148 return false; |
| 152 | 149 |
| 153 if (!device_thread_.Start()) { | 150 if (!device_thread_.Start()) { |
| 154 LOG(ERROR) << "Initialize(): encoder thread failed to start"; | 151 LOG(ERROR) << "Initialize(): encoder thread failed to start"; |
| 155 return false; | 152 return false; |
| 156 } | 153 } |
| (...skipping 29 matching lines...) Expand all Loading... |
| 186 int output_buffer_index, | 183 int output_buffer_index, |
| 187 const FrameReadyCB& cb) { | 184 const FrameReadyCB& cb) { |
| 188 DVLOG(3) << __func__ << ": ts=" << frame->timestamp().InMilliseconds(); | 185 DVLOG(3) << __func__ << ": ts=" << frame->timestamp().InMilliseconds(); |
| 189 | 186 |
| 190 std::unique_ptr<JobRecord> job_record(new JobRecord()); | 187 std::unique_ptr<JobRecord> job_record(new JobRecord()); |
| 191 job_record->frame = frame; | 188 job_record->frame = frame; |
| 192 job_record->output_buffer_index = output_buffer_index; | 189 job_record->output_buffer_index = output_buffer_index; |
| 193 job_record->ready_cb = cb; | 190 job_record->ready_cb = cb; |
| 194 | 191 |
| 195 device_thread_.message_loop()->PostTask( | 192 device_thread_.message_loop()->PostTask( |
| 196 FROM_HERE, | 193 FROM_HERE, base::Bind(&V4L2ImageProcessor::ProcessTask, |
| 197 base::Bind(&V4L2ImageProcessor::ProcessTask, | 194 base::Unretained(this), base::Passed(&job_record))); |
| 198 base::Unretained(this), | |
| 199 base::Passed(&job_record))); | |
| 200 } | 195 } |
| 201 | 196 |
| 202 void V4L2ImageProcessor::ProcessTask(std::unique_ptr<JobRecord> job_record) { | 197 void V4L2ImageProcessor::ProcessTask(std::unique_ptr<JobRecord> job_record) { |
| 203 int index = job_record->output_buffer_index; | 198 int index = job_record->output_buffer_index; |
| 204 DVLOG(3) << __func__ << ": Reusing output buffer, index=" << index; | 199 DVLOG(3) << __func__ << ": Reusing output buffer, index=" << index; |
| 205 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | 200 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); |
| 206 | 201 |
| 207 EnqueueOutput(index); | 202 EnqueueOutput(index); |
| 208 input_queue_.push(make_linked_ptr(job_record.release())); | 203 input_queue_.push(make_linked_ptr(job_record.release())); |
| 209 EnqueueInput(); | 204 EnqueueInput(); |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 273 for (size_t i = 0; i < input_planes_count_; ++i) { | 268 for (size_t i = 0; i < input_planes_count_; ++i) { |
| 274 format.fmt.pix_mp.plane_fmt[i].sizeimage = | 269 format.fmt.pix_mp.plane_fmt[i].sizeimage = |
| 275 media::VideoFrame::PlaneSize(input_format_, i, input_allocated_size_) | 270 media::VideoFrame::PlaneSize(input_format_, i, input_allocated_size_) |
| 276 .GetArea(); | 271 .GetArea(); |
| 277 format.fmt.pix_mp.plane_fmt[i].bytesperline = | 272 format.fmt.pix_mp.plane_fmt[i].bytesperline = |
| 278 base::checked_cast<__u32>(input_allocated_size_.width()); | 273 base::checked_cast<__u32>(input_allocated_size_.width()); |
| 279 } | 274 } |
| 280 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); | 275 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); |
| 281 | 276 |
| 282 input_allocated_size_ = V4L2Device::CodedSizeFromV4L2Format(format); | 277 input_allocated_size_ = V4L2Device::CodedSizeFromV4L2Format(format); |
| 283 DCHECK(gfx::Rect(input_allocated_size_).Contains( | 278 DCHECK(gfx::Rect(input_allocated_size_) |
| 284 gfx::Rect(input_visible_size_))); | 279 .Contains(gfx::Rect(input_visible_size_))); |
| 285 | 280 |
| 286 struct v4l2_crop crop; | 281 struct v4l2_crop crop; |
| 287 memset(&crop, 0, sizeof(crop)); | 282 memset(&crop, 0, sizeof(crop)); |
| 288 crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | 283 crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| 289 crop.c.left = 0; | 284 crop.c.left = 0; |
| 290 crop.c.top = 0; | 285 crop.c.top = 0; |
| 291 crop.c.width = base::checked_cast<__u32>(input_visible_size_.width()); | 286 crop.c.width = base::checked_cast<__u32>(input_visible_size_.width()); |
| 292 crop.c.height = base::checked_cast<__u32>(input_visible_size_.height()); | 287 crop.c.height = base::checked_cast<__u32>(input_visible_size_.height()); |
| 293 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); | 288 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); |
| 294 | 289 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 format.fmt.pix_mp.plane_fmt[i].sizeimage = | 324 format.fmt.pix_mp.plane_fmt[i].sizeimage = |
| 330 media::VideoFrame::PlaneSize(output_format_, i, output_allocated_size_) | 325 media::VideoFrame::PlaneSize(output_format_, i, output_allocated_size_) |
| 331 .GetArea(); | 326 .GetArea(); |
| 332 format.fmt.pix_mp.plane_fmt[i].bytesperline = | 327 format.fmt.pix_mp.plane_fmt[i].bytesperline = |
| 333 base::checked_cast<__u32>(output_allocated_size_.width()); | 328 base::checked_cast<__u32>(output_allocated_size_.width()); |
| 334 } | 329 } |
| 335 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); | 330 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format); |
| 336 | 331 |
| 337 gfx::Size adjusted_allocated_size = | 332 gfx::Size adjusted_allocated_size = |
| 338 V4L2Device::CodedSizeFromV4L2Format(format); | 333 V4L2Device::CodedSizeFromV4L2Format(format); |
| 339 DCHECK(gfx::Rect(adjusted_allocated_size).Contains( | 334 DCHECK(gfx::Rect(adjusted_allocated_size) |
| 340 gfx::Rect(output_allocated_size_))); | 335 .Contains(gfx::Rect(output_allocated_size_))); |
| 341 output_allocated_size_ = adjusted_allocated_size; | 336 output_allocated_size_ = adjusted_allocated_size; |
| 342 | 337 |
| 343 struct v4l2_crop crop; | 338 struct v4l2_crop crop; |
| 344 memset(&crop, 0, sizeof(crop)); | 339 memset(&crop, 0, sizeof(crop)); |
| 345 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | 340 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| 346 crop.c.left = 0; | 341 crop.c.left = 0; |
| 347 crop.c.top = 0; | 342 crop.c.top = 0; |
| 348 crop.c.width = base::checked_cast<__u32>(output_visible_size_.width()); | 343 crop.c.width = base::checked_cast<__u32>(output_visible_size_.width()); |
| 349 crop.c.height = base::checked_cast<__u32>(output_visible_size_.height()); | 344 crop.c.height = base::checked_cast<__u32>(output_visible_size_.height()); |
| 350 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); | 345 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_CROP, &crop); |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 | 396 |
| 402 bool event_pending; | 397 bool event_pending; |
| 403 if (!device_->Poll(poll_device, &event_pending)) { | 398 if (!device_->Poll(poll_device, &event_pending)) { |
| 404 NotifyError(); | 399 NotifyError(); |
| 405 return; | 400 return; |
| 406 } | 401 } |
| 407 | 402 |
| 408 // All processing should happen on ServiceDeviceTask(), since we shouldn't | 403 // All processing should happen on ServiceDeviceTask(), since we shouldn't |
| 409 // touch encoder state from this thread. | 404 // touch encoder state from this thread. |
| 410 device_thread_.message_loop()->PostTask( | 405 device_thread_.message_loop()->PostTask( |
| 411 FROM_HERE, | 406 FROM_HERE, base::Bind(&V4L2ImageProcessor::ServiceDeviceTask, |
| 412 base::Bind(&V4L2ImageProcessor::ServiceDeviceTask, | 407 base::Unretained(this))); |
| 413 base::Unretained(this))); | |
| 414 } | 408 } |
| 415 | 409 |
| 416 void V4L2ImageProcessor::ServiceDeviceTask() { | 410 void V4L2ImageProcessor::ServiceDeviceTask() { |
| 417 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | 411 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); |
| 418 // ServiceDeviceTask() should only ever be scheduled from DevicePollTask(), | 412 // ServiceDeviceTask() should only ever be scheduled from DevicePollTask(), |
| 419 // so either: | 413 // so either: |
| 420 // * device_poll_thread_ is running normally | 414 // * device_poll_thread_ is running normally |
| 421 // * device_poll_thread_ scheduled us, but then a DestroyTask() shut it down, | 415 // * device_poll_thread_ scheduled us, but then a DestroyTask() shut it down, |
| 422 // in which case we should early-out. | 416 // in which case we should early-out. |
| 423 if (!device_poll_thread_.message_loop()) | 417 if (!device_poll_thread_.message_loop()) |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 572 struct v4l2_buffer qbuf; | 566 struct v4l2_buffer qbuf; |
| 573 struct v4l2_plane qbuf_planes[VIDEO_MAX_PLANES]; | 567 struct v4l2_plane qbuf_planes[VIDEO_MAX_PLANES]; |
| 574 memset(&qbuf, 0, sizeof(qbuf)); | 568 memset(&qbuf, 0, sizeof(qbuf)); |
| 575 memset(qbuf_planes, 0, sizeof(qbuf_planes)); | 569 memset(qbuf_planes, 0, sizeof(qbuf_planes)); |
| 576 qbuf.index = index; | 570 qbuf.index = index; |
| 577 qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; | 571 qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
| 578 qbuf.memory = V4L2_MEMORY_USERPTR; | 572 qbuf.memory = V4L2_MEMORY_USERPTR; |
| 579 qbuf.m.planes = qbuf_planes; | 573 qbuf.m.planes = qbuf_planes; |
| 580 qbuf.length = input_planes_count_; | 574 qbuf.length = input_planes_count_; |
| 581 for (size_t i = 0; i < input_planes_count_; ++i) { | 575 for (size_t i = 0; i < input_planes_count_; ++i) { |
| 582 qbuf.m.planes[i].bytesused = media::VideoFrame::PlaneSize( | 576 qbuf.m.planes[i].bytesused = |
| 583 input_record.frame->format(), i, input_allocated_size_).GetArea(); | 577 media::VideoFrame::PlaneSize(input_record.frame->format(), i, |
| 578 input_allocated_size_) |
| 579 .GetArea(); |
| 584 qbuf.m.planes[i].length = qbuf.m.planes[i].bytesused; | 580 qbuf.m.planes[i].length = qbuf.m.planes[i].bytesused; |
| 585 qbuf.m.planes[i].m.userptr = | 581 qbuf.m.planes[i].m.userptr = |
| 586 reinterpret_cast<unsigned long>(input_record.frame->data(i)); | 582 reinterpret_cast<unsigned long>(input_record.frame->data(i)); |
| 587 } | 583 } |
| 588 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf); | 584 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf); |
| 589 input_record.at_device = true; | 585 input_record.at_device = true; |
| 590 running_jobs_.push(job_record); | 586 running_jobs_.push(job_record); |
| 591 free_input_buffers_.pop_back(); | 587 free_input_buffers_.pop_back(); |
| 592 input_buffer_queued_count_++; | 588 input_buffer_queued_count_++; |
| 593 | 589 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 625 | 621 |
| 626 // Start up the device poll thread and schedule its first DevicePollTask(). | 622 // Start up the device poll thread and schedule its first DevicePollTask(). |
| 627 if (!device_poll_thread_.Start()) { | 623 if (!device_poll_thread_.Start()) { |
| 628 LOG(ERROR) << "StartDevicePoll(): Device thread failed to start"; | 624 LOG(ERROR) << "StartDevicePoll(): Device thread failed to start"; |
| 629 NotifyError(); | 625 NotifyError(); |
| 630 return false; | 626 return false; |
| 631 } | 627 } |
| 632 // Enqueue a poll task with no devices to poll on - will wait only for the | 628 // Enqueue a poll task with no devices to poll on - will wait only for the |
| 633 // poll interrupt | 629 // poll interrupt |
| 634 device_poll_thread_.message_loop()->PostTask( | 630 device_poll_thread_.message_loop()->PostTask( |
| 635 FROM_HERE, | 631 FROM_HERE, base::Bind(&V4L2ImageProcessor::DevicePollTask, |
| 636 base::Bind( | 632 base::Unretained(this), false)); |
| 637 &V4L2ImageProcessor::DevicePollTask, base::Unretained(this), false)); | |
| 638 | 633 |
| 639 return true; | 634 return true; |
| 640 } | 635 } |
| 641 | 636 |
| 642 bool V4L2ImageProcessor::StopDevicePoll() { | 637 bool V4L2ImageProcessor::StopDevicePoll() { |
| 643 DVLOG(3) << __func__ << ": stopping device poll"; | 638 DVLOG(3) << __func__ << ": stopping device poll"; |
| 644 if (device_thread_.IsRunning()) | 639 if (device_thread_.IsRunning()) |
| 645 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); | 640 DCHECK_EQ(device_thread_.message_loop(), base::MessageLoop::current()); |
| 646 | 641 |
| 647 // Signal the DevicePollTask() to stop, and stop the device poll thread. | 642 // Signal the DevicePollTask() to stop, and stop the device poll thread. |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 687 | 682 |
| 688 return true; | 683 return true; |
| 689 } | 684 } |
| 690 | 685 |
| 691 void V4L2ImageProcessor::FrameReady(const FrameReadyCB& cb, | 686 void V4L2ImageProcessor::FrameReady(const FrameReadyCB& cb, |
| 692 int output_buffer_index) { | 687 int output_buffer_index) { |
| 693 DCHECK(child_task_runner_->BelongsToCurrentThread()); | 688 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 694 cb.Run(output_buffer_index); | 689 cb.Run(output_buffer_index); |
| 695 } | 690 } |
| 696 | 691 |
| 697 } // namespace content | 692 } // namespace media |
| OLD | NEW |