Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Unified Diff: content/common/gpu/media/exynos_video_decode_accelerator.cc

Issue 22590009: EVDA: Add support for dynamic resolution change and MSE players. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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());
}

Powered by Google App Engine
This is Rietveld 408576698