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_decode_accelerator.h" | |
6 | |
7 #include <jni.h> | |
8 | |
9 #include "base/android/jni_android.h" | |
10 #include "base/android/scoped_java_ref.h" | |
11 #include "base/bind.h" | |
12 #include "base/debug/trace_event.h" | |
13 #include "base/logging.h" | |
14 #include "base/stl_util.h" | |
15 #include "base/string_util.h" | |
16 #include "content/common/android/surface_callback.h" | |
17 #include "content/common/gpu/gpu_channel.h" | |
18 #include "content/common/gpu/media/gles2_external_texture_copier.h" | |
19 #include "media/base/android/media_codec_bridge.h" | |
20 #include "media/base/bitstream_buffer.h" | |
21 #include "media/video/picture.h" | |
22 #include "third_party/angle/include/GLES2/gl2.h" | |
23 #include "third_party/angle/include/GLES2/gl2ext.h" | |
24 | |
25 using base::android::MethodID; | |
26 using base::android::ScopedJavaLocalRef; | |
27 | |
28 namespace content { | |
29 | |
30 #define LOG_LINE() VLOG(1) << __FUNCTION__ | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
drop before submitting?
dwkang1
2013/01/28 14:54:30
Marked with XXX.
| |
31 | |
32 enum { kNumPictureBuffers = 4 }; | |
33 | |
34 // TODO(dwkang) : For now, we are using very short timeouts for dequeueing | |
35 // buffers in order to prevent dequeueing for input from blocking it for output, | |
36 // and vice versa. Decoupling input and output procedure and using a longer | |
37 // timeout value may improve performance. | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
See my comment on the bridge class; I hope you can
dwkang1
2013/01/28 14:54:30
Done.
| |
38 enum { kDequeueInputBufferTimeOutUs = 10 }; | |
39 enum { kDequeueOutputBufferTimeOutUs = 10 }; | |
40 | |
41 // static | |
42 const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay = | |
43 base::TimeDelta::FromMilliseconds(10); | |
44 | |
45 AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator( | |
46 media::VideoDecodeAccelerator::Client* client, | |
47 const base::Callback<bool(void)>& make_context_current) | |
48 : message_loop_(MessageLoop::current()), | |
49 client_(client), | |
50 make_context_current_(make_context_current), | |
51 codec_(UNKNOWN), | |
52 state_(NO_ERROR), | |
53 surface_texture_id_(0), | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
-1 to be more obvious?
dwkang1
2013/01/28 14:54:30
Done.
| |
54 picturebuffer_requested_(false), | |
55 color_format_(0), | |
56 width_(0), | |
57 height_(0), | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
should be able to drop once using gfx::Size (which
dwkang1
2013/01/28 14:54:30
Removed.
| |
58 current_bitstream_id_(-1) { | |
59 LOG_LINE(); | |
60 } | |
61 | |
62 AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() { | |
63 LOG_LINE(); | |
64 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
65 } | |
66 | |
67 bool AndroidVideoDecodeAccelerator::Initialize( | |
68 media::VideoCodecProfile profile) { | |
69 LOG_LINE(); | |
70 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
71 | |
72 if (profile == media::VP8PROFILE_MAIN) { | |
73 codec_ = VP8; | |
74 } else if (profile >= media::H264PROFILE_MIN | |
75 && profile <= media::H264PROFILE_MAX) { | |
76 codec_ = H264; | |
77 }else { | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
missing space
dwkang1
2013/01/28 14:54:30
Done.
| |
78 LOG(ERROR) << "Unsupported profile: " << profile; | |
79 return false; | |
80 } | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Once you pass profile to ConfigureMediaCodec below
dwkang1
2013/01/28 14:54:30
codec_ is used when re-configuring MediaCodec.
| |
81 | |
82 if (media_codec_ == NULL) { | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
How could this *not* be NULL?
dwkang1
2013/01/28 14:54:30
Changed to DCHECK.
| |
83 if (!make_context_current_.Run()) { | |
84 LOG(ERROR) << "Failed to make this decoder's GL context current."; | |
85 return false; | |
86 } | |
87 glGenTextures(1, &surface_texture_id_); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Please make sure this file follows the (brand-new)
dwkang1
2013/01/28 14:54:30
Let me address this issue once we finalize how we
| |
88 glActiveTexture(GL_TEXTURE0); | |
89 glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_); | |
90 | |
91 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
92 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
93 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
94 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
95 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
96 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
97 | |
98 surface_texture_ = new SurfaceTextureBridge(surface_texture_id_); | |
99 | |
100 ConfigureMediaCodec(); | |
101 } | |
102 | |
103 message_loop_->PostTask( | |
104 FROM_HERE, | |
105 base::Bind( | |
106 &AndroidVideoDecodeAccelerator::DoDecode, base::Unretained(this))); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Per below, drop.
dwkang1
2013/01/28 14:54:30
Done.
| |
107 | |
108 if (client_) | |
109 client_->NotifyInitializeDone(); | |
110 return true; | |
111 } | |
112 | |
113 void AndroidVideoDecodeAccelerator::DoDecode() { | |
114 if (state_ == NO_ERROR) { | |
115 QueueInput(); | |
116 DequeueOutput(); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Shouldn't you drain the output first, to free up d
dwkang1
2013/01/28 14:54:30
Makes sense. Changed.
| |
117 } | |
118 | |
119 message_loop_->PostDelayedTask( | |
120 FROM_HERE, | |
121 base::Bind( | |
122 &AndroidVideoDecodeAccelerator::DoDecode, base::Unretained(this)), | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Why is Unretained(this) safe here (and also at l.1
dwkang1
2013/01/28 14:54:30
You are right. Changed to base::AsWeakPtr().
| |
123 kDecodePollDelay); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Oh my goodness... I didn't realize MediaCodec re
dwkang1
2013/01/28 14:54:30
with 10ms, cost was about 3% of cpu rate.
| |
124 } | |
125 | |
126 void AndroidVideoDecodeAccelerator::QueueInput() { | |
127 if (!pending_bitstream_buffers_.empty()) { | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
if you reverse the test you can early-return and d
dwkang1
2013/01/28 14:54:30
Done.
| |
128 int input_buf_index = | |
129 media_codec_->DequeueInputBuffer(kDequeueInputBufferTimeOutUs); | |
130 if (input_buf_index < 0) { | |
131 return; | |
132 } | |
133 media::BitstreamBuffer& bitstream_buffer = | |
134 pending_bitstream_buffers_.front(); | |
135 pending_bitstream_buffers_.pop(); | |
136 | |
137 int flags = 0; | |
138 if (bitstream_buffer.id() == -1) { | |
139 flags |= media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM; | |
140 } | |
141 if (bitstream_buffer.size() > 0) { | |
142 scoped_ptr<base::SharedMemory> shm( | |
143 new base::SharedMemory(bitstream_buffer.handle(), true)); | |
144 if (!shm->Map(bitstream_buffer.size())) { | |
145 LOG(ERROR) << "Failed to SharedMemory::Map()"; | |
146 client_->NotifyError(UNREADABLE_INPUT); | |
147 state_ = ERROR; | |
148 return; | |
149 } | |
150 media_codec_->PutToInputBuffer( | |
151 input_buf_index, | |
152 static_cast<const uint8*>(shm->memory()), | |
153 bitstream_buffer.size()); | |
154 } | |
155 // Abuse the presentation time argument to propagate the bitstream | |
156 // buffer ID to the output, so we can report it back to the client in | |
157 // PictureReady(). | |
158 int64 timestamp = bitstream_buffer.id(); | |
159 media_codec_->QueueInputBuffer( | |
160 input_buf_index, 0, bitstream_buffer.size(), timestamp, flags); | |
161 | |
162 if (bitstream_buffer.id() != -1) { | |
163 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This is wrong; you should only NotifyEndOfBitstrea
dwkang1
2013/01/28 14:54:30
When I wrote this code, I referred OVDA code. In O
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
See my comment on the next patchset.
| |
164 } | |
165 } | |
166 } | |
167 | |
168 void AndroidVideoDecodeAccelerator::DequeueOutput() { | |
169 if (picturebuffer_requested_ && picture_map_.empty()) { | |
170 DLOG(INFO) << "Picture buffers are not ready."; | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
drop
dwkang1
2013/01/28 14:54:30
Done.
| |
171 return; | |
172 } | |
173 if (!picture_map_.empty() && free_picture_ids_.empty()) { | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Drop the picture_map_.empty() clause?
SendCurrentS
dwkang1
2013/01/28 14:54:30
Before picturebuffers is requested, free_picture_i
| |
174 // Don't have any picture buffer to send. Need to wait more. | |
175 return; | |
176 } | |
177 | |
178 int32 output_offset = 0; | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
this and the other params below don't seem to need
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Declare vars at first use unless there's a reason
dwkang1
2013/01/28 14:54:30
Done.
dwkang1
2013/01/28 14:54:30
Done.
| |
179 int32 output_size = 0; | |
180 int32 output_flag = 0; | |
181 int64 timestamp = 0; | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
bitstream_buffer_id
dwkang1
2013/01/28 14:54:30
Done.
| |
182 int32 output_buf_index = 0; | |
183 do { | |
184 output_buf_index = media_codec_->DequeueOutputBuffer( | |
185 kDequeueOutputBufferTimeOutUs, &output_offset, &output_size, | |
186 ×tamp, &output_flag); | |
187 switch (output_buf_index) { | |
188 case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER: | |
189 return; | |
190 | |
191 case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: | |
192 media_codec_->GetOutputFormat(&color_format_, &width_, &height_); | |
193 DLOG(INFO) << "Output color format: " << color_format_; | |
194 DLOG(INFO) << "Output size: " << width_ << "x" << height_; | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Drop these two lines
dwkang1
2013/01/28 14:54:30
Done.
| |
195 if (!picturebuffer_requested_) { | |
196 picturebuffer_requested_ = true; | |
197 texture_copier_.reset(new Gles2ExternalTextureCopier()); | |
198 texture_copier_->Init(width_, height_); | |
199 client_->ProvidePictureBuffers( | |
200 kNumPictureBuffers, | |
201 gfx::Size(width_, height_), | |
202 GL_TEXTURE_2D); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
If you requested a different texture target could
dwkang1
2013/01/28 14:54:30
Actually, using SurfaceTexure.attachToGLContext()
| |
203 } | |
204 // TODO(dwkang): support the dynamic resolution change. | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
set state to ERROR and NotifyError?
dwkang1
2013/01/28 14:54:30
Done.
| |
205 return; | |
206 | |
207 case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED: | |
208 media_codec_->GetOutputBuffers(); | |
209 break; | |
210 } | |
211 } while (output_buf_index < 0); | |
212 | |
213 if (output_flag & media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM) { | |
214 if (client_) { | |
215 client_->NotifyFlushDone(); | |
216 } | |
217 } | |
218 | |
219 media_codec_->ReleaseOutputBuffer(output_buf_index, true); | |
220 current_bitstream_id_ = static_cast<int32>(timestamp); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This doesn't need to be a class member; you can ju
dwkang1
2013/01/28 14:54:30
Done.
| |
221 if (current_bitstream_id_ != -1) { | |
222 SendCurrentSurfaceToClient(); | |
223 } | |
224 } | |
225 | |
226 void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient() { | |
227 LOG_LINE(); | |
228 | |
229 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
230 DCHECK_NE(current_bitstream_id_, -1); | |
231 DCHECK(!free_picture_ids_.empty()); | |
232 | |
233 int32 picture_buffer_id = free_picture_ids_.front(); | |
234 free_picture_ids_.pop(); | |
235 | |
236 if (!make_context_current_.Run()) { | |
237 LOG(ERROR) << "Failed to make this decoder's GL context current."; | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Shouldn't this set the state to ERROR and also Not
dwkang1
2013/01/28 14:54:30
Done.
| |
238 return; | |
239 } | |
240 | |
241 float mtx[16]; | |
242 surface_texture_->UpdateTexImage(); | |
243 surface_texture_->GetTransformMatrix(mtx); | |
244 CopyCurrentFrameToPictureBuffer(picture_buffer_id, mtx); | |
245 | |
246 client_->PictureReady( | |
247 media::Picture(picture_buffer_id, current_bitstream_id_)); | |
248 current_bitstream_id_ = -1; | |
249 } | |
250 | |
251 void AndroidVideoDecodeAccelerator::CopyCurrentFrameToPictureBuffer( | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
single call-site + simple impl == inline into call
dwkang1
2013/01/28 14:54:30
I think it depends on how we will define "simple",
| |
252 int32 picture_buffer_id, float transfrom_matrix[16]) { | |
253 PictureMap::const_iterator i = picture_map_.find(picture_buffer_id); | |
254 if (i == picture_map_.end()) { | |
255 LOG(ERROR) << "Can't find a PuctureBuffer for " << picture_buffer_id; | |
256 return; | |
257 } | |
258 uint32 picture_buffer_texture_id = i->second.texture_id(); | |
259 texture_copier_->Copy(surface_texture_id_, GL_TEXTURE_EXTERNAL_OES, | |
260 transfrom_matrix, | |
261 picture_buffer_texture_id, GL_TEXTURE_2D); | |
262 } | |
263 | |
264 void AndroidVideoDecodeAccelerator::Decode( | |
265 const media::BitstreamBuffer& bitstream_buffer) { | |
266 LOG_LINE(); | |
267 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
268 if (!client_) { | |
269 return; | |
270 } | |
271 pending_bitstream_buffers_.push(bitstream_buffer); | |
272 } | |
273 | |
274 void AndroidVideoDecodeAccelerator::AssignPictureBuffers( | |
275 const std::vector<media::PictureBuffer>& buffers) { | |
276 LOG_LINE(); | |
277 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
278 DCHECK(picture_map_.empty()); | |
279 | |
280 for (size_t i = 0; i < buffers.size(); ++i) { | |
281 picture_map_.insert(std::make_pair(buffers[i].id(), buffers[i])); | |
282 free_picture_ids_.push(buffers[i].id()); | |
283 } | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Add:
if (picture_map_.size() != kNumPictureBuffers
dwkang1
2013/01/28 14:54:30
Done.
| |
284 } | |
285 | |
286 void AndroidVideoDecodeAccelerator::ReusePictureBuffer( | |
287 int32 picture_buffer_id) { | |
288 LOG_LINE(); | |
289 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
290 free_picture_ids_.push(picture_buffer_id); | |
291 } | |
292 | |
293 void AndroidVideoDecodeAccelerator::Flush() { | |
294 LOG_LINE(); | |
295 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
296 | |
297 Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0)); | |
298 } | |
299 | |
300 void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() { | |
301 DCHECK(surface_texture_.get()); | |
302 DCHECK(codec_ == H264 || codec_ == VP8); | |
303 | |
304 std::string mime; | |
305 if (codec_ == VP8) { | |
306 mime = "video/x-vnd.on2.vp8"; | |
307 } else if (codec_ == H264) { | |
308 mime = "video/avc"; | |
309 } else { | |
310 LOG(ERROR) << "Unsupported codec type " << codec_; | |
311 NOTREACHED(); | |
312 } | |
313 media_codec_.reset(new media::MediaCodecBridge(mime)); | |
314 | |
315 JNIEnv* env = base::android::AttachCurrentThread(); | |
316 CHECK(env); | |
317 ScopedJavaLocalRef<jclass> cls( | |
318 base::android::GetClass(env, "android/view/Surface")); | |
319 jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>( | |
320 env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V"); | |
321 ScopedJavaLocalRef<jobject> j_surface( | |
322 env, env->NewObject( | |
323 cls.obj(), constructor, | |
324 surface_texture_->j_surface_texture().obj())); | |
325 | |
326 // VDA does not pass the container indicated resolution in the initialization | |
327 // phase. Here, we set 1080p by default. | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This is a mistake that is likely to hide bugs, IMO
dwkang1
2013/01/28 14:54:30
Actually, that was my first try, but it lead to Ex
| |
328 media_codec_->ConfigureVideo( | |
329 mime, 1920, 1080, NULL, 0, NULL, 0, j_surface.obj()); | |
330 content::ReleaseSurface(j_surface.obj()); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
How does this work? Didn't you just ask MediaCode
dwkang1
2013/01/28 14:54:30
If Surface is not registered to the windows manage
| |
331 | |
332 media_codec_->Start(); | |
333 media_codec_->GetInputBuffers(); | |
334 media_codec_->GetOutputBuffers(); | |
335 } | |
336 | |
337 void AndroidVideoDecodeAccelerator::Reset() { | |
338 LOG_LINE(); | |
339 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
340 | |
341 while(!pending_bitstream_buffers_.empty()) { | |
342 media::BitstreamBuffer& bitstream_buffer = | |
343 pending_bitstream_buffers_.front(); | |
344 pending_bitstream_buffers_.pop(); | |
345 | |
346 if (bitstream_buffer.id() != -1) { | |
347 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
I don't think the VDA interface requires this (and
dwkang1
2013/01/28 14:54:30
Actually, I referred OVDA code when I wrote this c
| |
348 } | |
349 } | |
350 media_codec_->Flush(); | |
351 media_codec_->Stop(); | |
352 media_codec_->Release(); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Why the Stop/Release/ConfigureMediaCodec dance?
dwkang1
2013/01/28 14:54:30
It's a recommended way to reset MediaCodec. FWIW,
| |
353 ConfigureMediaCodec(); | |
354 state_ = NO_ERROR; | |
355 | |
356 if (client_) { | |
357 client_->NotifyResetDone(); | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
For complicated reasons, it's not OK to make clien
dwkang1
2013/01/28 14:54:30
Done.
| |
358 } | |
359 } | |
360 | |
361 void AndroidVideoDecodeAccelerator::Destroy() { | |
362 LOG_LINE(); | |
363 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
364 } | |
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This method needs to
delete this;
or else you hav
dwkang1
2013/01/28 14:54:30
Done.
| |
365 | |
366 } // namespace content | |
OLD | NEW |