Index: content/renderer/media/video_track_recorder.cc |
diff --git a/content/renderer/media/video_track_recorder.cc b/content/renderer/media/video_track_recorder.cc |
index e5d477a18be2037f4f23a7461e7a8a65cdb9f370..ea69e45ba4799628c19852cf03f4b61f8189d2fa 100644 |
--- a/content/renderer/media/video_track_recorder.cc |
+++ b/content/renderer/media/video_track_recorder.cc |
@@ -15,12 +15,18 @@ |
#include "base/threading/thread_task_runner_handle.h" |
#include "base/time/time.h" |
#include "base/trace_event/trace_event.h" |
+#include "content/common/gpu/client/context_provider_command_buffer.h" |
#include "content/renderer/media/renderer_gpu_video_accelerator_factories.h" |
#include "content/renderer/render_thread_impl.h" |
#include "media/base/bind_to_current_loop.h" |
#include "media/base/video_frame.h" |
#include "media/base/video_util.h" |
-#include "third_party/libyuv/include/libyuv/convert.h" |
+#include "media/filters/context_3d.h" |
+#include "media/renderers/skcanvas_video_renderer.h" |
+#include "skia/ext/platform_canvas.h" |
+#include "third_party/libyuv/include/libyuv.h" |
+#include "third_party/skia/include/core/SkCanvas.h" |
+#include "third_party/skia/include/core/SkSurface.h" |
#include "ui/gfx/geometry/size.h" |
#if BUILDFLAG(RTC_USE_H264) |
@@ -105,8 +111,8 @@ media::VideoCodecProfile CodecIdToVEAProfile( |
// ref-counted to allow the MediaStreamVideoTrack to hold a reference to it (via |
// the callback that MediaStreamVideoSink passes along) and to jump back and |
// forth to an internal encoder thread. Moreover, this class: |
-// - is created and destroyed on its parent's thread (usually the main Render |
-// thread), |main_task_runner_|. |
+// - is created on its parent's thread (usually the main Render thread), |
+// that is, |main_task_runner_|. |
// - receives VideoFrames on |origin_task_runner_| and runs OnEncodedVideoCB on |
// that thread as well. This task runner is cached on first frame arrival, and |
// is supposed to be the render IO thread (but this is not enforced); |
@@ -136,15 +142,21 @@ class VideoTrackRecorder::Encoder : public base::RefCountedThreadSafe<Encoder> { |
// Start encoding |frame|, returning via |on_encoded_video_callback_|. This |
// call will also trigger a ConfigureEncoderOnEncodingTaskRunner() upon first |
// frame arrival or parameter change, and an EncodeOnEncodingTaskRunner() to |
- // actually encode the frame. |
+ // actually encode the frame. If the |frame|'s data is not directly available |
+ // (e.g. it's a texture) then RetrieveFrameOnMainThread() is called, and if |
+ // even that fails, black frames are sent instead. |
void StartFrameEncode(const scoped_refptr<VideoFrame>& frame, |
base::TimeTicks capture_timestamp); |
+ void RetrieveFrameOnMainThread(const scoped_refptr<VideoFrame>& video_frame, |
+ base::TimeTicks capture_timestamp); |
void SetPaused(bool paused); |
protected: |
friend class base::RefCountedThreadSafe<Encoder>; |
- virtual ~Encoder() {} |
+ virtual ~Encoder() { |
+ main_task_runner_->DeleteSoon(FROM_HERE, video_renderer_.release()); |
+ } |
virtual void EncodeOnEncodingTaskRunner( |
const scoped_refptr<VideoFrame>& frame, |
@@ -172,6 +184,11 @@ class VideoTrackRecorder::Encoder : public base::RefCountedThreadSafe<Encoder> { |
// Target bitrate for video encoding. If 0, a standard bitrate is used. |
const int32_t bits_per_second_; |
+ // Used to retrieve incoming opaque VideoFrames (i.e. VideoFrames backed by |
+ // textures). Created on-demand on |main_task_runner_|. |
+ std::unique_ptr<media::SkCanvasVideoRenderer> video_renderer_; |
+ sk_sp<SkSurface> surface_; |
+ |
DISALLOW_COPY_AND_ASSIGN(Encoder); |
}; |
@@ -187,10 +204,19 @@ void VideoTrackRecorder::Encoder::StartFrameEncode( |
if (!(video_frame->format() == media::PIXEL_FORMAT_I420 || |
video_frame->format() == media::PIXEL_FORMAT_YV12 || |
+ video_frame->format() == media::PIXEL_FORMAT_ARGB || |
video_frame->format() == media::PIXEL_FORMAT_YV12A)) { |
- NOTREACHED(); |
+ NOTREACHED() << media::VideoPixelFormatToString(video_frame->format()); |
+ return; |
+ } |
+ |
+ if (video_frame->HasTextures()) { |
+ main_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Encoder::RetrieveFrameOnMainThread, this, |
+ video_frame, capture_timestamp)); |
return; |
} |
+ |
scoped_refptr<media::VideoFrame> frame = video_frame; |
// Drop alpha channel since we do not support it yet. |
if (frame->format() == media::PIXEL_FORMAT_YV12A) |
@@ -201,6 +227,84 @@ void VideoTrackRecorder::Encoder::StartFrameEncode( |
capture_timestamp)); |
} |
+void VideoTrackRecorder::Encoder::RetrieveFrameOnMainThread( |
+ const scoped_refptr<VideoFrame>& video_frame, |
+ base::TimeTicks capture_timestamp) { |
+ DCHECK(main_task_runner_->BelongsToCurrentThread()); |
+ |
+ scoped_refptr<media::VideoFrame> frame; |
+ |
+ // |context_provider| is null if the GPU process has crashed or isn't there. |
+ ContextProviderCommandBuffer* const context_provider = |
+ RenderThreadImpl::current()->SharedMainThreadContextProvider().get(); |
+ if (!context_provider) { |
+ // Send black frames (yuv = {0, 127, 127}). |
+ frame = media::VideoFrame::CreateColorFrame( |
+ video_frame->visible_rect().size(), 0u, 0x80, 0x80, |
+ video_frame->timestamp()); |
+ } else { |
+ // Accelerated decoders produce ARGB/ABGR texture-backed frames (see |
+ // https://crbug.com/585242), fetch them using a SkCanvasVideoRenderer. |
+ DCHECK(video_frame->HasTextures()); |
+ DCHECK_EQ(media::PIXEL_FORMAT_ARGB, video_frame->format()); |
+ |
+ frame = media::VideoFrame::CreateFrame( |
+ media::PIXEL_FORMAT_I420, video_frame->coded_size(), |
+ video_frame->visible_rect(), video_frame->natural_size(), |
+ video_frame->timestamp()); |
+ |
+ const SkImageInfo info = SkImageInfo::MakeN32( |
+ frame->visible_rect().width(), frame->visible_rect().height(), |
+ kOpaque_SkAlphaType); |
+ |
+ // Create |surface_| if it doesn't exist or incoming resolution has changed. |
+ if (!surface_ || surface_->width() != info.width() || |
+ surface_->height() != info.height()) { |
+ surface_ = SkSurface::MakeRaster(info); |
+ } |
+ if (!video_renderer_) |
+ video_renderer_.reset(new media::SkCanvasVideoRenderer); |
+ |
+ DCHECK(context_provider->ContextGL()); |
+ video_renderer_->Copy(video_frame.get(), surface_->getCanvas(), |
+ media::Context3D(context_provider->ContextGL(), |
+ context_provider->GrContext())); |
+ |
+ SkPixmap pixmap; |
+ if (!skia::GetWritablePixels(surface_->getCanvas(), &pixmap)) { |
+ DLOG(ERROR) << "Error trying to map SkSurface's pixels"; |
+ return; |
+ } |
+ // TODO(mcasas): Use the incoming frame's rotation when |
+ // https://bugs.chromium.org/p/webrtc/issues/detail?id=6069 is closed. |
+ const libyuv::RotationMode source_rotation = libyuv::kRotate0; |
+ const uint32 source_pixel_format = |
+ (kN32_SkColorType == kRGBA_8888_SkColorType) ? libyuv::FOURCC_ABGR |
+ : libyuv::FOURCC_ARGB; |
+ if (libyuv::ConvertToI420(static_cast<uint8*>(pixmap.writable_addr()), |
+ pixmap.getSafeSize(), |
+ frame->visible_data(media::VideoFrame::kYPlane), |
+ frame->stride(media::VideoFrame::kYPlane), |
+ frame->visible_data(media::VideoFrame::kUPlane), |
+ frame->stride(media::VideoFrame::kUPlane), |
+ frame->visible_data(media::VideoFrame::kVPlane), |
+ frame->stride(media::VideoFrame::kVPlane), |
+ 0 /* crop_x */, 0 /* crop_y */, |
+ pixmap.width(), pixmap.height(), |
+ frame->visible_rect().width(), |
+ frame->visible_rect().height(), |
+ source_rotation, |
+ source_pixel_format) != 0) { |
+ DLOG(ERROR) << "Error converting frame to I420"; |
+ return; |
+ } |
+ } |
+ |
+ encoding_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Encoder::EncodeOnEncodingTaskRunner, this, frame, |
+ capture_timestamp)); |
+} |
+ |
void VideoTrackRecorder::Encoder::SetPaused(bool paused) { |
if (!encoding_task_runner_->BelongsToCurrentThread()) { |
encoding_task_runner_->PostTask( |