Index: content/common/gpu/media/exynos_video_decode_accelerator.cc |
diff --git a/content/common/gpu/media/exynos_video_decode_accelerator.cc b/content/common/gpu/media/exynos_video_decode_accelerator.cc |
index 6817ca9cb95ec343c644a52ea6fa012a97fa799f..ffb920b717d856d4f5a9234ea64e1fe8e30784a4 100644 |
--- a/content/common/gpu/media/exynos_video_decode_accelerator.cc |
+++ b/content/common/gpu/media/exynos_video_decode_accelerator.cc |
@@ -50,6 +50,11 @@ namespace content { |
namespace { |
+// TODO(posciak): remove once we update linux-headers. |
+#ifndef V4L2_EVENT_RESOLUTION_CHANGE |
+#define V4L2_EVENT_RESOLUTION_CHANGE 5 |
+#endif |
+ |
const char kExynosMfcDevice[] = "/dev/mfc-dec"; |
const char kExynosGscDevice[] = "/dev/gsc1"; |
const char kMaliDriver[] = "libmali.so"; |
@@ -212,6 +217,7 @@ ExynosVideoDecodeAccelerator::ExynosVideoDecodeAccelerator( |
decoder_decode_buffer_tasks_scheduled_(0), |
decoder_frames_at_client_(0), |
decoder_flushing_(false), |
+ resolution_change_pending_(false), |
decoder_partial_frame_pending_(false), |
mfc_fd_(-1), |
mfc_input_streamon_(false), |
@@ -379,6 +385,12 @@ bool ExynosVideoDecodeAccelerator::Initialize( |
format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12MT_16X16; |
IOCTL_OR_ERROR_RETURN_FALSE(mfc_fd_, VIDIOC_S_FMT, &format); |
+ // Subscribe for the resolution change event. |
+ struct v4l2_event_subscription sub; |
+ memset(&sub, 0, sizeof(sub)); |
+ sub.type = V4L2_EVENT_RESOLUTION_CHANGE; |
+ IOCTL_OR_ERROR_RETURN_FALSE(mfc_fd_, VIDIOC_SUBSCRIBE_EVENT, &sub); |
+ |
// Initialize format-specific bits. |
if (video_profile_ >= media::H264PROFILE_MIN && |
video_profile_ <= media::H264PROFILE_MAX) { |
@@ -637,6 +649,9 @@ void ExynosVideoDecodeAccelerator::DecodeBufferTask() { |
} else if (decoder_state_ == kError) { |
DVLOG(2) << "DecodeBufferTask(): early out: kError state"; |
return; |
+ } else if (decoder_state_ == kChangingResolution) { |
+ DVLOG(2) << "DecodeBufferTask(): early out: resolution change pending"; |
+ return; |
} |
if (decoder_current_bitstream_buffer_ == NULL) { |
@@ -836,6 +851,47 @@ void ExynosVideoDecodeAccelerator::ScheduleDecodeBufferTaskIfNeeded() { |
} |
} |
+bool ExynosVideoDecodeAccelerator::GetFormatInfo(struct v4l2_format* format, |
+ bool* again) { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ |
+ *again = false; |
+ memset(format, 0, sizeof(*format)); |
+ format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
+ if (ioctl(mfc_fd_, VIDIOC_G_FMT, format) != 0) { |
sheu
2013/08/09 09:23:16
I should have probably wrapped this in HANDLE_EINT
Pawel Osciak
2013/08/09 10:22:59
Done.
|
+ if (errno == EINVAL) { |
+ // EINVAL means we haven't seen sufficient stream to decode the format. |
+ *again = true; |
+ return true; |
+ } else { |
+ DPLOG(ERROR) << "DecodeBufferInitial(): ioctl() failed: VIDIOC_G_FMT"; |
+ NOTIFY_ERROR(PLATFORM_FAILURE); |
+ return false; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool ExynosVideoDecodeAccelerator::CreateBuffersForFormat( |
+ const struct v4l2_format& format) { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ CHECK_EQ(format.fmt.pix_mp.num_planes, 2); |
+ frame_buffer_size_.SetSize( |
+ format.fmt.pix_mp.width, format.fmt.pix_mp.height); |
+ mfc_output_buffer_size_[0] = format.fmt.pix_mp.plane_fmt[0].sizeimage; |
+ mfc_output_buffer_size_[1] = format.fmt.pix_mp.plane_fmt[1].sizeimage; |
+ mfc_output_buffer_pixelformat_ = format.fmt.pix_mp.pixelformat; |
+ DCHECK_EQ(mfc_output_buffer_pixelformat_, V4L2_PIX_FMT_NV12MT_16X16); |
+ DVLOG(3) << "New resolution: " << frame_buffer_size_.ToString(); |
sheu
2013/08/09 09:23:16
I got in the habit of "CreateBuffersForFormat(): B
Pawel Osciak
2013/08/09 10:22:59
Done.
|
+ |
+ if (!CreateMfcOutputBuffers() || !CreateGscInputBuffers() || |
+ !CreateGscOutputBuffers()) |
+ return false; |
+ |
+ return true; |
+} |
+ |
bool ExynosVideoDecodeAccelerator::DecodeBufferInitial( |
const void* data, size_t size, size_t* endpos) { |
DVLOG(3) << "DecodeBufferInitial(): data=" << data << ", size=" << size; |
@@ -862,35 +918,21 @@ bool ExynosVideoDecodeAccelerator::DecodeBufferInitial( |
// Check and see if we have format info yet. |
struct v4l2_format format; |
- format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
- if (ioctl(mfc_fd_, VIDIOC_G_FMT, &format) != 0) { |
- if (errno == EINVAL) { |
- // We will get EINVAL if we haven't seen sufficient stream to decode the |
- // format. Return true and schedule the next buffer. |
- *endpos = size; |
- return true; |
- } else { |
- DPLOG(ERROR) << "DecodeBufferInitial(): ioctl() failed: VIDIOC_G_FMT"; |
- NOTIFY_ERROR(PLATFORM_FAILURE); |
- return false; |
- } |
+ bool again = false; |
+ if (!GetFormatInfo(&format, &again)) |
+ return false; |
+ |
+ if (again) { |
+ // Need more stream to decode format, return true and schedule next buffer. |
+ *endpos = size; |
+ return true; |
} |
// Run this initialization only on first startup. |
if (decoder_state_ == kInitialized) { |
DVLOG(3) << "DecodeBufferInitial(): running one-time initialization"; |
sheu
2013/08/09 09:23:16
It's not one-time anymore.
Pawel Osciak
2013/08/09 10:22:59
Done.
|
// Success! Setup our parameters. |
- CHECK_EQ(format.fmt.pix_mp.num_planes, 2); |
- frame_buffer_size_.SetSize( |
- format.fmt.pix_mp.width, format.fmt.pix_mp.height); |
- mfc_output_buffer_size_[0] = format.fmt.pix_mp.plane_fmt[0].sizeimage; |
- mfc_output_buffer_size_[1] = format.fmt.pix_mp.plane_fmt[1].sizeimage; |
- mfc_output_buffer_pixelformat_ = format.fmt.pix_mp.pixelformat; |
- DCHECK_EQ(mfc_output_buffer_pixelformat_, V4L2_PIX_FMT_NV12MT_16X16); |
- |
- // Create our other buffers. |
- if (!CreateMfcOutputBuffers() || !CreateGscInputBuffers() || |
- !CreateGscOutputBuffers()) |
+ if (!CreateBuffersForFormat(format)) |
return false; |
// MFC expects to process the initial buffer once during stream init to |
@@ -1064,9 +1106,12 @@ void ExynosVideoDecodeAccelerator::AssignPictureBuffersTask( |
// We got buffers! Kick the GSC. |
EnqueueGsc(); |
+ |
+ if (decoder_state_ == kChangingResolution) |
+ ResumeAfterResolutionChangeTask(); |
} |
-void ExynosVideoDecodeAccelerator::ServiceDeviceTask() { |
+void ExynosVideoDecodeAccelerator::ServiceDeviceTask(bool event_pending) { |
DVLOG(3) << "ServiceDeviceTask()"; |
DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
DCHECK_NE(decoder_state_, kUninitialized); |
@@ -1080,8 +1125,13 @@ void ExynosVideoDecodeAccelerator::ServiceDeviceTask() { |
} else if (decoder_state_ == kError) { |
DVLOG(2) << "ServiceDeviceTask(): early out: kError state"; |
return; |
+ } else if (decoder_state_ == kChangingResolution) { |
+ DVLOG(2) << "ServiceDeviceTask(): early out: kChangingResolution state"; |
+ return; |
} |
+ if (event_pending) |
+ DequeueEvents(); |
DequeueMfc(); |
DequeueGsc(); |
EnqueueMfc(); |
@@ -1133,6 +1183,83 @@ void ExynosVideoDecodeAccelerator::ServiceDeviceTask() { |
<< decoder_frames_at_client_ << "]"; |
ScheduleDecodeBufferTaskIfNeeded(); |
+ StartResolutionChangeIfNeeded(); |
+} |
+ |
+void ExynosVideoDecodeAccelerator::StartResolutionChangeIfNeeded() { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ DCHECK_NE(decoder_state_, kUninitialized); |
sheu
2013/08/09 09:23:16
DCHECK_EQ(decoder_state_, kDecoding)
Pawel Osciak
2013/08/09 10:22:59
Done.
|
+ |
+ if (!resolution_change_pending_) |
+ return; |
+ |
+ if (!mfc_output_gsc_input_queue_.empty() || |
sheu
2013/08/09 09:23:16
Do we need to wait for mfc_output_buffer_queued_co
Pawel Osciak
2013/08/09 10:22:59
The event will happen after MFC internal queues ar
|
+ gsc_input_buffer_queued_count_ + gsc_output_buffer_queued_count_ > 0) { |
+ DVLOG(3) << "StartResolutionChangeIfNeeded(): waiting for GSC to finish."; |
+ return; |
+ } |
+ |
+ DVLOG(3) << "No more work for GSC, initiate resolution change"; |
+ decoder_state_ = kChangingResolution; |
+ |
+ // Keep MFC input queue. |
+ if (!StopDevicePoll(true)) |
+ return; |
+ |
+ // Post a task to clean up buffers on child thread. This will also ensure |
+ // that we won't accept ReusePictureBuffer() anymore after that. |
+ child_message_loop_proxy_->PostTask(FROM_HERE, base::Bind( |
+ &ExynosVideoDecodeAccelerator::ResolutionChangeDestroyBuffers, |
+ weak_this_)); |
+} |
+ |
+void ExynosVideoDecodeAccelerator::ResolutionChangeDestroyBuffers() { |
sheu
2013/08/09 09:23:16
We should run this on the decoder_thread_, since t
Pawel Osciak
2013/08/09 10:22:59
As per offline chat, we'll do this in a separate C
|
+ DCHECK(child_message_loop_proxy_->BelongsToCurrentThread()); |
+ DVLOG(3) << "ResolutionChangeDestroyBuffers()"; |
+ |
+ DestroyGscInputBuffers(); |
+ DestroyGscOutputBuffers(); |
+ DestroyMfcOutputBuffers(); |
+ |
+ // Finish resolution change on decoder thread. |
+ decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( |
+ &ExynosVideoDecodeAccelerator::FinishResolutionChangeTask, |
+ base::Unretained(this))); |
+} |
+ |
+void ExynosVideoDecodeAccelerator::FinishResolutionChangeTask() { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ DVLOG(3) << "FinishResolutionChangeTask()"; |
+ |
+ DCHECK(resolution_change_pending_); |
+ resolution_change_pending_ = false; |
+ |
+ struct v4l2_format format; |
+ bool again; |
+ bool ret = GetFormatInfo(&format, &again); |
+ if (!ret || again) { |
+ DVLOG(3) << "Couldn't get format information after resolution change"; |
+ return; |
+ } |
+ |
+ if (!CreateBuffersForFormat(format)) |
+ DVLOG(3) << "Couldn't reallocate buffers after resolution change"; |
+ |
+ // From here we stay in kChangingResolution and wait for |
+ // AssignPictureBuffers() before we can resume. |
+} |
+ |
+void ExynosVideoDecodeAccelerator::ResumeAfterResolutionChangeTask() { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ DVLOG(3) << "ResumeAfterResolutionChangeTask()"; |
+ |
+ if (!StartDevicePoll()) |
+ return; |
+ |
+ decoder_state_ = kDecoding; |
+ EnqueueMfc(); |
+ // Gsc will get enqueued in AssignPictureBuffersTask(). |
+ ScheduleDecodeBufferTaskIfNeeded(); |
} |
void ExynosVideoDecodeAccelerator::EnqueueMfc() { |
@@ -1180,6 +1307,23 @@ void ExynosVideoDecodeAccelerator::EnqueueMfc() { |
} |
} |
+void ExynosVideoDecodeAccelerator::DequeueEvents() { |
+ DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
+ DCHECK_NE(decoder_state_, kUninitialized); |
sheu
2013/08/09 09:23:16
this really should just be DCHECK_EQ(decoder_state
Pawel Osciak
2013/08/09 10:22:59
Done.
|
+ DVLOG(3) << "DequeueEvents()"; |
+ |
+ struct v4l2_event ev; |
+ memset(&ev, 0, sizeof(ev)); |
+ |
+ while (ioctl(mfc_fd_, VIDIOC_DQEVENT, &ev) == 0) { |
+ if (ev.type == V4L2_EVENT_RESOLUTION_CHANGE) { |
+ DVLOG(3) << "DequeueEvents(): got resolution change event."; |
+ DCHECK(!resolution_change_pending_); |
+ resolution_change_pending_ = true; |
+ } |
+ } |
+} |
+ |
void ExynosVideoDecodeAccelerator::DequeueMfc() { |
DVLOG(3) << "DequeueMfc()"; |
DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
@@ -1538,6 +1682,11 @@ void ExynosVideoDecodeAccelerator::ReusePictureBufferTask( |
return; |
} |
+ if (decoder_state_ == kChangingResolution) { |
+ DVLOG(2) << "ReusePictureBufferTask(): early out: kChangingResolution"; |
+ return; |
+ } |
+ |
size_t index; |
for (index = 0; index < gsc_output_buffer_map_.size(); ++index) |
if (gsc_output_buffer_map_[index].picture_id == picture_buffer_id) |
@@ -1619,6 +1768,21 @@ void ExynosVideoDecodeAccelerator::NotifyFlushDoneIfNeeded() { |
gsc_input_buffer_queued_count_ + gsc_output_buffer_queued_count_ ) != 0) |
return; |
+ // TODO(posciak): crbug.com/270039. MFC requires a streamoff-streamon |
+ // sequence after flush to continue, even if we are not resetting. This would |
+ // make sense, because we don't really want to resume from a non-resume point |
+ // (e.g. not from an IDR) if we are flushed. |
+ // MSE player however triggers a Flush() on chunk end, but never Reset(). One |
+ // could argue either way, or even say that Flush() is not needed/harmful when |
+ // transitioning to next chunk. |
+ // For now, do the streamoff-streamon cycle to satisfy MFC and not freeze when |
+ // doing MSE. This should be harmless otherwise. |
+ if (!StopDevicePoll(false)) |
+ return; |
+ |
+ if (!StartDevicePoll()) |
+ return; |
+ |
decoder_delay_bitstream_buffer_id_ = -1; |
decoder_flushing_ = false; |
DVLOG(3) << "NotifyFlushDoneIfNeeded(): returning flush"; |
@@ -1640,7 +1804,7 @@ void ExynosVideoDecodeAccelerator::ResetTask() { |
} |
// We stop streaming, but we _don't_ destroy our buffers. |
- if (!StopDevicePoll()) |
+ if (!StopDevicePoll(false)) |
return; |
decoder_current_bitstream_buffer_.reset(); |
@@ -1694,7 +1858,7 @@ void ExynosVideoDecodeAccelerator::DestroyTask() { |
// DestroyTask() should run regardless of decoder_state_. |
// Stop streaming and the device_poll_thread_. |
- StopDevicePoll(); |
+ StopDevicePoll(false); |
decoder_current_bitstream_buffer_.reset(); |
decoder_current_input_buffer_ = -1; |
@@ -1726,7 +1890,7 @@ bool ExynosVideoDecodeAccelerator::StartDevicePoll() { |
return true; |
} |
-bool ExynosVideoDecodeAccelerator::StopDevicePoll() { |
+bool ExynosVideoDecodeAccelerator::StopDevicePoll(bool keep_mfc_input) { |
DVLOG(3) << "StopDevicePoll()"; |
DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current()); |
@@ -1739,11 +1903,13 @@ bool ExynosVideoDecodeAccelerator::StopDevicePoll() { |
return false; |
// Stop streaming. |
- if (mfc_input_streamon_) { |
- __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
- IOCTL_OR_ERROR_RETURN_FALSE(mfc_fd_, VIDIOC_STREAMOFF, &type); |
+ if (!keep_mfc_input) { |
+ if (mfc_input_streamon_) { |
+ __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; |
+ IOCTL_OR_ERROR_RETURN_FALSE(mfc_fd_, VIDIOC_STREAMOFF, &type); |
+ } |
+ mfc_input_streamon_ = false; |
} |
- mfc_input_streamon_ = false; |
if (mfc_output_streamon_) { |
__u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
IOCTL_OR_ERROR_RETURN_FALSE(mfc_fd_, VIDIOC_STREAMOFF, &type); |
@@ -1761,15 +1927,17 @@ bool ExynosVideoDecodeAccelerator::StopDevicePoll() { |
gsc_output_streamon_ = false; |
// Reset all our accounting info. |
- mfc_input_ready_queue_.clear(); |
- mfc_free_input_buffers_.clear(); |
- for (size_t i = 0; i < mfc_input_buffer_map_.size(); ++i) { |
- mfc_free_input_buffers_.push_back(i); |
- mfc_input_buffer_map_[i].at_device = false; |
- mfc_input_buffer_map_[i].bytes_used = 0; |
- mfc_input_buffer_map_[i].input_id = -1; |
+ if (!keep_mfc_input) { |
+ mfc_input_ready_queue_.clear(); |
+ mfc_free_input_buffers_.clear(); |
+ for (size_t i = 0; i < mfc_input_buffer_map_.size(); ++i) { |
+ mfc_free_input_buffers_.push_back(i); |
+ mfc_input_buffer_map_[i].at_device = false; |
+ mfc_input_buffer_map_[i].bytes_used = 0; |
+ mfc_input_buffer_map_[i].input_id = -1; |
+ } |
+ mfc_input_buffer_queued_count_ = 0; |
} |
- mfc_input_buffer_queued_count_ = 0; |
mfc_free_output_buffers_.clear(); |
for (size_t i = 0; i < mfc_output_buffer_map_.size(); ++i) { |
mfc_free_output_buffers_.push_back(i); |
@@ -1841,6 +2009,7 @@ void ExynosVideoDecodeAccelerator::DevicePollTask(unsigned int poll_fds) { |
// device_poll_interrupt_fd_. |
struct pollfd pollfds[3]; |
nfds_t nfds; |
+ int mfc_pollfd = -1; |
// Add device_poll_interrupt_fd_; |
pollfds[0].fd = device_poll_interrupt_fd_; |
@@ -1850,7 +2019,8 @@ void ExynosVideoDecodeAccelerator::DevicePollTask(unsigned int poll_fds) { |
if (poll_fds & kPollMfc) { |
DVLOG(3) << "DevicePollTask(): adding MFC to poll() set"; |
pollfds[nfds].fd = mfc_fd_; |
- pollfds[nfds].events = POLLIN | POLLOUT | POLLERR; |
+ pollfds[nfds].events = POLLIN | POLLOUT | POLLERR | POLLPRI; |
+ mfc_pollfd = nfds; |
nfds++; |
} |
// Add GSC fd, if we should poll on it. |
@@ -1869,11 +2039,14 @@ void ExynosVideoDecodeAccelerator::DevicePollTask(unsigned int poll_fds) { |
return; |
} |
+ bool event_pending = (mfc_pollfd != -1 && |
+ pollfds[mfc_pollfd].revents & POLLPRI); |
+ |
// All processing should happen on ServiceDeviceTask(), since we shouldn't |
// touch decoder state from this thread. |
decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( |
&ExynosVideoDecodeAccelerator::ServiceDeviceTask, |
- base::Unretained(this))); |
+ base::Unretained(this), event_pending)); |
} |
void ExynosVideoDecodeAccelerator::NotifyError(Error error) { |
@@ -1969,7 +2142,8 @@ bool ExynosVideoDecodeAccelerator::CreateMfcInputBuffers() { |
bool ExynosVideoDecodeAccelerator::CreateMfcOutputBuffers() { |
DVLOG(3) << "CreateMfcOutputBuffers()"; |
- DCHECK_EQ(decoder_state_, kInitialized); |
+ DCHECK(decoder_state_ == kInitialized || |
+ decoder_state_ == kChangingResolution); |
DCHECK(!mfc_output_streamon_); |
DCHECK(mfc_output_buffer_map_.empty()); |
@@ -2026,7 +2200,8 @@ bool ExynosVideoDecodeAccelerator::CreateMfcOutputBuffers() { |
bool ExynosVideoDecodeAccelerator::CreateGscInputBuffers() { |
DVLOG(3) << "CreateGscInputBuffers()"; |
- DCHECK_EQ(decoder_state_, kInitialized); |
+ DCHECK(decoder_state_ == kInitialized || |
+ decoder_state_ == kChangingResolution); |
DCHECK(!gsc_input_streamon_); |
DCHECK(gsc_input_buffer_map_.empty()); |
@@ -2089,7 +2264,8 @@ bool ExynosVideoDecodeAccelerator::CreateGscInputBuffers() { |
bool ExynosVideoDecodeAccelerator::CreateGscOutputBuffers() { |
DVLOG(3) << "CreateGscOutputBuffers()"; |
- DCHECK_EQ(decoder_state_, kInitialized); |
+ DCHECK(decoder_state_ == kInitialized || |
+ decoder_state_ == kChangingResolution); |
DCHECK(!gsc_output_streamon_); |
DCHECK(gsc_output_buffer_map_.empty()); |
@@ -2216,8 +2392,10 @@ void ExynosVideoDecodeAccelerator::DestroyGscOutputBuffers() { |
eglDestroyImageKHR(egl_display_, output_record.egl_image); |
if (output_record.egl_sync != EGL_NO_SYNC_KHR) |
eglDestroySyncKHR(egl_display_, output_record.egl_sync); |
- if (client_) |
+ if (client_) { |
+ DVLOG(1) << "Dismissing PictureBuffer id=" << output_record.picture_id; |
sheu
2013/08/09 09:23:16
(same as above) "DestroyGscOutputBuffers(): XXX "
Pawel Osciak
2013/08/09 10:22:59
Done.
|
client_->DismissPictureBuffer(output_record.picture_id); |
+ } |
++i; |
} while (i < gsc_output_buffer_map_.size()); |
} |