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 <fcntl.h> | 5 #include <fcntl.h> |
| 6 #include <linux/videodev2.h> | 6 #include <linux/videodev2.h> |
| 7 #include <poll.h> | 7 #include <poll.h> |
| 8 #include <sys/eventfd.h> | 8 #include <sys/eventfd.h> |
| 9 #include <sys/ioctl.h> | 9 #include <sys/ioctl.h> |
| 10 #include <sys/mman.h> | 10 #include <sys/mman.h> |
| 11 | 11 |
| 12 #include "base/bind.h" | 12 #include "base/bind.h" |
| 13 #include "base/bind_helpers.h" | 13 #include "base/bind_helpers.h" |
| 14 #include "base/callback.h" | 14 #include "base/callback.h" |
| 15 #include "base/callback_helpers.h" | 15 #include "base/callback_helpers.h" |
| 16 #include "base/command_line.h" | 16 #include "base/command_line.h" |
| 17 #include "base/message_loop/message_loop_proxy.h" | |
| 18 #include "base/numerics/safe_conversions.h" | 17 #include "base/numerics/safe_conversions.h" |
| 19 #include "base/strings/stringprintf.h" | 18 #include "base/strings/stringprintf.h" |
| 20 #include "content/common/gpu/media/v4l2_slice_video_decode_accelerator.h" | 19 #include "content/common/gpu/media/v4l2_slice_video_decode_accelerator.h" |
| 21 #include "media/base/bind_to_current_loop.h" | 20 #include "media/base/bind_to_current_loop.h" |
| 22 #include "media/base/media_switches.h" | 21 #include "media/base/media_switches.h" |
| 23 #include "ui/gl/scoped_binders.h" | 22 #include "ui/gl/scoped_binders.h" |
| 24 | 23 |
| 25 #define LOGF(level) LOG(level) << __FUNCTION__ << "(): " | 24 #define LOGF(level) LOG(level) << __FUNCTION__ << "(): " |
| 26 #define DVLOGF(level) DVLOG(level) << __FUNCTION__ << "(): " | 25 #define DVLOGF(level) DVLOG(level) << __FUNCTION__ << "(): " |
| 27 | 26 |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 155 at_client(false), | 154 at_client(false), |
| 156 picture_id(-1), | 155 picture_id(-1), |
| 157 egl_image(EGL_NO_IMAGE_KHR), | 156 egl_image(EGL_NO_IMAGE_KHR), |
| 158 egl_sync(EGL_NO_SYNC_KHR), | 157 egl_sync(EGL_NO_SYNC_KHR), |
| 159 cleared(false) { | 158 cleared(false) { |
| 160 } | 159 } |
| 161 | 160 |
| 162 struct V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef { | 161 struct V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef { |
| 163 BitstreamBufferRef( | 162 BitstreamBufferRef( |
| 164 base::WeakPtr<VideoDecodeAccelerator::Client>& client, | 163 base::WeakPtr<VideoDecodeAccelerator::Client>& client, |
| 165 const scoped_refptr<base::MessageLoopProxy>& client_message_loop_proxy, | 164 const scoped_refptr<base::SingleThreadTaskRunner>& client_task_runner, |
| 166 base::SharedMemory* shm, | 165 base::SharedMemory* shm, |
| 167 size_t size, | 166 size_t size, |
| 168 int32 input_id); | 167 int32 input_id); |
| 169 ~BitstreamBufferRef(); | 168 ~BitstreamBufferRef(); |
| 170 const base::WeakPtr<VideoDecodeAccelerator::Client> client; | 169 const base::WeakPtr<VideoDecodeAccelerator::Client> client; |
| 171 const scoped_refptr<base::MessageLoopProxy> client_message_loop_proxy; | 170 const scoped_refptr<base::SingleThreadTaskRunner> client_task_runner; |
| 172 const scoped_ptr<base::SharedMemory> shm; | 171 const scoped_ptr<base::SharedMemory> shm; |
| 173 const size_t size; | 172 const size_t size; |
| 174 off_t bytes_used; | 173 off_t bytes_used; |
| 175 const int32 input_id; | 174 const int32 input_id; |
| 176 }; | 175 }; |
| 177 | 176 |
| 178 V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef::BitstreamBufferRef( | 177 V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef::BitstreamBufferRef( |
| 179 base::WeakPtr<VideoDecodeAccelerator::Client>& client, | 178 base::WeakPtr<VideoDecodeAccelerator::Client>& client, |
| 180 const scoped_refptr<base::MessageLoopProxy>& client_message_loop_proxy, | 179 const scoped_refptr<base::SingleThreadTaskRunner>& client_task_runner, |
| 181 base::SharedMemory* shm, | 180 base::SharedMemory* shm, |
| 182 size_t size, | 181 size_t size, |
| 183 int32 input_id) | 182 int32 input_id) |
| 184 : client(client), | 183 : client(client), |
| 185 client_message_loop_proxy(client_message_loop_proxy), | 184 client_task_runner(client_task_runner), |
| 186 shm(shm), | 185 shm(shm), |
| 187 size(size), | 186 size(size), |
| 188 bytes_used(0), | 187 bytes_used(0), |
| 189 input_id(input_id) { | 188 input_id(input_id) { |
| 190 } | 189 } |
| 191 | 190 |
| 192 V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef::~BitstreamBufferRef() { | 191 V4L2SliceVideoDecodeAccelerator::BitstreamBufferRef::~BitstreamBufferRef() { |
| 193 if (input_id >= 0) { | 192 if (input_id >= 0) { |
| 194 DVLOGF(5) << "returning input_id: " << input_id; | 193 DVLOGF(5) << "returning input_id: " << input_id; |
| 195 client_message_loop_proxy->PostTask( | 194 client_task_runner->PostTask( |
| 196 FROM_HERE, | 195 FROM_HERE, |
| 197 base::Bind(&VideoDecodeAccelerator::Client::NotifyEndOfBitstreamBuffer, | 196 base::Bind(&VideoDecodeAccelerator::Client::NotifyEndOfBitstreamBuffer, |
| 198 client, input_id)); | 197 client, input_id)); |
| 199 } | 198 } |
| 200 } | 199 } |
| 201 | 200 |
| 202 struct V4L2SliceVideoDecodeAccelerator::EGLSyncKHRRef { | 201 struct V4L2SliceVideoDecodeAccelerator::EGLSyncKHRRef { |
| 203 EGLSyncKHRRef(EGLDisplay egl_display, EGLSyncKHR egl_sync); | 202 EGLSyncKHRRef(EGLDisplay egl_display, EGLSyncKHR egl_sync); |
| 204 ~EGLSyncKHRRef(); | 203 ~EGLSyncKHRRef(); |
| 205 EGLDisplay const egl_display; | 204 EGLDisplay const egl_display; |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 373 | 372 |
| 374 V4L2VP8Picture::~V4L2VP8Picture() { | 373 V4L2VP8Picture::~V4L2VP8Picture() { |
| 375 } | 374 } |
| 376 | 375 |
| 377 V4L2SliceVideoDecodeAccelerator::V4L2SliceVideoDecodeAccelerator( | 376 V4L2SliceVideoDecodeAccelerator::V4L2SliceVideoDecodeAccelerator( |
| 378 const scoped_refptr<V4L2Device>& device, | 377 const scoped_refptr<V4L2Device>& device, |
| 379 EGLDisplay egl_display, | 378 EGLDisplay egl_display, |
| 380 EGLContext egl_context, | 379 EGLContext egl_context, |
| 381 const base::WeakPtr<Client>& io_client, | 380 const base::WeakPtr<Client>& io_client, |
| 382 const base::Callback<bool(void)>& make_context_current, | 381 const base::Callback<bool(void)>& make_context_current, |
| 383 const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy) | 382 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) |
| 384 : input_planes_count_(0), | 383 : input_planes_count_(0), |
| 385 output_planes_count_(0), | 384 output_planes_count_(0), |
| 386 child_message_loop_proxy_(base::MessageLoopProxy::current()), | 385 child_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 387 io_message_loop_proxy_(io_message_loop_proxy), | 386 io_task_runner_(io_task_runner), |
| 388 io_client_(io_client), | 387 io_client_(io_client), |
| 389 device_(device), | 388 device_(device), |
| 390 decoder_thread_("V4L2SliceVideoDecodeAcceleratorThread"), | 389 decoder_thread_("V4L2SliceVideoDecodeAcceleratorThread"), |
| 391 device_poll_thread_("V4L2SliceVideoDecodeAcceleratorDevicePollThread"), | 390 device_poll_thread_("V4L2SliceVideoDecodeAcceleratorDevicePollThread"), |
| 392 input_streamon_(false), | 391 input_streamon_(false), |
| 393 input_buffer_queued_count_(0), | 392 input_buffer_queued_count_(0), |
| 394 output_streamon_(false), | 393 output_streamon_(false), |
| 395 output_buffer_queued_count_(0), | 394 output_buffer_queued_count_(0), |
| 396 video_profile_(media::VIDEO_CODEC_PROFILE_UNKNOWN), | 395 video_profile_(media::VIDEO_CODEC_PROFILE_UNKNOWN), |
| 397 output_format_fourcc_(0), | 396 output_format_fourcc_(0), |
| 398 state_(kUninitialized), | 397 state_(kUninitialized), |
| 399 decoder_flushing_(false), | 398 decoder_flushing_(false), |
| 400 decoder_resetting_(false), | 399 decoder_resetting_(false), |
| 401 surface_set_change_pending_(false), | 400 surface_set_change_pending_(false), |
| 402 picture_clearing_count_(0), | 401 picture_clearing_count_(0), |
| 403 pictures_assigned_(false, false), | 402 pictures_assigned_(false, false), |
| 404 make_context_current_(make_context_current), | 403 make_context_current_(make_context_current), |
| 405 egl_display_(egl_display), | 404 egl_display_(egl_display), |
| 406 egl_context_(egl_context), | 405 egl_context_(egl_context), |
| 407 weak_this_factory_(this) { | 406 weak_this_factory_(this) { |
| 408 weak_this_ = weak_this_factory_.GetWeakPtr(); | 407 weak_this_ = weak_this_factory_.GetWeakPtr(); |
| 409 } | 408 } |
| 410 | 409 |
| 411 V4L2SliceVideoDecodeAccelerator::~V4L2SliceVideoDecodeAccelerator() { | 410 V4L2SliceVideoDecodeAccelerator::~V4L2SliceVideoDecodeAccelerator() { |
| 412 DVLOGF(2); | 411 DVLOGF(2); |
| 413 | 412 |
| 414 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 413 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 415 DCHECK(!decoder_thread_.IsRunning()); | 414 DCHECK(!decoder_thread_.IsRunning()); |
| 416 DCHECK(!device_poll_thread_.IsRunning()); | 415 DCHECK(!device_poll_thread_.IsRunning()); |
| 417 | 416 |
| 418 DCHECK(input_buffer_map_.empty()); | 417 DCHECK(input_buffer_map_.empty()); |
| 419 DCHECK(output_buffer_map_.empty()); | 418 DCHECK(output_buffer_map_.empty()); |
| 420 } | 419 } |
| 421 | 420 |
| 422 void V4L2SliceVideoDecodeAccelerator::NotifyError(Error error) { | 421 void V4L2SliceVideoDecodeAccelerator::NotifyError(Error error) { |
| 423 if (!child_message_loop_proxy_->BelongsToCurrentThread()) { | 422 if (!child_task_runner_->BelongsToCurrentThread()) { |
| 424 child_message_loop_proxy_->PostTask( | 423 child_task_runner_->PostTask( |
| 425 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::NotifyError, | 424 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::NotifyError, |
| 426 weak_this_, error)); | 425 weak_this_, error)); |
| 427 return; | 426 return; |
| 428 } | 427 } |
| 429 | 428 |
| 430 if (client_) { | 429 if (client_) { |
| 431 client_->NotifyError(error); | 430 client_->NotifyError(error); |
| 432 client_ptr_factory_.reset(); | 431 client_ptr_factory_.reset(); |
| 433 } | 432 } |
| 434 } | 433 } |
| 435 | 434 |
| 436 bool V4L2SliceVideoDecodeAccelerator::Initialize( | 435 bool V4L2SliceVideoDecodeAccelerator::Initialize( |
| 437 media::VideoCodecProfile profile, | 436 media::VideoCodecProfile profile, |
| 438 VideoDecodeAccelerator::Client* client) { | 437 VideoDecodeAccelerator::Client* client) { |
| 439 DVLOGF(3) << "profile: " << profile; | 438 DVLOGF(3) << "profile: " << profile; |
| 440 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 439 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 441 DCHECK_EQ(state_, kUninitialized); | 440 DCHECK_EQ(state_, kUninitialized); |
| 442 | 441 |
| 443 client_ptr_factory_.reset( | 442 client_ptr_factory_.reset( |
| 444 new base::WeakPtrFactory<VideoDecodeAccelerator::Client>(client)); | 443 new base::WeakPtrFactory<VideoDecodeAccelerator::Client>(client)); |
| 445 client_ = client_ptr_factory_->GetWeakPtr(); | 444 client_ = client_ptr_factory_->GetWeakPtr(); |
| 446 | 445 |
| 447 video_profile_ = profile; | 446 video_profile_ = profile; |
| 448 | 447 |
| 449 if (video_profile_ >= media::H264PROFILE_MIN && | 448 if (video_profile_ >= media::H264PROFILE_MIN && |
| 450 video_profile_ <= media::H264PROFILE_MAX) { | 449 video_profile_ <= media::H264PROFILE_MAX) { |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 492 return false; | 491 return false; |
| 493 } | 492 } |
| 494 | 493 |
| 495 if (!SetupFormats()) | 494 if (!SetupFormats()) |
| 496 return false; | 495 return false; |
| 497 | 496 |
| 498 if (!decoder_thread_.Start()) { | 497 if (!decoder_thread_.Start()) { |
| 499 DLOG(ERROR) << "Initialize(): device thread failed to start"; | 498 DLOG(ERROR) << "Initialize(): device thread failed to start"; |
| 500 return false; | 499 return false; |
| 501 } | 500 } |
| 502 decoder_thread_proxy_ = decoder_thread_.message_loop_proxy(); | 501 decoder_thread_proxy_ = decoder_thread_.task_runner(); |
|
no sievers
2015/05/12 18:11:30
nit: consider renaming |decoder_thread_proxy_|
Sami
2015/05/13 18:17:52
Done.
| |
| 503 | 502 |
| 504 state_ = kInitialized; | 503 state_ = kInitialized; |
| 505 | 504 |
| 506 // InitializeTask will NOTIFY_ERROR on failure. | 505 // InitializeTask will NOTIFY_ERROR on failure. |
| 507 decoder_thread_proxy_->PostTask( | 506 decoder_thread_proxy_->PostTask( |
| 508 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::InitializeTask, | 507 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::InitializeTask, |
| 509 base::Unretained(this))); | 508 base::Unretained(this))); |
| 510 | 509 |
| 511 DVLOGF(1) << "V4L2SliceVideoDecodeAccelerator initialized"; | 510 DVLOGF(1) << "V4L2SliceVideoDecodeAccelerator initialized"; |
| 512 return true; | 511 return true; |
| 513 } | 512 } |
| 514 | 513 |
| 515 void V4L2SliceVideoDecodeAccelerator::InitializeTask() { | 514 void V4L2SliceVideoDecodeAccelerator::InitializeTask() { |
| 516 DVLOGF(3); | 515 DVLOGF(3); |
| 517 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 516 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| 518 DCHECK_EQ(state_, kInitialized); | 517 DCHECK_EQ(state_, kInitialized); |
| 519 | 518 |
| 520 if (!CreateInputBuffers()) | 519 if (!CreateInputBuffers()) |
| 521 NOTIFY_ERROR(PLATFORM_FAILURE); | 520 NOTIFY_ERROR(PLATFORM_FAILURE); |
| 522 | 521 |
| 523 // Output buffers will be created once decoder gives us information | 522 // Output buffers will be created once decoder gives us information |
| 524 // about their size and required count. | 523 // about their size and required count. |
| 525 state_ = kDecoding; | 524 state_ = kDecoding; |
| 526 } | 525 } |
| 527 | 526 |
| 528 void V4L2SliceVideoDecodeAccelerator::Destroy() { | 527 void V4L2SliceVideoDecodeAccelerator::Destroy() { |
| 529 DVLOGF(3); | 528 DVLOGF(3); |
| 530 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 529 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 531 | 530 |
| 532 if (decoder_thread_.IsRunning()) { | 531 if (decoder_thread_.IsRunning()) { |
| 533 decoder_thread_proxy_->PostTask( | 532 decoder_thread_proxy_->PostTask( |
| 534 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DestroyTask, | 533 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DestroyTask, |
| 535 base::Unretained(this))); | 534 base::Unretained(this))); |
| 536 | 535 |
| 537 // Wait for tasks to finish/early-exit. | 536 // Wait for tasks to finish/early-exit. |
| 538 decoder_thread_.Stop(); | 537 decoder_thread_.Stop(); |
| 539 } | 538 } |
| 540 | 539 |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 717 PLOG(ERROR) << "Could not allocate enough output buffers"; | 716 PLOG(ERROR) << "Could not allocate enough output buffers"; |
| 718 return false; | 717 return false; |
| 719 } | 718 } |
| 720 | 719 |
| 721 output_buffer_map_.resize(reqbufs.count); | 720 output_buffer_map_.resize(reqbufs.count); |
| 722 | 721 |
| 723 DVLOGF(3) << "buffer_count=" << output_buffer_map_.size() | 722 DVLOGF(3) << "buffer_count=" << output_buffer_map_.size() |
| 724 << ", visible size=" << visible_size_.ToString() | 723 << ", visible size=" << visible_size_.ToString() |
| 725 << ", coded size=" << coded_size_.ToString(); | 724 << ", coded size=" << coded_size_.ToString(); |
| 726 | 725 |
| 727 child_message_loop_proxy_->PostTask( | 726 child_task_runner_->PostTask( |
| 728 FROM_HERE, | 727 FROM_HERE, |
| 729 base::Bind(&VideoDecodeAccelerator::Client::ProvidePictureBuffers, | 728 base::Bind(&VideoDecodeAccelerator::Client::ProvidePictureBuffers, |
| 730 client_, output_buffer_map_.size(), coded_size_, | 729 client_, output_buffer_map_.size(), coded_size_, |
| 731 device_->GetTextureTarget())); | 730 device_->GetTextureTarget())); |
| 732 | 731 |
| 733 // Wait for the client to call AssignPictureBuffers() on the Child thread. | 732 // Wait for the client to call AssignPictureBuffers() on the Child thread. |
| 734 // We do this, because if we continue decoding without finishing buffer | 733 // We do this, because if we continue decoding without finishing buffer |
| 735 // allocation, we may end up Resetting before AssignPictureBuffers arrives, | 734 // allocation, we may end up Resetting before AssignPictureBuffers arrives, |
| 736 // resulting in unnecessary complications and subtle bugs. | 735 // resulting in unnecessary complications and subtle bugs. |
| 737 pictures_assigned_.Wait(); | 736 pictures_assigned_.Wait(); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 758 IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs); | 757 IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs); |
| 759 | 758 |
| 760 input_buffer_map_.clear(); | 759 input_buffer_map_.clear(); |
| 761 free_input_buffers_.clear(); | 760 free_input_buffers_.clear(); |
| 762 } | 761 } |
| 763 | 762 |
| 764 void V4L2SliceVideoDecodeAccelerator::DismissPictures( | 763 void V4L2SliceVideoDecodeAccelerator::DismissPictures( |
| 765 std::vector<int32> picture_buffer_ids, | 764 std::vector<int32> picture_buffer_ids, |
| 766 base::WaitableEvent* done) { | 765 base::WaitableEvent* done) { |
| 767 DVLOGF(3); | 766 DVLOGF(3); |
| 768 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 767 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 769 | 768 |
| 770 for (auto picture_buffer_id : picture_buffer_ids) { | 769 for (auto picture_buffer_id : picture_buffer_ids) { |
| 771 DVLOGF(1) << "dismissing PictureBuffer id=" << picture_buffer_id; | 770 DVLOGF(1) << "dismissing PictureBuffer id=" << picture_buffer_id; |
| 772 client_->DismissPictureBuffer(picture_buffer_id); | 771 client_->DismissPictureBuffer(picture_buffer_id); |
| 773 } | 772 } |
| 774 | 773 |
| 775 done->Signal(); | 774 done->Signal(); |
| 776 } | 775 } |
| 777 | 776 |
| 778 void V4L2SliceVideoDecodeAccelerator::DevicePollTask(bool poll_device) { | 777 void V4L2SliceVideoDecodeAccelerator::DevicePollTask(bool poll_device) { |
| (...skipping 373 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1152 decoder_display_queue_.pop(); | 1151 decoder_display_queue_.pop(); |
| 1153 | 1152 |
| 1154 DVLOGF(3) << "Device poll stopped"; | 1153 DVLOGF(3) << "Device poll stopped"; |
| 1155 return true; | 1154 return true; |
| 1156 } | 1155 } |
| 1157 | 1156 |
| 1158 void V4L2SliceVideoDecodeAccelerator::Decode( | 1157 void V4L2SliceVideoDecodeAccelerator::Decode( |
| 1159 const media::BitstreamBuffer& bitstream_buffer) { | 1158 const media::BitstreamBuffer& bitstream_buffer) { |
| 1160 DVLOGF(3) << "input_id=" << bitstream_buffer.id() | 1159 DVLOGF(3) << "input_id=" << bitstream_buffer.id() |
| 1161 << ", size=" << bitstream_buffer.size(); | 1160 << ", size=" << bitstream_buffer.size(); |
| 1162 DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); | 1161 DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| 1163 | 1162 |
| 1164 decoder_thread_proxy_->PostTask( | 1163 decoder_thread_proxy_->PostTask( |
| 1165 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DecodeTask, | 1164 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DecodeTask, |
| 1166 base::Unretained(this), bitstream_buffer)); | 1165 base::Unretained(this), bitstream_buffer)); |
| 1167 } | 1166 } |
| 1168 | 1167 |
| 1169 void V4L2SliceVideoDecodeAccelerator::DecodeTask( | 1168 void V4L2SliceVideoDecodeAccelerator::DecodeTask( |
| 1170 const media::BitstreamBuffer& bitstream_buffer) { | 1169 const media::BitstreamBuffer& bitstream_buffer) { |
| 1171 DVLOGF(3) << "input_id=" << bitstream_buffer.id() | 1170 DVLOGF(3) << "input_id=" << bitstream_buffer.id() |
| 1172 << " size=" << bitstream_buffer.size(); | 1171 << " size=" << bitstream_buffer.size(); |
| 1173 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 1172 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| 1174 | 1173 |
| 1175 scoped_ptr<BitstreamBufferRef> bitstream_record(new BitstreamBufferRef( | 1174 scoped_ptr<BitstreamBufferRef> bitstream_record(new BitstreamBufferRef( |
| 1176 io_client_, io_message_loop_proxy_, | 1175 io_client_, io_task_runner_, |
| 1177 new base::SharedMemory(bitstream_buffer.handle(), true), | 1176 new base::SharedMemory(bitstream_buffer.handle(), true), |
| 1178 bitstream_buffer.size(), bitstream_buffer.id())); | 1177 bitstream_buffer.size(), bitstream_buffer.id())); |
| 1179 if (!bitstream_record->shm->Map(bitstream_buffer.size())) { | 1178 if (!bitstream_record->shm->Map(bitstream_buffer.size())) { |
| 1180 LOGF(ERROR) << "Could not map bitstream_buffer"; | 1179 LOGF(ERROR) << "Could not map bitstream_buffer"; |
| 1181 NOTIFY_ERROR(UNREADABLE_INPUT); | 1180 NOTIFY_ERROR(UNREADABLE_INPUT); |
| 1182 return; | 1181 return; |
| 1183 } | 1182 } |
| 1184 DVLOGF(3) << "mapped at=" << bitstream_record->shm->memory(); | 1183 DVLOGF(3) << "mapped at=" << bitstream_record->shm->memory(); |
| 1185 | 1184 |
| 1186 decoder_input_queue_.push( | 1185 decoder_input_queue_.push( |
| (...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1333 | 1332 |
| 1334 for (auto output_record : output_buffer_map_) { | 1333 for (auto output_record : output_buffer_map_) { |
| 1335 DCHECK(!output_record.at_device); | 1334 DCHECK(!output_record.at_device); |
| 1336 | 1335 |
| 1337 if (output_record.egl_sync != EGL_NO_SYNC_KHR) { | 1336 if (output_record.egl_sync != EGL_NO_SYNC_KHR) { |
| 1338 if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) | 1337 if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) |
| 1339 DVLOGF(1) << "eglDestroySyncKHR failed."; | 1338 DVLOGF(1) << "eglDestroySyncKHR failed."; |
| 1340 } | 1339 } |
| 1341 | 1340 |
| 1342 if (output_record.egl_image != EGL_NO_IMAGE_KHR) { | 1341 if (output_record.egl_image != EGL_NO_IMAGE_KHR) { |
| 1343 child_message_loop_proxy_->PostTask( | 1342 child_task_runner_->PostTask( |
| 1344 FROM_HERE, | 1343 FROM_HERE, |
| 1345 base::Bind(base::IgnoreResult(&V4L2Device::DestroyEGLImage), device_, | 1344 base::Bind(base::IgnoreResult(&V4L2Device::DestroyEGLImage), device_, |
| 1346 egl_display_, output_record.egl_image)); | 1345 egl_display_, output_record.egl_image)); |
| 1347 } | 1346 } |
| 1348 | 1347 |
| 1349 picture_buffers_to_dismiss.push_back(output_record.picture_id); | 1348 picture_buffers_to_dismiss.push_back(output_record.picture_id); |
| 1350 } | 1349 } |
| 1351 | 1350 |
| 1352 if (dismiss) { | 1351 if (dismiss) { |
| 1353 DVLOGF(2) << "Scheduling picture dismissal"; | 1352 DVLOGF(2) << "Scheduling picture dismissal"; |
| 1354 base::WaitableEvent done(false, false); | 1353 base::WaitableEvent done(false, false); |
| 1355 child_message_loop_proxy_->PostTask( | 1354 child_task_runner_->PostTask( |
| 1356 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DismissPictures, | 1355 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::DismissPictures, |
| 1357 weak_this_, picture_buffers_to_dismiss, &done)); | 1356 weak_this_, picture_buffers_to_dismiss, &done)); |
| 1358 done.Wait(); | 1357 done.Wait(); |
| 1359 } | 1358 } |
| 1360 | 1359 |
| 1361 // At this point client can't call ReusePictureBuffer on any of the pictures | 1360 // At this point client can't call ReusePictureBuffer on any of the pictures |
| 1362 // anymore, so it's safe to destroy. | 1361 // anymore, so it's safe to destroy. |
| 1363 return DestroyOutputBuffers(); | 1362 return DestroyOutputBuffers(); |
| 1364 } | 1363 } |
| 1365 | 1364 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1400 reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; | 1399 reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| 1401 reqbufs.memory = V4L2_MEMORY_MMAP; | 1400 reqbufs.memory = V4L2_MEMORY_MMAP; |
| 1402 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs); | 1401 IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs); |
| 1403 | 1402 |
| 1404 return true; | 1403 return true; |
| 1405 } | 1404 } |
| 1406 | 1405 |
| 1407 void V4L2SliceVideoDecodeAccelerator::AssignPictureBuffers( | 1406 void V4L2SliceVideoDecodeAccelerator::AssignPictureBuffers( |
| 1408 const std::vector<media::PictureBuffer>& buffers) { | 1407 const std::vector<media::PictureBuffer>& buffers) { |
| 1409 DVLOGF(3); | 1408 DVLOGF(3); |
| 1410 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 1409 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 1411 | 1410 |
| 1412 if (buffers.size() != output_buffer_map_.size()) { | 1411 if (buffers.size() != output_buffer_map_.size()) { |
| 1413 DLOG(ERROR) << "Failed to provide requested picture buffers. " | 1412 DLOG(ERROR) << "Failed to provide requested picture buffers. " |
| 1414 << "(Got " << buffers.size() | 1413 << "(Got " << buffers.size() |
| 1415 << ", requested " << output_buffer_map_.size() << ")"; | 1414 << ", requested " << output_buffer_map_.size() << ")"; |
| 1416 NOTIFY_ERROR(INVALID_ARGUMENT); | 1415 NOTIFY_ERROR(INVALID_ARGUMENT); |
| 1417 return; | 1416 return; |
| 1418 } | 1417 } |
| 1419 | 1418 |
| 1420 if (!make_context_current_.Run()) { | 1419 if (!make_context_current_.Run()) { |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1459 output_record.picture_id = buffers[i].id(); | 1458 output_record.picture_id = buffers[i].id(); |
| 1460 free_output_buffers_.push_back(i); | 1459 free_output_buffers_.push_back(i); |
| 1461 DVLOGF(3) << "buffer[" << i << "]: picture_id=" << output_record.picture_id; | 1460 DVLOGF(3) << "buffer[" << i << "]: picture_id=" << output_record.picture_id; |
| 1462 } | 1461 } |
| 1463 | 1462 |
| 1464 pictures_assigned_.Signal(); | 1463 pictures_assigned_.Signal(); |
| 1465 } | 1464 } |
| 1466 | 1465 |
| 1467 void V4L2SliceVideoDecodeAccelerator::ReusePictureBuffer( | 1466 void V4L2SliceVideoDecodeAccelerator::ReusePictureBuffer( |
| 1468 int32 picture_buffer_id) { | 1467 int32 picture_buffer_id) { |
| 1469 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 1468 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 1470 DVLOGF(4) << "picture_buffer_id=" << picture_buffer_id; | 1469 DVLOGF(4) << "picture_buffer_id=" << picture_buffer_id; |
| 1471 | 1470 |
| 1472 if (!make_context_current_.Run()) { | 1471 if (!make_context_current_.Run()) { |
| 1473 LOGF(ERROR) << "could not make context current"; | 1472 LOGF(ERROR) << "could not make context current"; |
| 1474 NOTIFY_ERROR(PLATFORM_FAILURE); | 1473 NOTIFY_ERROR(PLATFORM_FAILURE); |
| 1475 return; | 1474 return; |
| 1476 } | 1475 } |
| 1477 | 1476 |
| 1478 EGLSyncKHR egl_sync = | 1477 EGLSyncKHR egl_sync = |
| 1479 eglCreateSyncKHR(egl_display_, EGL_SYNC_FENCE_KHR, NULL); | 1478 eglCreateSyncKHR(egl_display_, EGL_SYNC_FENCE_KHR, NULL); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1522 DCHECK(!output_record.at_device); | 1521 DCHECK(!output_record.at_device); |
| 1523 output_record.at_client = false; | 1522 output_record.at_client = false; |
| 1524 output_record.egl_sync = egl_sync_ref->egl_sync; | 1523 output_record.egl_sync = egl_sync_ref->egl_sync; |
| 1525 // Take ownership of the EGLSync. | 1524 // Take ownership of the EGLSync. |
| 1526 egl_sync_ref->egl_sync = EGL_NO_SYNC_KHR; | 1525 egl_sync_ref->egl_sync = EGL_NO_SYNC_KHR; |
| 1527 surfaces_at_display_.erase(it); | 1526 surfaces_at_display_.erase(it); |
| 1528 } | 1527 } |
| 1529 | 1528 |
| 1530 void V4L2SliceVideoDecodeAccelerator::Flush() { | 1529 void V4L2SliceVideoDecodeAccelerator::Flush() { |
| 1531 DVLOGF(3); | 1530 DVLOGF(3); |
| 1532 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 1531 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 1533 | 1532 |
| 1534 decoder_thread_proxy_->PostTask( | 1533 decoder_thread_proxy_->PostTask( |
| 1535 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::FlushTask, | 1534 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::FlushTask, |
| 1536 base::Unretained(this))); | 1535 base::Unretained(this))); |
| 1537 } | 1536 } |
| 1538 | 1537 |
| 1539 void V4L2SliceVideoDecodeAccelerator::FlushTask() { | 1538 void V4L2SliceVideoDecodeAccelerator::FlushTask() { |
| 1540 DVLOGF(3); | 1539 DVLOGF(3); |
| 1541 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 1540 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| 1542 | 1541 |
| 1543 if (!decoder_input_queue_.empty()) { | 1542 if (!decoder_input_queue_.empty()) { |
| 1544 // We are not done with pending inputs, so queue an empty buffer, | 1543 // We are not done with pending inputs, so queue an empty buffer, |
| 1545 // which - when reached - will trigger flush sequence. | 1544 // which - when reached - will trigger flush sequence. |
| 1546 decoder_input_queue_.push( | 1545 decoder_input_queue_.push( |
| 1547 linked_ptr<BitstreamBufferRef>(new BitstreamBufferRef( | 1546 linked_ptr<BitstreamBufferRef>(new BitstreamBufferRef( |
| 1548 io_client_, io_message_loop_proxy_, nullptr, 0, kFlushBufferId))); | 1547 io_client_, io_task_runner_, nullptr, 0, kFlushBufferId))); |
| 1549 return; | 1548 return; |
| 1550 } | 1549 } |
| 1551 | 1550 |
| 1552 // No more inputs pending, so just finish flushing here. | 1551 // No more inputs pending, so just finish flushing here. |
| 1553 InitiateFlush(); | 1552 InitiateFlush(); |
| 1554 } | 1553 } |
| 1555 | 1554 |
| 1556 void V4L2SliceVideoDecodeAccelerator::InitiateFlush() { | 1555 void V4L2SliceVideoDecodeAccelerator::InitiateFlush() { |
| 1557 DVLOGF(3); | 1556 DVLOGF(3); |
| 1558 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 1557 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1597 // we will have all remaining PictureReady() posted to the client and we | 1596 // we will have all remaining PictureReady() posted to the client and we |
| 1598 // can post NotifyFlushDone(). | 1597 // can post NotifyFlushDone(). |
| 1599 DCHECK(decoder_display_queue_.empty()); | 1598 DCHECK(decoder_display_queue_.empty()); |
| 1600 | 1599 |
| 1601 // Decoder should have already returned all surfaces and all surfaces are | 1600 // Decoder should have already returned all surfaces and all surfaces are |
| 1602 // out of hardware. There can be no other owners of input buffers. | 1601 // out of hardware. There can be no other owners of input buffers. |
| 1603 DCHECK_EQ(free_input_buffers_.size(), input_buffer_map_.size()); | 1602 DCHECK_EQ(free_input_buffers_.size(), input_buffer_map_.size()); |
| 1604 | 1603 |
| 1605 SendPictureReady(); | 1604 SendPictureReady(); |
| 1606 | 1605 |
| 1607 child_message_loop_proxy_->PostTask( | 1606 child_task_runner_->PostTask(FROM_HERE, |
| 1608 FROM_HERE, base::Bind(&Client::NotifyFlushDone, client_)); | 1607 base::Bind(&Client::NotifyFlushDone, client_)); |
| 1609 | 1608 |
| 1610 decoder_flushing_ = false; | 1609 decoder_flushing_ = false; |
| 1611 | 1610 |
| 1612 DVLOGF(3) << "Flush finished"; | 1611 DVLOGF(3) << "Flush finished"; |
| 1613 state_ = kDecoding; | 1612 state_ = kDecoding; |
| 1614 ScheduleDecodeBufferTaskIfNeeded(); | 1613 ScheduleDecodeBufferTaskIfNeeded(); |
| 1615 } | 1614 } |
| 1616 | 1615 |
| 1617 void V4L2SliceVideoDecodeAccelerator::Reset() { | 1616 void V4L2SliceVideoDecodeAccelerator::Reset() { |
| 1618 DVLOGF(3); | 1617 DVLOGF(3); |
| 1619 DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); | 1618 DCHECK(child_task_runner_->BelongsToCurrentThread()); |
| 1620 | 1619 |
| 1621 decoder_thread_proxy_->PostTask( | 1620 decoder_thread_proxy_->PostTask( |
| 1622 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::ResetTask, | 1621 FROM_HERE, base::Bind(&V4L2SliceVideoDecodeAccelerator::ResetTask, |
| 1623 base::Unretained(this))); | 1622 base::Unretained(this))); |
| 1624 } | 1623 } |
| 1625 | 1624 |
| 1626 void V4L2SliceVideoDecodeAccelerator::ResetTask() { | 1625 void V4L2SliceVideoDecodeAccelerator::ResetTask() { |
| 1627 DVLOGF(3); | 1626 DVLOGF(3); |
| 1628 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 1627 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| 1629 | 1628 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1673 // simply mark them all as available. | 1672 // simply mark them all as available. |
| 1674 DCHECK_EQ(input_buffer_queued_count_, 0); | 1673 DCHECK_EQ(input_buffer_queued_count_, 0); |
| 1675 free_input_buffers_.clear(); | 1674 free_input_buffers_.clear(); |
| 1676 for (size_t i = 0; i < input_buffer_map_.size(); ++i) { | 1675 for (size_t i = 0; i < input_buffer_map_.size(); ++i) { |
| 1677 DCHECK(!input_buffer_map_[i].at_device); | 1676 DCHECK(!input_buffer_map_[i].at_device); |
| 1678 ReuseInputBuffer(i); | 1677 ReuseInputBuffer(i); |
| 1679 } | 1678 } |
| 1680 | 1679 |
| 1681 decoder_resetting_ = false; | 1680 decoder_resetting_ = false; |
| 1682 | 1681 |
| 1683 child_message_loop_proxy_->PostTask( | 1682 child_task_runner_->PostTask(FROM_HERE, |
| 1684 FROM_HERE, base::Bind(&Client::NotifyResetDone, client_)); | 1683 base::Bind(&Client::NotifyResetDone, client_)); |
| 1685 | 1684 |
| 1686 DVLOGF(3) << "Reset finished"; | 1685 DVLOGF(3) << "Reset finished"; |
| 1687 | 1686 |
| 1688 state_ = kDecoding; | 1687 state_ = kDecoding; |
| 1689 ScheduleDecodeBufferTaskIfNeeded(); | 1688 ScheduleDecodeBufferTaskIfNeeded(); |
| 1690 } | 1689 } |
| 1691 | 1690 |
| 1692 void V4L2SliceVideoDecodeAccelerator::SetErrorState(Error error) { | 1691 void V4L2SliceVideoDecodeAccelerator::SetErrorState(Error error) { |
| 1693 // We can touch decoder_state_ only if this is the decoder thread or the | 1692 // We can touch decoder_state_ only if this is the decoder thread or the |
| 1694 // decoder thread isn't running. | 1693 // decoder thread isn't running. |
| (...skipping 753 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2448 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); | 2447 DCHECK(decoder_thread_proxy_->BelongsToCurrentThread()); |
| 2449 bool resetting_or_flushing = (decoder_resetting_ || decoder_flushing_); | 2448 bool resetting_or_flushing = (decoder_resetting_ || decoder_flushing_); |
| 2450 while (!pending_picture_ready_.empty()) { | 2449 while (!pending_picture_ready_.empty()) { |
| 2451 bool cleared = pending_picture_ready_.front().cleared; | 2450 bool cleared = pending_picture_ready_.front().cleared; |
| 2452 const media::Picture& picture = pending_picture_ready_.front().picture; | 2451 const media::Picture& picture = pending_picture_ready_.front().picture; |
| 2453 if (cleared && picture_clearing_count_ == 0) { | 2452 if (cleared && picture_clearing_count_ == 0) { |
| 2454 DVLOGF(4) << "Posting picture ready to IO for: " | 2453 DVLOGF(4) << "Posting picture ready to IO for: " |
| 2455 << picture.picture_buffer_id(); | 2454 << picture.picture_buffer_id(); |
| 2456 // This picture is cleared. Post it to IO thread to reduce latency. This | 2455 // This picture is cleared. Post it to IO thread to reduce latency. This |
| 2457 // should be the case after all pictures are cleared at the beginning. | 2456 // should be the case after all pictures are cleared at the beginning. |
| 2458 io_message_loop_proxy_->PostTask( | 2457 io_task_runner_->PostTask( |
| 2459 FROM_HERE, base::Bind(&Client::PictureReady, io_client_, picture)); | 2458 FROM_HERE, base::Bind(&Client::PictureReady, io_client_, picture)); |
| 2460 pending_picture_ready_.pop(); | 2459 pending_picture_ready_.pop(); |
| 2461 } else if (!cleared || resetting_or_flushing) { | 2460 } else if (!cleared || resetting_or_flushing) { |
| 2462 DVLOGF(3) << "cleared=" << pending_picture_ready_.front().cleared | 2461 DVLOGF(3) << "cleared=" << pending_picture_ready_.front().cleared |
| 2463 << ", decoder_resetting_=" << decoder_resetting_ | 2462 << ", decoder_resetting_=" << decoder_resetting_ |
| 2464 << ", decoder_flushing_=" << decoder_flushing_ | 2463 << ", decoder_flushing_=" << decoder_flushing_ |
| 2465 << ", picture_clearing_count_=" << picture_clearing_count_; | 2464 << ", picture_clearing_count_=" << picture_clearing_count_; |
| 2466 DVLOGF(4) << "Posting picture ready to GPU for: " | 2465 DVLOGF(4) << "Posting picture ready to GPU for: " |
| 2467 << picture.picture_buffer_id(); | 2466 << picture.picture_buffer_id(); |
| 2468 // If the picture is not cleared, post it to the child thread because it | 2467 // If the picture is not cleared, post it to the child thread because it |
| 2469 // has to be cleared in the child thread. A picture only needs to be | 2468 // has to be cleared in the child thread. A picture only needs to be |
| 2470 // cleared once. If the decoder is resetting or flushing, send all | 2469 // cleared once. If the decoder is resetting or flushing, send all |
| 2471 // pictures to ensure PictureReady arrive before reset or flush done. | 2470 // pictures to ensure PictureReady arrive before reset or flush done. |
| 2472 child_message_loop_proxy_->PostTaskAndReply( | 2471 child_task_runner_->PostTaskAndReply( |
| 2473 FROM_HERE, base::Bind(&Client::PictureReady, client_, picture), | 2472 FROM_HERE, base::Bind(&Client::PictureReady, client_, picture), |
| 2474 // Unretained is safe. If Client::PictureReady gets to run, |this| is | 2473 // Unretained is safe. If Client::PictureReady gets to run, |this| is |
| 2475 // alive. Destroy() will wait the decode thread to finish. | 2474 // alive. Destroy() will wait the decode thread to finish. |
| 2476 base::Bind(&V4L2SliceVideoDecodeAccelerator::PictureCleared, | 2475 base::Bind(&V4L2SliceVideoDecodeAccelerator::PictureCleared, |
| 2477 base::Unretained(this))); | 2476 base::Unretained(this))); |
| 2478 picture_clearing_count_++; | 2477 picture_clearing_count_++; |
| 2479 pending_picture_ready_.pop(); | 2478 pending_picture_ready_.pop(); |
| 2480 } else { | 2479 } else { |
| 2481 // This picture is cleared. But some pictures are about to be cleared on | 2480 // This picture is cleared. But some pictures are about to be cleared on |
| 2482 // the child thread. To preserve the order, do not send this until those | 2481 // the child thread. To preserve the order, do not send this until those |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2533 profile.profile = media::VP8PROFILE_ANY; | 2532 profile.profile = media::VP8PROFILE_ANY; |
| 2534 profiles.push_back(profile); | 2533 profiles.push_back(profile); |
| 2535 break; | 2534 break; |
| 2536 } | 2535 } |
| 2537 } | 2536 } |
| 2538 | 2537 |
| 2539 return profiles; | 2538 return profiles; |
| 2540 } | 2539 } |
| 2541 | 2540 |
| 2542 } // namespace content | 2541 } // namespace content |
| OLD | NEW |