Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/common/gpu/media/android_video_encode_accelerator.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/message_loop/message_loop.h" | |
| 10 #include "base/metrics/histogram.h" | |
| 11 #include "content/common/gpu/gpu_channel.h" | |
| 12 #include "gpu/command_buffer/service/gles2_cmd_decoder.h" | |
| 13 #include "media/base/android/media_codec_bridge.h" | |
| 14 #include "media/base/bitstream_buffer.h" | |
| 15 #include "media/base/limits.h" | |
| 16 #include "media/video/picture.h" | |
| 17 #include "third_party/libyuv/include/libyuv/convert_from.h" | |
| 18 #include "ui/gl/android/scoped_java_surface.h" | |
| 19 #include "ui/gl/gl_bindings.h" | |
| 20 | |
| 21 using media::MediaCodecBridge; | |
| 22 using media::VideoCodecBridge; | |
| 23 using media::VideoFrame; | |
| 24 | |
| 25 namespace content { | |
| 26 | |
| 27 enum { | |
| 28 // Subset of MediaCodecInfo.CodecCapabilities. | |
|
xhwang
2013/11/19 00:11:25
Should this be a MediaCodecBridge constant? It'd b
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Maybe; I think the real answer is to convert Media
| |
| 29 kColorFormatYUV420SemiPlanar = 21, | |
|
xhwang
2013/11/19 00:11:25
ditto about enum naming style
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Done.
| |
| 30 }; | |
| 31 | |
| 32 // Helper macros for dealing with failure. If |result| evaluates false, emit | |
| 33 // |log| to DLOG(ERROR), register |error| with the client, and return. | |
| 34 #define RETURN_ON_FAILURE(result, log, error) \ | |
| 35 do { \ | |
| 36 if (!(result)) { \ | |
| 37 DLOG(ERROR) << log; \ | |
| 38 if (client_ptr_factory_.GetWeakPtr()) { \ | |
| 39 client_ptr_factory_.GetWeakPtr()->NotifyError(error); \ | |
| 40 client_ptr_factory_.InvalidateWeakPtrs(); \ | |
| 41 } \ | |
| 42 return; \ | |
| 43 } \ | |
| 44 } while (0) | |
| 45 | |
| 46 static inline const base::TimeDelta EncodePollDelay() { | |
| 47 return base::TimeDelta::FromMilliseconds(10); | |
|
xhwang
2013/11/19 00:11:25
// arbitrary choice?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Done.
| |
| 48 } | |
| 49 | |
| 50 static inline const base::TimeDelta NoWaitTimeOut() { | |
| 51 return base::TimeDelta::FromMicroseconds(0); | |
| 52 } | |
| 53 | |
| 54 AndroidVideoEncodeAccelerator::AndroidVideoEncodeAccelerator( | |
| 55 media::VideoEncodeAccelerator::Client* client) | |
| 56 : client_ptr_factory_(client), | |
| 57 num_buffers_at_codec_(0), | |
| 58 num_output_buffers_(-1), | |
| 59 output_buffers_capacity_(0), | |
| 60 last_set_bitrate_(0) {} | |
| 61 | |
| 62 AndroidVideoEncodeAccelerator::~AndroidVideoEncodeAccelerator() { | |
| 63 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 64 } | |
| 65 | |
| 66 // static | |
| 67 std::vector<media::VideoEncodeAccelerator::SupportedProfile> | |
| 68 AndroidVideoEncodeAccelerator::GetSupportedProfiles() { | |
| 69 std::vector<MediaCodecBridge::CodecsInfo> codecs_info = | |
| 70 MediaCodecBridge::GetCodecsInfo(); | |
| 71 | |
| 72 std::vector<SupportedProfile> profiles; | |
| 73 for (size_t i = 0; i < codecs_info.size(); ++i) { | |
| 74 const MediaCodecBridge::CodecsInfo& info = codecs_info[i]; | |
| 75 if (!info.is_encoder || info.codecs != "vp8" || | |
| 76 VideoCodecBridge::IsKnownUnaccelerated(media::kCodecVP8, true)) { | |
|
xhwang
2013/11/19 00:11:25
Comment that we are only looking for "hardware vp8
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Done.
| |
| 77 continue; | |
| 78 } | |
| 79 SupportedProfile profile; | |
| 80 profile.profile = media::VP8PROFILE_MAIN; | |
| 81 profile.max_resolution.SetSize(1920, 1088); | |
| 82 profile.max_framerate.numerator = 30; | |
|
xhwang
2013/11/19 00:11:25
Where are the max resolution and size coming from?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
I don't think it's worth hoisting these to named c
| |
| 83 profile.max_framerate.denominator = 1; | |
| 84 profiles.push_back(profile); | |
| 85 } | |
| 86 return profiles; | |
| 87 } | |
| 88 | |
| 89 void AndroidVideoEncodeAccelerator::Initialize( | |
| 90 VideoFrame::Format format, const gfx::Size& input_visible_size, | |
| 91 media::VideoCodecProfile output_profile, uint32 initial_bitrate) { | |
| 92 DVLOG(3) << "AndroidVideoEncodeAccelerator::Initialize: format: " << format | |
| 93 << ", input_visible_size: " << input_visible_size.ToString() | |
| 94 << ", output_profile: " << output_profile | |
| 95 << ", initial_bitrate: " << initial_bitrate; | |
| 96 DCHECK(!media_codec_); | |
| 97 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 98 | |
| 99 RETURN_ON_FAILURE(media::MediaCodecBridge::IsAvailable() && | |
| 100 media::MediaCodecBridge::SupportsSetParameters() && | |
| 101 format == VideoFrame::I420 && | |
| 102 output_profile == media::VP8PROFILE_MAIN, | |
| 103 "Unexpected combo: " << format << ", " << output_profile, | |
| 104 kInvalidArgumentError); | |
| 105 | |
| 106 last_set_bitrate_ = initial_bitrate; | |
| 107 | |
| 108 // Only consider using MediaCodec if it's likely backed by hardware. | |
| 109 RETURN_ON_FAILURE( | |
| 110 !media::VideoCodecBridge::IsKnownUnaccelerated(media::kCodecVP8, true), | |
| 111 "No HW support", kPlatformFailureError); | |
| 112 | |
| 113 // TODO(fischman): when there is more HW out there with different color-space | |
| 114 // support, this should turn into a negotiation with the codec for supported | |
| 115 // formats. For now we use the only format supported by the only available | |
| 116 // HW. | |
| 117 media_codec_.reset(media::VideoCodecBridge::CreateEncoder( | |
| 118 media::kCodecVP8, input_visible_size, initial_bitrate, kInitialFramerate, | |
| 119 kIFrameInterval, kColorFormatYUV420SemiPlanar)); | |
| 120 | |
| 121 RETURN_ON_FAILURE(media_codec_, "Failed to create/start the codec: " | |
| 122 << input_visible_size.ToString(), | |
| 123 kPlatformFailureError); | |
| 124 | |
| 125 base::MessageLoop::current()->PostTask( | |
| 126 FROM_HERE, | |
| 127 base::Bind(&VideoEncodeAccelerator::Client::NotifyInitializeDone, | |
| 128 client_ptr_factory_.GetWeakPtr())); | |
| 129 | |
| 130 num_output_buffers_ = media_codec_->GetOutputBuffersCount(); | |
| 131 output_buffers_capacity_ = media_codec_->GetOutputBuffersCapacity(); | |
| 132 base::MessageLoop::current()->PostTask( | |
| 133 FROM_HERE, | |
| 134 base::Bind(&VideoEncodeAccelerator::Client::RequireBitstreamBuffers, | |
| 135 client_ptr_factory_.GetWeakPtr(), num_output_buffers_, | |
| 136 input_visible_size, output_buffers_capacity_)); | |
| 137 } | |
| 138 | |
| 139 void AndroidVideoEncodeAccelerator::MaybeStartIOTimer() { | |
| 140 if (!io_timer_.IsRunning() && | |
| 141 (num_buffers_at_codec_ > 0 || !pending_frames_.empty())) { | |
| 142 io_timer_.Start(FROM_HERE, EncodePollDelay(), this, | |
| 143 &AndroidVideoEncodeAccelerator::DoIOTask); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 void AndroidVideoEncodeAccelerator::MaybeStopIOTimer() { | |
| 148 if (io_timer_.IsRunning() && | |
| 149 (num_buffers_at_codec_ == 0 && pending_frames_.empty())) { | |
| 150 io_timer_.Stop(); | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 void AndroidVideoEncodeAccelerator::Encode( | |
| 155 const scoped_refptr<VideoFrame>& frame, bool force_keyframe) { | |
| 156 DVLOG(3) << "AndroidVideoEncodeAccelerator::Encode: " << force_keyframe; | |
|
xhwang
2013/11/19 00:11:25
Here and below, will __FUNCTION__ work for you?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
__PRETTY_FUNCTION__, even, since we're guaranteed
| |
| 157 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 158 RETURN_ON_FAILURE(frame->format() == VideoFrame::I420, "Unexpected format", | |
| 159 kInvalidArgumentError); | |
| 160 | |
| 161 // MediaCodec doesn't have a way to specify stride for non-Packed formats, so | |
| 162 // we insist on being called with packed frames and no cropping :( | |
| 163 RETURN_ON_FAILURE(frame->row_bytes(VideoFrame::kYPlane) == | |
| 164 frame->stride(VideoFrame::kYPlane) && | |
| 165 frame->row_bytes(VideoFrame::kUPlane) == | |
| 166 frame->stride(VideoFrame::kUPlane) && | |
| 167 frame->row_bytes(VideoFrame::kVPlane) == | |
| 168 frame->stride(VideoFrame::kVPlane) && | |
| 169 gfx::Rect(frame->coded_size()) == frame->visible_rect(), | |
| 170 "Non-packed frame, or visible rect != coded size", | |
| 171 kInvalidArgumentError); | |
| 172 | |
| 173 pending_frames_.push(MakeTuple(frame, force_keyframe, base::Time::Now())); | |
| 174 DoIOTask(); | |
| 175 } | |
| 176 | |
| 177 void AndroidVideoEncodeAccelerator::UseOutputBitstreamBuffer( | |
|
xhwang
2013/11/19 00:11:25
nit comment for the interface: "Use" is more like
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
"Provide" in the decode API is "request", so... ;)
| |
| 178 const media::BitstreamBuffer& buffer) { | |
| 179 DVLOG(3) << "AndroidVideoEncodeAccelerator::UseOutputBitstreamBuffer: " | |
| 180 << "bitstream_buffer_id=" << buffer.id(); | |
| 181 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 182 RETURN_ON_FAILURE(buffer.size() >= media_codec_->GetOutputBuffersCapacity(), | |
| 183 "Output buffers too small!", kInvalidArgumentError); | |
| 184 available_bitstream_buffers_.push_back(buffer); | |
| 185 DoIOTask(); | |
| 186 } | |
| 187 | |
| 188 void AndroidVideoEncodeAccelerator::RequestEncodingParametersChange( | |
| 189 uint32 bitrate, uint32 framerate) { | |
| 190 DVLOG(3) << "AndroidVideoEncodeAccelerator::RequestEncodingParametersChange: " | |
| 191 << "bitrate: " << bitrate << ", framerate: " << framerate; | |
| 192 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 193 if (bitrate != last_set_bitrate_) { | |
| 194 last_set_bitrate_ = bitrate; | |
| 195 media_codec_->SetVideoBitrate(bitrate); | |
| 196 } | |
| 197 // Note: Android's MediaCodec doesn't allow mid-stream adjustments to | |
| 198 // framerate, so we ignore that here. This is OK because Android only uses | |
| 199 // the framerate value from MediaFormat during configure() as a proxy for | |
| 200 // bitrate, and we set that explicitly. | |
|
xhwang
2013/11/19 00:11:25
Will we hit unused variable for |framerate| in rel
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Unused warnings don't apply to function parameters
| |
| 201 } | |
| 202 | |
| 203 void AndroidVideoEncodeAccelerator::Destroy() { | |
| 204 DVLOG(3) << "AndroidVideoEncodeAccelerator::Destroy"; | |
| 205 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 206 client_ptr_factory_.InvalidateWeakPtrs(); | |
| 207 if (media_codec_) { | |
| 208 if (io_timer_.IsRunning()) io_timer_.Stop(); | |
| 209 media_codec_->Stop(); | |
| 210 } | |
| 211 delete this; | |
| 212 } | |
| 213 | |
| 214 void AndroidVideoEncodeAccelerator::DoIOTask() { | |
| 215 QueueInput(); | |
| 216 DequeueOutput(); | |
| 217 MaybeStartIOTimer(); | |
| 218 MaybeStopIOTimer(); | |
| 219 } | |
| 220 | |
| 221 void AndroidVideoEncodeAccelerator::QueueInput() { | |
| 222 if (!client_ptr_factory_.GetWeakPtr() || pending_frames_.empty()) return; | |
|
xhwang
2013/11/19 00:11:25
nit: I like your use of if-return-in-one-line in m
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
I obey clang-format. Wrapped now.
| |
| 223 | |
| 224 int input_buf_index = 0; | |
| 225 media::MediaCodecStatus status = | |
| 226 media_codec_->DequeueInputBuffer(NoWaitTimeOut(), &input_buf_index); | |
| 227 if (status != media::MEDIA_CODEC_OK) { | |
| 228 DCHECK(status == media::MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER || | |
| 229 status == media::MEDIA_CODEC_ERROR); | |
| 230 RETURN_ON_FAILURE(status != media::MEDIA_CODEC_ERROR, "MediaCodec error", | |
| 231 kPlatformFailureError); | |
| 232 return; | |
| 233 } | |
| 234 | |
| 235 const PendingFrames::value_type& input = pending_frames_.front(); | |
| 236 if (input.b) { | |
|
xhwang
2013/11/19 00:11:25
I have to go to the .h file to check what "b" is.
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Done, except I refuse to use bool& instead of bool
| |
| 237 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could | |
| 238 // indicate this in the QueueInputBuffer() call below and guarantee _this_ | |
| 239 // frame be encoded as a key frame, but sadly that flag is ignored. | |
| 240 // Instead, we request a key frame "soon". | |
| 241 media_codec_->RequestKeyFrameSoon(); | |
| 242 } | |
| 243 scoped_refptr<VideoFrame> frame = input.a; | |
| 244 | |
| 245 uint8* buffer = NULL; | |
| 246 size_t capacity = 0; | |
| 247 media_codec_->GetInputBuffer(input_buf_index, &buffer, &capacity); | |
| 248 | |
| 249 size_t queued_size = | |
| 250 frame->AllocationSize(VideoFrame::I420, frame->coded_size()); | |
|
xhwang
2013/11/19 00:11:25
VideoFrame::AllocationSize()?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Done.
| |
| 251 RETURN_ON_FAILURE(capacity >= queued_size, | |
| 252 "Failed to get input buffer: " << input_buf_index, | |
| 253 kPlatformFailureError); | |
| 254 | |
| 255 uint8* dst_y = buffer; | |
| 256 int dst_stride_y = frame->stride(VideoFrame::kYPlane); | |
| 257 uint8* dst_uv = buffer + frame->stride(VideoFrame::kYPlane) * | |
| 258 frame->rows(VideoFrame::kYPlane); | |
| 259 int dst_stride_uv = frame->stride(VideoFrame::kUPlane) * 2; | |
| 260 // Why NV12? Because kColorFormatYUV420SemiPlanar. See comment at other | |
| 261 // mention of that constant. | |
| 262 bool converted = | |
| 263 !libyuv::I420ToNV12( | |
| 264 frame->data(VideoFrame::kYPlane), frame->stride(VideoFrame::kYPlane), | |
| 265 frame->data(VideoFrame::kUPlane), frame->stride(VideoFrame::kUPlane), | |
| 266 frame->data(VideoFrame::kVPlane), frame->stride(VideoFrame::kVPlane), | |
| 267 dst_y, dst_stride_y, dst_uv, dst_stride_uv, | |
| 268 frame->coded_size().width(), frame->coded_size().height()); | |
| 269 RETURN_ON_FAILURE(converted, "Failed to I420ToNV12!", kPlatformFailureError); | |
| 270 | |
| 271 fake_input_timestamp_ += base::TimeDelta::FromMicroseconds(1); | |
| 272 status = media_codec_->QueueInputBuffer(input_buf_index, NULL, queued_size, | |
| 273 fake_input_timestamp_); | |
| 274 UMA_HISTOGRAM_TIMES("Media.AVEA.InputQueueTime", base::Time::Now() - input.c); | |
| 275 RETURN_ON_FAILURE(status == media::MEDIA_CODEC_OK, | |
| 276 "Failed to QueueInputBuffer: " << status, | |
| 277 kPlatformFailureError); | |
| 278 ++num_buffers_at_codec_; | |
| 279 pending_frames_.pop(); | |
| 280 } | |
| 281 | |
| 282 bool AndroidVideoEncodeAccelerator::DoOutputBuffersSuffice() { | |
| 283 // If this returns false ever, then the VEA::Client interface will need to | |
| 284 // grow a DismissBitstreamBuffer() call, and VEA::Client impls will have to be | |
| 285 // prepared to field multiple requests to RequireBitstreamBuffers(). | |
| 286 int count = media_codec_->GetOutputBuffersCount(); | |
| 287 size_t capacity = media_codec_->GetOutputBuffersCapacity(); | |
| 288 bool ret = media_codec_->GetOutputBuffers() && count <= num_output_buffers_ && | |
| 289 capacity <= output_buffers_capacity_; | |
| 290 if (!ret) { | |
| 291 LOG(ERROR) << "Need more/bigger buffers; before: " << num_output_buffers_ | |
|
xhwang
2013/11/19 00:11:25
Use LOG_IF(ERROR, !ret)?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
Oh wow I never use LOG_IF. Done.
| |
| 292 << "x" << output_buffers_capacity_ << ", now: " << count << "x" | |
| 293 << capacity; | |
| 294 } | |
| 295 UMA_HISTOGRAM_BOOLEAN("Media.AVEA.OutputBuffersSuffice", ret); | |
| 296 return ret; | |
| 297 } | |
| 298 | |
| 299 void AndroidVideoEncodeAccelerator::DequeueOutput() { | |
| 300 if (!client_ptr_factory_.GetWeakPtr() || | |
| 301 available_bitstream_buffers_.empty() || num_buffers_at_codec_ == 0) { | |
| 302 return; | |
| 303 } | |
| 304 | |
| 305 int32 buf_index = 0; | |
| 306 size_t offset = 0; | |
| 307 size_t size = 0; | |
| 308 bool key_frame = false; | |
| 309 do { | |
| 310 media::MediaCodecStatus status = media_codec_->DequeueOutputBuffer( | |
| 311 NoWaitTimeOut(), &buf_index, &offset, &size, NULL, NULL, &key_frame); | |
| 312 switch (status) { | |
| 313 case media::MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER: | |
| 314 return; | |
| 315 | |
| 316 case media::MEDIA_CODEC_ERROR: | |
| 317 RETURN_ON_FAILURE(false, "Codec error", kPlatformFailureError); | |
| 318 // Unreachable because of previous statement, but included for clarity. | |
| 319 return; | |
| 320 | |
| 321 case media::MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: // Fall-through. | |
| 322 case media::MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED: | |
| 323 RETURN_ON_FAILURE(DoOutputBuffersSuffice(), | |
| 324 "Bitstream now requires more/larger buffers", | |
| 325 kPlatformFailureError); | |
| 326 break; | |
| 327 | |
| 328 case media::MEDIA_CODEC_OK: | |
| 329 DCHECK_GE(buf_index, 0); | |
| 330 break; | |
| 331 | |
| 332 default: | |
| 333 NOTREACHED(); | |
| 334 break; | |
| 335 } | |
| 336 } while (buf_index < 0); | |
|
xhwang
2013/11/19 00:11:25
In what case would buf_index < 0?
Ami GONE FROM CHROMIUM
2013/11/21 22:59:07
In all of the cases where status!=MEDIA_CODEC_OK.
| |
| 337 | |
| 338 media::BitstreamBuffer bitstream_buffer = available_bitstream_buffers_.back(); | |
| 339 available_bitstream_buffers_.pop_back(); | |
| 340 scoped_ptr<base::SharedMemory> shm( | |
| 341 new base::SharedMemory(bitstream_buffer.handle(), false)); | |
| 342 RETURN_ON_FAILURE(shm->Map(bitstream_buffer.size()), "Failed to map SHM", | |
| 343 kPlatformFailureError); | |
| 344 RETURN_ON_FAILURE(size <= shm->mapped_size(), | |
| 345 "Encoded buffer too large: " << size << ">" | |
| 346 << shm->mapped_size(), | |
| 347 kPlatformFailureError); | |
| 348 | |
| 349 media_codec_->CopyFromOutputBuffer(buf_index, offset, shm->memory(), size); | |
| 350 media_codec_->ReleaseOutputBuffer(buf_index, false); | |
| 351 --num_buffers_at_codec_; | |
| 352 | |
| 353 UMA_HISTOGRAM_COUNTS_10000("Media.AVEA.EncodedBufferSizeKB", size / 1024); | |
| 354 base::MessageLoop::current()->PostTask( | |
| 355 FROM_HERE, | |
| 356 base::Bind(&VideoEncodeAccelerator::Client::BitstreamBufferReady, | |
| 357 client_ptr_factory_.GetWeakPtr(), bitstream_buffer.id(), size, | |
| 358 key_frame)); | |
| 359 } | |
| 360 | |
| 361 } // namespace content | |
| OLD | NEW |