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/logging.h" | |
13 #include "base/message_loop.h" | |
14 #include "content/common/android/surface_callback.h" | |
15 #include "content/common/gpu/gpu_channel.h" | |
16 #include "content/common/gpu/media/gles2_external_texture_copier.h" | |
17 #include "media/base/bitstream_buffer.h" | |
18 #include "media/video/picture.h" | |
19 #include "third_party/angle/include/GLES2/gl2.h" | |
20 #include "third_party/angle/include/GLES2/gl2ext.h" | |
21 | |
22 using base::android::MethodID; | |
23 using base::android::ScopedJavaLocalRef; | |
24 | |
25 namespace content { | |
26 | |
27 // XXX: drop the below before submitting. | |
28 #define LOG_LINE() LOG(INFO) << __FUNCTION__ | |
29 | |
30 #undef DCHECK | |
31 #define DCHECK CHECK | |
32 | |
33 // Helper macros for dealing with failure. If |result| evaluates false, emit | |
34 // |log| to ERROR, register |error| with the decoder, and return. | |
35 #define RETURN_ON_FAILURE(result, log, error) \ | |
36 do { \ | |
37 if (!(result)) { \ | |
38 LOG(ERROR) << log; \ | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
DLOG instead of LOG
dwkang1
2013/02/04 14:08:26
Done.
| |
39 client_->NotifyError(error); \ | |
40 state_ = ERROR; \ | |
41 return; \ | |
42 } \ | |
43 } while (0) | |
44 | |
45 enum { kNumPictureBuffers = 4 }; | |
46 | |
47 // static | |
48 const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay = | |
49 base::TimeDelta::FromMilliseconds(10); | |
50 | |
51 AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator( | |
52 media::VideoDecodeAccelerator::Client* client, | |
53 const base::Callback<bool(void)>& make_context_current) | |
54 : client_(client), | |
55 make_context_current_(make_context_current), | |
56 codec_(media::MediaCodecBridge::UNKNOWN), | |
57 state_(NO_ERROR), | |
58 surface_texture_id_(-1), | |
59 picturebuffers_requested_(false), | |
60 io_task_is_running_(false) { | |
61 LOG_LINE(); | |
62 } | |
63 | |
64 AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() { | |
65 LOG_LINE(); | |
66 DCHECK(thread_checker_.CalledOnValidThread()); | |
67 } | |
68 | |
69 bool AndroidVideoDecodeAccelerator::Initialize( | |
70 media::VideoCodecProfile profile) { | |
71 LOG_LINE(); | |
72 DCHECK(media_codec_ == NULL); | |
73 DCHECK(thread_checker_.CalledOnValidThread()); | |
74 | |
75 if (profile == media::VP8PROFILE_MAIN) { | |
76 codec_ = media::MediaCodecBridge::VIDEO_VP8; | |
77 } else if (profile >= media::H264PROFILE_MIN | |
78 && profile <= media::H264PROFILE_MAX) { | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
&& operator goes on previous line (here and elsewh
dwkang1
2013/02/04 14:08:26
Done.
| |
79 codec_ = media::MediaCodecBridge::VIDEO_H264; | |
80 } else { | |
81 codec_ = media::MediaCodecBridge::UNKNOWN; | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
unnecessary
dwkang1
2013/02/04 14:08:26
Done.
| |
82 LOG(ERROR) << "Unsupported profile: " << profile; | |
83 return false; | |
84 } | |
85 | |
86 if (!make_context_current_.Run()) { | |
87 LOG(ERROR) << "Failed to make this decoder's GL context current."; | |
88 return false; | |
89 } | |
90 // XXX: apply the scheme for GL access. http://crbug.com/169433 | |
91 glGenTextures(1, &surface_texture_id_); | |
92 glActiveTexture(GL_TEXTURE0); | |
93 glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_); | |
94 | |
95 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
96 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
97 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
98 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
99 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
100 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
101 | |
102 surface_texture_ = new SurfaceTextureBridge(surface_texture_id_); | |
103 | |
104 ConfigureMediaCodec(); | |
105 | |
106 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | |
107 &AndroidVideoDecodeAccelerator::NotifyInitializeDone, | |
108 base::AsWeakPtr(this))); | |
109 return true; | |
110 } | |
111 | |
112 void AndroidVideoDecodeAccelerator::DoIOTask() { | |
113 if (state_ == NO_ERROR) { | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
If this is false, then you don't want to enqueue m
dwkang1
2013/02/04 14:08:26
Done.
| |
114 DequeueOutput(); | |
115 QueueInput(); | |
116 } | |
117 | |
118 if (!pending_bitstream_buffers_.empty() | |
119 || !bitstream_buffer_ids_in_decoder_.empty()) { | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
operator goes on previous line
dwkang1
2013/02/04 14:08:26
Done.
| |
120 MessageLoop::current()->PostDelayedTask( | |
121 FROM_HERE, | |
122 base::Bind( | |
123 &AndroidVideoDecodeAccelerator::DoIOTask, base::AsWeakPtr(this)), | |
124 kDecodePollDelay); | |
125 } else { | |
126 io_task_is_running_ = false; | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
IMO this would be more clear as io_task_is_posted_
dwkang1
2013/02/04 14:08:26
Good idea. Thanks,
| |
127 } | |
128 } | |
129 | |
130 void AndroidVideoDecodeAccelerator::QueueInput() { | |
131 if (pending_bitstream_buffers_.empty()) { | |
132 return; | |
133 } | |
134 int input_buf_index = | |
135 media_codec_->DequeueInputBuffer(0); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
wrapping necessary?
dwkang1
2013/02/04 14:08:26
Done.
| |
136 if (input_buf_index < 0) { | |
137 return; | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
DCHECK_EQ TRY_AGAIN_LATER?
dwkang1
2013/02/04 14:08:26
Done.
| |
138 } | |
139 media::BitstreamBuffer& bitstream_buffer = | |
140 pending_bitstream_buffers_.front(); | |
141 pending_bitstream_buffers_.pop(); | |
142 | |
143 int flags = 0; | |
144 if (bitstream_buffer.id() == -1) { | |
145 flags |= media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM; | |
146 } | |
147 if (bitstream_buffer.size() > 0) { | |
148 scoped_ptr<base::SharedMemory> shm( | |
149 new base::SharedMemory(bitstream_buffer.handle(), true)); | |
150 | |
151 RETURN_ON_FAILURE(shm->Map(bitstream_buffer.size()), | |
152 "Failed to SharedMemory::Map()", | |
153 UNREADABLE_INPUT); | |
154 | |
155 media_codec_->PutToInputBuffer( | |
156 input_buf_index, | |
157 static_cast<const uint8*>(shm->memory()), | |
158 bitstream_buffer.size()); | |
159 } | |
160 // Abuse the presentation time argument to propagate the bitstream | |
161 // buffer ID to the output, so we can report it back to the client in | |
162 // PictureReady(). | |
163 int64 timestamp = bitstream_buffer.id(); | |
164 media_codec_->QueueInputBuffer( | |
165 input_buf_index, 0, bitstream_buffer.size(), timestamp, flags); | |
166 | |
167 bitstream_buffer_ids_in_decoder_.push(bitstream_buffer.id()); | |
168 if (bitstream_buffer.id() != -1) { | |
169 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
This is wrong - NEOBB() must only be called once i
dwkang1
2013/01/29 03:58:48
Got it. Seems that I misunderstood the meaning of
dwkang1
2013/02/04 14:08:26
I checked GpuVideoDecoder implementation and have
| |
170 } | |
171 } | |
172 | |
173 void AndroidVideoDecodeAccelerator::DequeueOutput() { | |
174 if (picturebuffers_requested_ && output_picture_buffers_.empty()) { | |
175 return; | |
176 } | |
177 if (!output_picture_buffers_.empty() && free_picture_ids_.empty()) { | |
178 // Don't have any picture buffer to send. Need to wait more. | |
179 return; | |
180 } | |
181 | |
182 int32 flag = 0; | |
183 int64 bitstream_buffer_id = 0; | |
184 int32 buf_index = 0; | |
185 do { | |
186 int32 offset = 0; | |
187 int32 size = 0; | |
188 buf_index = media_codec_->DequeueOutputBuffer( | |
189 0, &offset, &size, &bitstream_buffer_id, &flag); | |
190 switch (buf_index) { | |
191 case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER: { | |
192 return; | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
braces are unnecessary (here and elsewhere in case
dwkang1
2013/02/04 14:08:26
Done.
| |
193 } | |
194 | |
195 case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: { | |
196 int32 unused_color_format, width, height; | |
197 media_codec_->GetOutputFormat(&unused_color_format, &width, &height); | |
198 | |
199 if (!picturebuffers_requested_) { | |
200 picturebuffers_requested_ = true; | |
201 size_ = gfx::Size(width, height); | |
202 texture_copier_.reset(new Gles2ExternalTextureCopier()); | |
203 texture_copier_->Init(width, height); | |
204 client_->ProvidePictureBuffers( | |
205 kNumPictureBuffers, | |
206 size_, | |
207 GL_TEXTURE_2D); | |
208 } else { | |
209 // TODO(dwkang): support the dynamic resolution change. | |
210 RETURN_ON_FAILURE(size_.width() == width && size_.height() == height, | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
size_ == gfx::Size(width, height)
might be more re
dwkang1
2013/02/04 14:08:26
Done.
| |
211 "Dynamic resolution change is not supported.", | |
212 PLATFORM_FAILURE); | |
213 } | |
214 return; | |
215 } | |
216 | |
217 case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED: { | |
218 media_codec_->GetOutputBuffers(); | |
219 break; | |
220 } | |
221 } | |
222 } while (buf_index < 0); | |
223 | |
224 if (flag & media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM) { | |
225 if (client_) { | |
226 client_->NotifyFlushDone(); | |
227 } | |
228 } | |
229 | |
230 media_codec_->ReleaseOutputBuffer(buf_index, true); | |
231 | |
232 CHECK_EQ(bitstream_buffer_ids_in_decoder_.front(), bitstream_buffer_id); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
Presentation order does not have to match decode o
dwkang1
2013/02/04 14:08:26
Agreed. Haste made waste.
| |
233 bitstream_buffer_ids_in_decoder_.pop(); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
It's also wrong to assume that pictures & bitstrea
dwkang1
2013/02/04 14:08:26
You are correct. removed.
| |
234 | |
235 if (bitstream_buffer_id != -1) { | |
236 SendCurrentSurfaceToClient(static_cast<int32>(bitstream_buffer_id)); | |
237 } | |
238 } | |
239 | |
240 void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient( | |
241 int32 bitstream_id) { | |
242 DCHECK(thread_checker_.CalledOnValidThread()); | |
243 DCHECK_NE(bitstream_id, -1); | |
244 DCHECK(!free_picture_ids_.empty()); | |
245 | |
246 int32 picture_buffer_id = free_picture_ids_.front(); | |
247 free_picture_ids_.pop(); | |
248 | |
249 RETURN_ON_FAILURE(make_context_current_.Run(), | |
250 "Failed to make this decoder's GL context current.", | |
251 PLATFORM_FAILURE); | |
252 | |
253 float transfrom_matrix[16]; | |
254 surface_texture_->UpdateTexImage(); | |
255 surface_texture_->GetTransformMatrix(transfrom_matrix); | |
256 | |
257 OutputBufferMap::const_iterator i = | |
258 output_picture_buffers_.find(picture_buffer_id); | |
259 if (i == output_picture_buffers_.end()) { | |
260 LOG(ERROR) << "Can't find a PuctureBuffer for " << picture_buffer_id; | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
typo: PuctureBuffer
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
RETURN_ON_FAILURE
dwkang1
2013/02/04 14:08:26
Done.
dwkang1
2013/02/04 14:08:26
Done.
| |
261 return; | |
262 } | |
263 uint32 picture_buffer_texture_id = i->second.texture_id(); | |
264 | |
265 // Here, we copy |surface_texture_id_| to the picture buffer instead of | |
266 // setting new texture to |surface_texture_| by calling attachToGLContext() | |
267 // because: | |
268 // 1. Once we call detachFrameGLContext(), it deletes the texture previous | |
269 // attached. | |
270 // 2. SurfaceTexture requires us to apply a transform matrix when we show | |
271 // the texture. | |
272 texture_copier_->Copy( | |
273 surface_texture_id_, picture_buffer_texture_id, transfrom_matrix); | |
274 | |
275 client_->PictureReady( | |
276 media::Picture(picture_buffer_id, bitstream_id)); | |
277 } | |
278 | |
279 void AndroidVideoDecodeAccelerator::Decode( | |
280 const media::BitstreamBuffer& bitstream_buffer) { | |
281 LOG_LINE(); | |
282 DCHECK(thread_checker_.CalledOnValidThread()); | |
283 if (!client_) { | |
284 return; | |
285 } | |
286 pending_bitstream_buffers_.push(bitstream_buffer); | |
287 | |
288 if (!io_task_is_running_) { | |
289 io_task_is_running_ = true; | |
290 MessageLoop::current()->PostDelayedTask( | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
Why not call directly?
dwkang1
2013/02/04 14:08:26
Done.
| |
291 FROM_HERE, | |
292 base::Bind( | |
293 &AndroidVideoDecodeAccelerator::DoIOTask, base::AsWeakPtr(this)), | |
294 base::TimeDelta()); | |
295 } | |
296 } | |
297 | |
298 void AndroidVideoDecodeAccelerator::AssignPictureBuffers( | |
299 const std::vector<media::PictureBuffer>& buffers) { | |
300 LOG_LINE(); | |
301 DCHECK(thread_checker_.CalledOnValidThread()); | |
302 DCHECK(output_picture_buffers_.empty()); | |
303 | |
304 for (size_t i = 0; i < buffers.size(); ++i) { | |
305 output_picture_buffers_.insert(std::make_pair(buffers[i].id(), buffers[i])); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
output_picture_buffers_[buffers[i].id()] = buffers
dwkang1
2013/02/04 14:08:26
It lead a compile error because PictureBuffer does
| |
306 free_picture_ids_.push(buffers[i].id()); | |
307 } | |
308 | |
309 RETURN_ON_FAILURE(output_picture_buffers_.size() == kNumPictureBuffers, | |
310 "Invalid picture buffers were passed.", | |
311 INVALID_ARGUMENT); | |
312 } | |
313 | |
314 void AndroidVideoDecodeAccelerator::ReusePictureBuffer( | |
315 int32 picture_buffer_id) { | |
316 DCHECK(thread_checker_.CalledOnValidThread()); | |
317 free_picture_ids_.push(picture_buffer_id); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
Doesn't this potentially require a DoIOTask?
dwkang1
2013/02/04 14:08:26
Done.
| |
318 } | |
319 | |
320 void AndroidVideoDecodeAccelerator::Flush() { | |
321 LOG_LINE(); | |
322 DCHECK(thread_checker_.CalledOnValidThread()); | |
323 | |
324 Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0)); | |
325 } | |
326 | |
327 void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() { | |
328 DCHECK(surface_texture_.get()); | |
329 DCHECK_NE(media::MediaCodecBridge::UNKNOWN, codec_); | |
330 | |
331 media_codec_.reset(new media::MediaCodecBridge(codec_)); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
You don't need codec_ to re-configure a MediaCodec
dwkang1
2013/02/04 14:08:26
Unfortunately, I was not able to drop the recreati
| |
332 | |
333 JNIEnv* env = base::android::AttachCurrentThread(); | |
334 CHECK(env); | |
335 ScopedJavaLocalRef<jclass> cls( | |
336 base::android::GetClass(env, "android/view/Surface")); | |
337 jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>( | |
338 env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V"); | |
339 ScopedJavaLocalRef<jobject> j_surface( | |
340 env, env->NewObject( | |
341 cls.obj(), constructor, | |
342 surface_texture_->j_surface_texture().obj())); | |
343 | |
344 // VDA does not pass the container indicated resolution in the initialization | |
345 // phase. Here, we set 1080p by default. | |
346 media_codec_->ConfigureVideo( | |
347 codec_, gfx::Size(1920, 1080), j_surface.obj()); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
Can you give more detail on what Exception MediaCo
dwkang1
2013/02/04 14:08:26
Here is the logcat output when I pass 0x0 on Nexus
| |
348 content::ReleaseSurface(j_surface.obj()); | |
349 | |
350 media_codec_->GetInputBuffers(); | |
351 media_codec_->GetOutputBuffers(); | |
352 } | |
353 | |
354 void AndroidVideoDecodeAccelerator::Reset() { | |
355 LOG_LINE(); | |
356 DCHECK(thread_checker_.CalledOnValidThread()); | |
357 | |
358 while(!pending_bitstream_buffers_.empty()) { | |
359 media::BitstreamBuffer& bitstream_buffer = | |
360 pending_bitstream_buffers_.front(); | |
361 pending_bitstream_buffers_.pop(); | |
362 | |
363 if (bitstream_buffer.id() != -1) { | |
364 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
Please audit all uses of client_ to make sure they
dwkang1
2013/02/04 14:08:26
Done.
| |
365 } | |
366 } | |
367 while(!bitstream_buffer_ids_in_decoder_.empty()) { | |
368 bitstream_buffer_ids_in_decoder_.pop(); | |
369 } | |
370 | |
371 media_codec_->Stop(); | |
372 ConfigureMediaCodec(); | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
I still don't see why this is necessary; why not s
dwkang1
2013/01/29 03:58:48
When I tested "playback complete" and "replay" cas
dwkang1
2013/02/04 14:08:26
As you may know, we had a discussion with Android
| |
373 state_ = NO_ERROR; | |
374 | |
375 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | |
376 &AndroidVideoDecodeAccelerator::NotifyResetDone, base::AsWeakPtr(this))); | |
377 } | |
378 | |
379 void AndroidVideoDecodeAccelerator::Destroy() { | |
380 LOG_LINE(); | |
381 DCHECK(thread_checker_.CalledOnValidThread()); | |
382 delete this; | |
383 } | |
384 | |
385 void AndroidVideoDecodeAccelerator::NotifyInitializeDone() { | |
386 if (client_) { | |
387 client_->NotifyInitializeDone(); | |
388 } | |
389 } | |
390 | |
391 void AndroidVideoDecodeAccelerator::NotifyResetDone() { | |
392 if (client_) { | |
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
here and elsewhere, client_ cannot be non-NULL; dr
dwkang1
2013/02/04 14:08:26
Done.
| |
393 client_->NotifyResetDone(); | |
394 } | |
395 } | |
396 | |
397 } // namespace content | |
OLD | NEW |