Commit f12d64fb authored by vigneshv's avatar vigneshv Committed by Commit bot

media/vpx: Add support for VP9 alpha channel

TEST=<new pipeline integration test>

Review-Url: https://codereview.chromium.org/2096813002
Cr-Commit-Position: refs/heads/master@{#402649}
parent 88b27fc7
...@@ -287,6 +287,48 @@ scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvGpuMemoryBuffers( ...@@ -287,6 +287,48 @@ scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvGpuMemoryBuffers(
return frame; return frame;
} }
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvaData(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
int32_t y_stride,
int32_t u_stride,
int32_t v_stride,
int32_t a_stride,
uint8_t* y_data,
uint8_t* u_data,
uint8_t* v_data,
uint8_t* a_data,
base::TimeDelta timestamp) {
const StorageType storage = STORAGE_UNOWNED_MEMORY;
if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
LOG(DFATAL) << __FUNCTION__ << " Invalid config."
<< ConfigToString(format, storage, coded_size, visible_rect,
natural_size);
return nullptr;
}
if (NumPlanes(format) != 4) {
LOG(DFATAL) << "Expecting Y, U, V and A planes to be present for the video"
<< " format.";
return nullptr;
}
scoped_refptr<VideoFrame> frame(new VideoFrame(
format, storage, coded_size, visible_rect, natural_size, timestamp));
frame->strides_[kYPlane] = y_stride;
frame->strides_[kUPlane] = u_stride;
frame->strides_[kVPlane] = v_stride;
frame->strides_[kAPlane] = a_stride;
frame->data_[kYPlane] = y_data;
frame->data_[kUPlane] = u_data;
frame->data_[kVPlane] = v_data;
frame->data_[kAPlane] = a_data;
return frame;
}
#if defined(OS_LINUX) #if defined(OS_LINUX)
// static // static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalDmabufs( scoped_refptr<VideoFrame> VideoFrame::WrapExternalDmabufs(
......
...@@ -194,6 +194,23 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { ...@@ -194,6 +194,23 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
const gfx::GpuMemoryBufferHandle& v_handle, const gfx::GpuMemoryBufferHandle& v_handle,
base::TimeDelta timestamp); base::TimeDelta timestamp);
// Wraps external YUVA data of the given parameters with a VideoFrame.
// The returned VideoFrame does not own the data passed in.
static scoped_refptr<VideoFrame> WrapExternalYuvaData(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
int32_t y_stride,
int32_t u_stride,
int32_t v_stride,
int32_t a_stride,
uint8_t* y_data,
uint8_t* u_data,
uint8_t* v_data,
uint8_t* a_data,
base::TimeDelta timestamp);
#if defined(OS_LINUX) #if defined(OS_LINUX)
// Wraps provided dmabufs // Wraps provided dmabufs
// (https://www.kernel.org/doc/Documentation/dma-buf-sharing.txt) with a // (https://www.kernel.org/doc/Documentation/dma-buf-sharing.txt) with a
......
...@@ -200,19 +200,20 @@ class VpxVideoDecoder::MemoryPool ...@@ -200,19 +200,20 @@ class VpxVideoDecoder::MemoryPool
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override; base::trace_event::ProcessMemoryDump* pmd) override;
private:
friend class base::RefCountedThreadSafe<VpxVideoDecoder::MemoryPool>;
~MemoryPool() override;
// Reference counted frame buffers used for VP9 decoding. Reference counting // Reference counted frame buffers used for VP9 decoding. Reference counting
// is done manually because both chromium and libvpx has to release this // is done manually because both chromium and libvpx has to release this
// before a buffer can be re-used. // before a buffer can be re-used.
struct VP9FrameBuffer { struct VP9FrameBuffer {
VP9FrameBuffer() : ref_cnt(0) {} VP9FrameBuffer() : ref_cnt(0) {}
std::vector<uint8_t> data; std::vector<uint8_t> data;
std::vector<uint8_t> alpha_data;
uint32_t ref_cnt; uint32_t ref_cnt;
}; };
private:
friend class base::RefCountedThreadSafe<VpxVideoDecoder::MemoryPool>;
~MemoryPool() override;
// Gets the next available frame buffer for use by libvpx. // Gets the next available frame buffer for use by libvpx.
VP9FrameBuffer* GetFreeFrameBuffer(size_t min_size); VP9FrameBuffer* GetFreeFrameBuffer(size_t min_size);
...@@ -446,11 +447,11 @@ bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) { ...@@ -446,11 +447,11 @@ bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) {
return false; return false;
// These are the combinations of codec-pixel format supported in principle. // These are the combinations of codec-pixel format supported in principle.
// Note that VP9 does not support Alpha in the current implementation.
DCHECK( DCHECK(
(config.codec() == kCodecVP8 && config.format() == PIXEL_FORMAT_YV12) || (config.codec() == kCodecVP8 && config.format() == PIXEL_FORMAT_YV12) ||
(config.codec() == kCodecVP8 && config.format() == PIXEL_FORMAT_YV12A) || (config.codec() == kCodecVP8 && config.format() == PIXEL_FORMAT_YV12A) ||
(config.codec() == kCodecVP9 && config.format() == PIXEL_FORMAT_YV12) || (config.codec() == kCodecVP9 && config.format() == PIXEL_FORMAT_YV12) ||
(config.codec() == kCodecVP9 && config.format() == PIXEL_FORMAT_YV12A) ||
(config.codec() == kCodecVP9 && config.format() == PIXEL_FORMAT_YV24)); (config.codec() == kCodecVP9 && config.format() == PIXEL_FORMAT_YV24));
#if !defined(DISABLE_FFMPEG_VIDEO_DECODERS) #if !defined(DISABLE_FFMPEG_VIDEO_DECODERS)
...@@ -466,9 +467,10 @@ bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) { ...@@ -466,9 +467,10 @@ bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) {
if (!vpx_codec_) if (!vpx_codec_)
return false; return false;
// Configure VP9 to decode on our buffers to skip a data copy on decoding. // Configure VP9 to decode on our buffers to skip a data copy on
// decoding. For YV12A-VP9, we use our buffers for the Y, U and V planes and
// copy the A plane.
if (config.codec() == kCodecVP9) { if (config.codec() == kCodecVP9) {
DCHECK_NE(PIXEL_FORMAT_YV12A, config.format());
DCHECK(vpx_codec_get_caps(vpx_codec_->iface) & DCHECK(vpx_codec_get_caps(vpx_codec_->iface) &
VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER); VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER);
...@@ -549,8 +551,26 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer, ...@@ -549,8 +551,26 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer,
return false; return false;
} }
if (!CopyVpxImageToVideoFrame(vpx_image, video_frame)) const vpx_image_t* vpx_image_alpha = nullptr;
AlphaDecodeStatus alpha_decode_status =
DecodeAlphaPlane(vpx_image, &vpx_image_alpha, buffer);
if (alpha_decode_status == kAlphaPlaneError) {
return false; return false;
} else if (alpha_decode_status == kNoAlphaPlaneData) {
*video_frame = nullptr;
return true;
}
if (!CopyVpxImageToVideoFrame(vpx_image, vpx_image_alpha, video_frame)) {
return false;
}
if (vpx_image_alpha && config_.codec() == kCodecVP8) {
libyuv::CopyPlane(vpx_image_alpha->planes[VPX_PLANE_Y],
vpx_image_alpha->stride[VPX_PLANE_Y],
(*video_frame)->visible_data(VideoFrame::kAPlane),
(*video_frame)->stride(VideoFrame::kAPlane),
(*video_frame)->visible_rect().width(),
(*video_frame)->visible_rect().height());
}
(*video_frame)->set_timestamp(base::TimeDelta::FromMicroseconds(timestamp)); (*video_frame)->set_timestamp(base::TimeDelta::FromMicroseconds(timestamp));
...@@ -564,29 +584,26 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer, ...@@ -564,29 +584,26 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer,
(*video_frame) (*video_frame)
->metadata() ->metadata()
->SetInteger(VideoFrameMetadata::COLOR_SPACE, color_space); ->SetInteger(VideoFrameMetadata::COLOR_SPACE, color_space);
return true;
}
if (!vpx_codec_alpha_) VpxVideoDecoder::AlphaDecodeStatus VpxVideoDecoder::DecodeAlphaPlane(
return true; const struct vpx_image* vpx_image,
const struct vpx_image** vpx_image_alpha,
if (buffer->side_data_size() < 8) { const scoped_refptr<DecoderBuffer>& buffer) {
// TODO(mcasas): Is this a warning or an error? if (!vpx_codec_alpha_ || buffer->side_data_size() < 8) {
DLOG(WARNING) << "Making Alpha channel opaque due to missing input"; return kAlphaPlaneProcessed;
const uint32_t kAlphaOpaqueValue = 255;
libyuv::SetPlane((*video_frame)->visible_data(VideoFrame::kAPlane),
(*video_frame)->stride(VideoFrame::kAPlane),
(*video_frame)->visible_rect().width(),
(*video_frame)->visible_rect().height(),
kAlphaOpaqueValue);
return true;
} }
// First 8 bytes of side data is |side_data_id| in big endian. // First 8 bytes of side data is |side_data_id| in big endian.
const uint64_t side_data_id = base::NetToHost64( const uint64_t side_data_id = base::NetToHost64(
*(reinterpret_cast<const uint64_t*>(buffer->side_data()))); *(reinterpret_cast<const uint64_t*>(buffer->side_data())));
if (side_data_id != 1) if (side_data_id != 1) {
return true; return kAlphaPlaneProcessed;
}
// Try and decode buffer->side_data() minus the first 8 bytes as a full frame. // Try and decode buffer->side_data() minus the first 8 bytes as a full
// frame.
int64_t timestamp_alpha = buffer->timestamp().InMicroseconds(); int64_t timestamp_alpha = buffer->timestamp().InMicroseconds();
void* user_priv_alpha = reinterpret_cast<void*>(&timestamp_alpha); void* user_priv_alpha = reinterpret_cast<void*>(&timestamp_alpha);
{ {
...@@ -598,48 +615,56 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer, ...@@ -598,48 +615,56 @@ bool VpxVideoDecoder::VpxDecode(const scoped_refptr<DecoderBuffer>& buffer,
if (status != VPX_CODEC_OK) { if (status != VPX_CODEC_OK) {
DLOG(ERROR) << "vpx_codec_decode() failed for the alpha: " DLOG(ERROR) << "vpx_codec_decode() failed for the alpha: "
<< vpx_codec_error(vpx_codec_); << vpx_codec_error(vpx_codec_);
return false; return kAlphaPlaneError;
} }
} }
vpx_codec_iter_t iter_alpha = NULL; vpx_codec_iter_t iter_alpha = NULL;
const vpx_image_t* vpx_image_alpha = *vpx_image_alpha = vpx_codec_get_frame(vpx_codec_alpha_, &iter_alpha);
vpx_codec_get_frame(vpx_codec_alpha_, &iter_alpha); if (!(*vpx_image_alpha)) {
if (!vpx_image_alpha) { return kNoAlphaPlaneData;
*video_frame = nullptr;
return true;
} }
if (vpx_image_alpha->user_priv != user_priv_alpha) { if ((*vpx_image_alpha)->user_priv != user_priv_alpha) {
DLOG(ERROR) << "Invalid output timestamp on alpha."; DLOG(ERROR) << "Invalid output timestamp on alpha.";
return false; return kAlphaPlaneError;
} }
if (vpx_image_alpha->d_h != vpx_image->d_h || if ((*vpx_image_alpha)->d_h != vpx_image->d_h ||
vpx_image_alpha->d_w != vpx_image->d_w) { (*vpx_image_alpha)->d_w != vpx_image->d_w) {
DLOG(ERROR) << "The alpha plane dimensions are not the same as the " DLOG(ERROR) << "The alpha plane dimensions are not the same as the "
"image dimensions."; "image dimensions.";
return false; return kAlphaPlaneError;
} }
libyuv::CopyPlane(vpx_image_alpha->planes[VPX_PLANE_Y], if (config_.codec() == kCodecVP9) {
vpx_image_alpha->stride[VPX_PLANE_Y], VpxVideoDecoder::MemoryPool::VP9FrameBuffer* frame_buffer =
(*video_frame)->visible_data(VideoFrame::kAPlane), static_cast<VpxVideoDecoder::MemoryPool::VP9FrameBuffer*>(
(*video_frame)->stride(VideoFrame::kAPlane), vpx_image->fb_priv);
(*video_frame)->visible_rect().width(), uint64_t alpha_plane_size =
(*video_frame)->visible_rect().height()); (*vpx_image_alpha)->stride[VPX_PLANE_Y] * (*vpx_image_alpha)->d_h;
return true; if (frame_buffer->alpha_data.size() < alpha_plane_size) {
frame_buffer->alpha_data.resize(alpha_plane_size);
}
libyuv::CopyPlane((*vpx_image_alpha)->planes[VPX_PLANE_Y],
(*vpx_image_alpha)->stride[VPX_PLANE_Y],
&frame_buffer->alpha_data[0],
(*vpx_image_alpha)->stride[VPX_PLANE_Y],
(*vpx_image_alpha)->d_w, (*vpx_image_alpha)->d_h);
}
return kAlphaPlaneProcessed;
} }
bool VpxVideoDecoder::CopyVpxImageToVideoFrame( bool VpxVideoDecoder::CopyVpxImageToVideoFrame(
const struct vpx_image* vpx_image, const struct vpx_image* vpx_image,
const struct vpx_image* vpx_image_alpha,
scoped_refptr<VideoFrame>* video_frame) { scoped_refptr<VideoFrame>* video_frame) {
DCHECK(vpx_image); DCHECK(vpx_image);
VideoPixelFormat codec_format; VideoPixelFormat codec_format;
switch (vpx_image->fmt) { switch (vpx_image->fmt) {
case VPX_IMG_FMT_I420: case VPX_IMG_FMT_I420:
codec_format = vpx_codec_alpha_ ? PIXEL_FORMAT_YV12A : PIXEL_FORMAT_YV12; codec_format = vpx_image_alpha ? PIXEL_FORMAT_YV12A : PIXEL_FORMAT_YV12;
break; break;
case VPX_IMG_FMT_I444: case VPX_IMG_FMT_I444:
...@@ -660,17 +685,25 @@ bool VpxVideoDecoder::CopyVpxImageToVideoFrame( ...@@ -660,17 +685,25 @@ bool VpxVideoDecoder::CopyVpxImageToVideoFrame(
if (memory_pool_.get()) { if (memory_pool_.get()) {
DCHECK_EQ(kCodecVP9, config_.codec()); DCHECK_EQ(kCodecVP9, config_.codec());
DCHECK(!vpx_codec_alpha_) << "Uh-oh, VP9 and Alpha shouldn't coexist."; if (vpx_image_alpha) {
*video_frame = VideoFrame::WrapExternalYuvData( VpxVideoDecoder::MemoryPool::VP9FrameBuffer* frame_buffer =
codec_format, static_cast<VpxVideoDecoder::MemoryPool::VP9FrameBuffer*>(
coded_size, gfx::Rect(visible_size), config_.natural_size(), vpx_image->fb_priv);
vpx_image->stride[VPX_PLANE_Y], *video_frame = VideoFrame::WrapExternalYuvaData(
vpx_image->stride[VPX_PLANE_U], codec_format, coded_size, gfx::Rect(visible_size),
vpx_image->stride[VPX_PLANE_V], config_.natural_size(), vpx_image->stride[VPX_PLANE_Y],
vpx_image->planes[VPX_PLANE_Y], vpx_image->stride[VPX_PLANE_U], vpx_image->stride[VPX_PLANE_V],
vpx_image->planes[VPX_PLANE_U], vpx_image_alpha->stride[VPX_PLANE_Y], vpx_image->planes[VPX_PLANE_Y],
vpx_image->planes[VPX_PLANE_V], vpx_image->planes[VPX_PLANE_U], vpx_image->planes[VPX_PLANE_V],
kNoTimestamp()); &frame_buffer->alpha_data[0], kNoTimestamp());
} else {
*video_frame = VideoFrame::WrapExternalYuvData(
codec_format, coded_size, gfx::Rect(visible_size),
config_.natural_size(), vpx_image->stride[VPX_PLANE_Y],
vpx_image->stride[VPX_PLANE_U], vpx_image->stride[VPX_PLANE_V],
vpx_image->planes[VPX_PLANE_Y], vpx_image->planes[VPX_PLANE_U],
vpx_image->planes[VPX_PLANE_V], kNoTimestamp());
}
if (!(*video_frame)) if (!(*video_frame))
return false; return false;
......
...@@ -55,6 +55,14 @@ class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder { ...@@ -55,6 +55,14 @@ class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder {
kError kError
}; };
// Return values for decoding alpha plane.
enum AlphaDecodeStatus {
kAlphaPlaneProcessed, // Alpha plane (if found) was decoded successfully.
kNoAlphaPlaneData, // Alpha plane was found, but decoder did not return any
// data.
kAlphaPlaneError // Fatal error occured when trying to decode alpha plane.
};
// Handles (re-)initializing the decoder with a (new) config. // Handles (re-)initializing the decoder with a (new) config.
// Returns true when initialization was successful. // Returns true when initialization was successful.
bool ConfigureDecoder(const VideoDecoderConfig& config); bool ConfigureDecoder(const VideoDecoderConfig& config);
...@@ -74,8 +82,14 @@ class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder { ...@@ -74,8 +82,14 @@ class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder {
scoped_refptr<VideoFrame>* video_frame); scoped_refptr<VideoFrame>* video_frame);
bool CopyVpxImageToVideoFrame(const struct vpx_image* vpx_image, bool CopyVpxImageToVideoFrame(const struct vpx_image* vpx_image,
const struct vpx_image* vpx_image_alpha,
scoped_refptr<VideoFrame>* video_frame); scoped_refptr<VideoFrame>* video_frame);
AlphaDecodeStatus DecodeAlphaPlane(
const struct vpx_image* vpx_image,
const struct vpx_image** vpx_image_alpha,
const scoped_refptr<DecoderBuffer>& buffer);
base::ThreadChecker thread_checker_; base::ThreadChecker thread_checker_;
// |state_| must only be read and written to on |offload_task_runner_| if it // |state_| must only be read and written to on |offload_task_runner_| if it
......
...@@ -2198,6 +2198,22 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Odd_WebM) { ...@@ -2198,6 +2198,22 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Odd_WebM) {
ASSERT_TRUE(WaitUntilOnEnded()); ASSERT_TRUE(WaitUntilOnEnded());
} }
// Verify that VP9 video with alpha channel can be played back.
TEST_F(PipelineIntegrationTest, BasicPlayback_VP9A_WebM) {
ASSERT_EQ(PIPELINE_OK, Start("bear-vp9a.webm"));
Play();
ASSERT_TRUE(WaitUntilOnEnded());
EXPECT_VIDEO_FORMAT_EQ(last_video_frame_format_, PIXEL_FORMAT_YV12A);
}
// Verify that VP9A video with odd width/height can be played back.
TEST_F(PipelineIntegrationTest, BasicPlayback_VP9A_Odd_WebM) {
ASSERT_EQ(PIPELINE_OK, Start("bear-vp9a-odd-dimensions.webm"));
Play();
ASSERT_TRUE(WaitUntilOnEnded());
EXPECT_VIDEO_FORMAT_EQ(last_video_frame_format_, PIXEL_FORMAT_YV12A);
}
// Verify that VP8 video with inband text track can be played back. // Verify that VP8 video with inband text track can be played back.
TEST_F(PipelineIntegrationTest, MAYBE_TEXT(BasicPlayback_VP8_WebVTT_WebM)) { TEST_F(PipelineIntegrationTest, MAYBE_TEXT(BasicPlayback_VP8_WebVTT_WebM)) {
EXPECT_CALL(*this, OnAddTextTrack(_, _)); EXPECT_CALL(*this, OnAddTextTrack(_, _));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment