OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 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 // This file contains an implementation of VideoDecoderAccelerator | |
6 // that utilizes hardware video decoder present on Intel CPUs. | |
Ami GONE FROM CHROMIUM
2012/10/18 08:30:26
no it doesn't ;)
sheu
2012/10/30 09:05:03
Done.
| |
7 | |
8 #ifndef CONTENT_COMMON_GPU_MEDIA_EXYNOS_VIDEO_DECODE_ACCELERATOR_H_ | |
9 #define CONTENT_COMMON_GPU_MEDIA_EXYNOS_VIDEO_DECODE_ACCELERATOR_H_ | |
10 | |
11 #include <deque> | |
12 #include <vector> | |
13 | |
14 #include "base/callback_forward.h" | |
15 #include "base/memory/linked_ptr.h" | |
16 #include "base/memory/scoped_ptr.h" | |
17 #include "base/threading/thread.h" | |
18 #include "media/base/video_decoder_config.h" | |
19 #include "media/video/video_decode_accelerator.h" | |
20 #include "third_party/angle/include/EGL/egl.h" | |
21 #include "third_party/angle/include/EGL/eglext.h" | |
22 | |
23 namespace base { | |
24 class MessageLoopProxy; | |
25 } | |
26 | |
27 namespace gfx { | |
28 class GLContext; | |
29 } | |
30 | |
31 // This class handls Exynos video acceleration directly through the V4L2 devices | |
32 // exported by the Multi Format Codec and GScaler hardware blocks. | |
33 // | |
34 // The threading model of this class is driven by the fact that it needs to | |
35 // interface two fundamentally different event queues -- the one Chromium | |
36 // provides through MessageLoop, and the one driven by the V4L2 devices which | |
37 // is waited on with epoll(). There are three threads involved in this class: | |
38 // | |
39 // * The API thread, which is the main GPU process thread which calls the | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Chromium terminology calls this thread the ChildTh
sheu
2012/10/30 09:05:03
Done.
| |
40 // media::VideoDecodeAccelerator entry points. Calls from this thread | |
41 // generally do not block (with the exception of Initialize() and Destroy()). | |
42 // They post tasks to the decoder_thread_, which actually services the task | |
43 // and calls back when complete through the | |
44 // media::VideoDecodeAccelerator::Client interface. | |
45 // * The decoder_thread_, owned by this class. It services API tasks as well as | |
46 // device events. Pretty much all state modification is done on this thread. | |
47 // * The device_thread_, owned by this class. All it does is epoll() on the | |
48 // V4L2 device and notify the decoder_thread_ when something interesting | |
49 // happens. | |
50 // | |
51 // Note that this class has no locks! Everything's serviced on the | |
52 // decoder_thread_, so there are no synchronization issues. | |
53 // ... well, there are, but it's a matter of getting messages posted in the | |
54 // right order, not fiddling with locks. | |
55 class ExynosVideoDecodeAccelerator : public media::VideoDecodeAccelerator { | |
56 public: | |
57 ExynosVideoDecodeAccelerator(gfx::GLContext* gl_context, | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
style: here & elsewhere, arg lists should be eithe
sheu
2012/10/30 09:05:03
Done.
| |
58 Client* client, | |
59 const base::Callback<bool(void)>& make_context_current); | |
60 virtual ~ExynosVideoDecodeAccelerator(); | |
61 | |
62 // media::VideoDecodeAccelerator implementation. | |
63 // Note: Initialize() and Destroy() are synchronous. | |
64 virtual bool Initialize(media::VideoCodecProfile profile) OVERRIDE; | |
65 virtual void Decode(const media::BitstreamBuffer& bitstream_buffer) OVERRIDE; | |
66 virtual void AssignPictureBuffers( | |
67 const std::vector<media::PictureBuffer>& buffers) OVERRIDE; | |
68 virtual void ReusePictureBuffer(int32 picture_buffer_id) OVERRIDE; | |
69 virtual void Flush() OVERRIDE; | |
70 virtual void Reset() OVERRIDE; | |
71 virtual void Destroy() OVERRIDE; | |
72 | |
73 // Do any necessary initialization before the sandbox is enabled. | |
74 static void PreSandboxInitialization(); | |
75 | |
76 // Lazily initialize static data after sandbox is enabled. Return false on | |
77 // init failure. | |
78 static bool PostSandboxInitialization(); | |
79 | |
80 // Call through the static function pointers we hold. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Can't these be private?
For that matter, they seem
sheu
2012/10/30 09:05:03
Done.
| |
81 static EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, | |
82 const EGLint* attrib_list); | |
83 static EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync); | |
84 static EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, | |
85 EGLint flags, EGLTimeKHR timeout); | |
86 | |
87 private: | |
88 // These are rather subjectively tuned. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Have you tested flash? ihf@ found he needed 6-8 o
sheu
2012/10/30 09:05:03
I tested flash. Over and over again. I can turn
| |
89 enum { | |
90 kMfcInputBufferCount = 4, | |
91 kMfcOutputBufferExtraCount = 2, | |
92 kMfcInputBufferMaxSize = 1024 * 1024, | |
93 kGscInputBufferCount = 4, | |
94 kGscOutputBufferCount = 6, | |
95 }; | |
96 | |
97 // Internal state of the decoder. | |
98 enum State { | |
99 kUninitialized, // Initialize() not yet called. | |
100 kInitialized, // Initialize() called. Ready to start decoding. | |
101 kDecoding, // DecodeBufferInitial() successful; decoding frames. | |
102 kResetting, // Presently resetting. | |
103 kAfterReset, // After Reset(), ready to start decoding again. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
I believe the only diff between this and kInitiali
sheu
2012/10/30 09:05:03
I think the kInitialized/kDecoding/kAfterReset sta
| |
104 kError, // Error in kDecoding state. | |
105 }; | |
106 | |
107 // Record for decoder input buffers. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Comments that apply to each of the structs defined
sheu
2012/10/30 09:05:03
I suppose I could move BitstreamBufferRecord, but
| |
108 struct BitstreamBufferRecord { | |
109 BitstreamBufferRecord(); | |
110 scoped_ptr<base::SharedMemory> shm; | |
111 size_t size; | |
112 int32 input_id; | |
113 }; | |
114 | |
115 // Record for MFC input buffers. | |
116 struct MfcInputRecord { | |
117 MfcInputRecord(); | |
118 bool queued : 1; // queued to device. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
here and below: why bother with the :1?
If the com
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
s/queued/at_device/ ?
(ditto for other "queued" fi
sheu
2012/10/30 09:05:03
Done.
| |
119 void* offset; // mmap() offset. | |
120 size_t length; // mmap() length. | |
121 off_t bytes_used; // bytes filled in the mmap() segment. | |
122 int32 input_id; // triggering input_id as given to Decode(). | |
123 }; | |
124 | |
125 // Record for MFC output buffers. | |
126 struct MfcOutputRecord { | |
127 MfcOutputRecord(); | |
128 bool queued : 1; // queued to device. | |
129 size_t bytes_used[2]; // bytes used in each dmabuf. | |
130 void* offset[2]; // mmap() offset for each plane. | |
131 size_t length[2]; // mmap() length for each plane. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
what are these dmabufs/planes?
sheu
2012/10/30 09:05:03
It's hardware-specific business; I'm not sure that
| |
132 int32 input_id; // triggering input_id as given to Decode(). | |
133 }; | |
134 | |
135 // Record for GSC input buffers. | |
136 struct GscInputRecord { | |
137 GscInputRecord(); | |
138 bool queued : 1; // queued to device. | |
139 int mfc_output; // MFC output to recycle when this input is complete | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
s/MFC output/MfcOutputRecord/
sheu
2012/10/30 09:05:03
Well, we're tracking the buffer index here, not re
sheu
2012/10/30 09:05:03
We're tracking the output buffer itself, not the r
| |
140 }; | |
141 | |
142 // Record for GSC output buffers. | |
143 struct GscOutputRecord { | |
144 GscOutputRecord(); | |
145 bool queued : 1; // queued to device. | |
146 bool in_vda : 1; // held by VDA. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
_this_ is the VDA :)
Do you mean at_client?
sheu
2012/10/30 09:05:03
Done.
| |
147 int fd; // file descriptor from backing EGLImage. | |
148 EGLImageKHR egl_image; // backing EGLImage. | |
149 EGLSyncKHR egl_sync; // sync the compositor's use of the EGLImage. | |
150 int32 picture_id; // picture ID as returned to PictureReady(). | |
151 }; | |
152 | |
153 // Auto-destruction reference for an array of EGLImage (for message-passing) | |
154 struct EGLImageKHRArrayRef { | |
155 EGLImageKHRArrayRef(); | |
156 ~EGLImageKHRArrayRef(); | |
157 EGLDisplay egl_display; | |
158 scoped_array<EGLImageKHR> egl_images; | |
159 scoped_array<int> egl_image_fds; | |
160 int egl_images_count; | |
161 }; | |
162 | |
163 // Auto-destruction reference for EGLSync (for message-passing) | |
164 struct EGLSyncKHRRef { | |
165 EGLSyncKHRRef(); | |
166 ~EGLSyncKHRRef(); | |
167 EGLDisplay egl_display; | |
168 EGLSyncKHR egl_sync; | |
169 }; | |
170 | |
171 // | |
172 // Decoding tasks, to be run on decode_thread_. | |
173 // | |
174 | |
175 // Enqueue a BitstreamBuffer to decode. This will enqueue a | |
176 // DecodeBufferTask() to actually decoe the buffer. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
typo: decoe
sheu
2012/10/30 09:05:03
Done.
| |
177 void DecodeTask(scoped_ptr<BitstreamBufferRecord> bitstream_record); | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
I think you can improve function naming here. WDY
sheu
2012/10/30 09:05:03
Generally I'm matching each "Foo" call from the AP
| |
178 | |
179 // Decode from the queued BitstreamBuffers. Calls DecodeBufferInitial() | |
180 // or DecodeBufferContinue(). | |
181 void DecodeBufferTask(); | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
...and rename: DoSomeDecodeWork
(and below)
sheu
2012/10/30 09:05:03
They're pretty closely associated with DecodeBuffe
| |
182 bool DecodeBufferInitial(const void* data, size_t size); | |
183 bool DecodeBufferContinue(const void* data, size_t size); | |
184 // Helpers: accumulate and flush data for one decoded frame. | |
185 bool AppendToInputFrame(const void* data, size_t size); | |
186 bool FlushInputFrame(); | |
187 | |
188 // Process an AssignPictureBuffers() API call. After this, the device_thread_ | |
189 // can be started safely, since we have all our buffers. | |
190 void AssignPictureBuffersTask(scoped_ptr<EGLImageKHRArrayRef> egl_images_ref); | |
191 | |
192 // Service I/O on the V4L2 devices. | |
193 void ServiceDeviceTask(); | |
194 // Handle the various device queues. | |
195 void EnqueueMfc(); | |
196 void DequeueMfc(); | |
197 void EnqueueGsc(); | |
198 void DequeueGsc(); | |
199 | |
200 // Process a ReusePictureBuffer() API call. The API call create an EGLSync | |
201 // object on the main (GPU process) thread; we will record this object so we | |
202 // can wait on it before reusing the buffer. | |
203 void ReusePictureBufferTask(int32 picture_buffer_id, | |
204 scoped_ptr<EGLSyncKHRRef> egl_sync_ref); | |
205 | |
206 // Flush() task. API thread should not submit any more buffers until it | |
207 // receives the NotifyFlushDone callback. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
this is fine, but make sure you can deal with a Re
sheu
2012/10/30 09:05:03
Yep, that case should work. It just sets a flag a
| |
208 void FlushTask(); | |
209 | |
210 // Reset() task. This task will schedule a ResetDoneTask() that will send | |
211 // the NotifyResetDone callback. | |
212 void ResetTask(); | |
213 void ResetDoneTask(); | |
214 | |
215 // Device destruction task. | |
216 void DestroyTask(); | |
217 | |
218 // Cleanup() task, mostly used on the error path. | |
219 void CleanupTask(); | |
220 | |
221 // Start/stop DeviceTask() running on device_thread_. | |
222 bool StartDevice(); | |
223 bool StopDevice(); | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
You should doco the return value of these methods,
sheu
2012/10/30 09:05:03
Done.
| |
224 | |
225 // | |
226 // Device tasks, to be run on device_thread_. | |
227 // | |
228 | |
229 // The device task. | |
230 void DeviceTask(); | |
231 | |
232 // | |
233 // Safe from any thread (uses PostTask() to API thread). | |
234 // | |
235 | |
236 // Error notification. | |
237 void NotifyError(Error error); | |
238 | |
239 // | |
240 // Other utility functions | |
241 // | |
242 | |
243 // Create the buffers we need. | |
244 bool CreateMfcInputBuffers(); | |
245 bool CreateMfcOutputBuffers(); | |
246 bool CreateGscInputBuffers(); | |
247 bool CreateGscOutputBuffers(); | |
248 | |
249 // Destroy these buffers. | |
250 bool DestroyMfcInputBuffers(); | |
251 bool DestroyMfcOutputBuffers(); | |
252 bool DestroyGscInputBuffers(); | |
253 bool DestroyGscOutputBuffers(); | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Again, return values seem to be silently ignored s
sheu
2012/10/30 09:05:03
Done.
| |
254 | |
255 // Our original calling message loop for the API thread. | |
256 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
name it something more distinct, like {main,child}
sheu
2012/10/30 09:05:03
Done.
| |
257 | |
258 // WeakPtr<> pointing to |this| for use in posting tasks from the decoder | |
259 // thread back to the API thread. Because the decoder thread is a member of | |
260 // this class, any task running on the decoder thread is guaranteed that this | |
261 // object is still alive. As a result, tasks posted from API thread to | |
262 // decoder thread should use base::Unretained(this), and tasks posted from the | |
263 // decoder thread to the API thread should use |weak_this_|. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Comment that |device_thread_| follows the same log
sheu
2012/10/30 09:05:03
Done.
| |
264 base::WeakPtr<ExynosVideoDecodeAccelerator> weak_this_; | |
265 | |
266 // To expose client callbacks from VideoDecodeAccelerator. | |
267 // NOTE: all calls to these objects *MUST* be executed on message_loop_proxy_. | |
268 base::WeakPtrFactory<Client> client_ptr_factory_; | |
269 base::WeakPtr<Client> client_; | |
270 | |
271 // This thread handles VDA API entry points and callbacks. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
I think this comment is misleading.
sheu
2012/10/30 09:05:03
Done.
| |
272 base::Thread decoder_thread_; | |
273 // Decoder state. Owned by decoder_thread_. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Replace "Owned by..." with a blanket comment at l.
sheu
2012/10/30 09:05:03
Done. I did some declaration moving-around to hop
| |
274 State decoder_state_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
...except this seems to be read from both the deco
sheu
2012/10/30 09:05:03
Probably the trickiest bit of state. It's only re
Ami GONE FROM CHROMIUM
2012/10/31 01:06:50
If you wanted to be super-XTREME about it, you cou
| |
275 // Bitstream buffer we're presently reading. | |
276 scoped_ptr<BitstreamBufferRecord> decoder_current_bitstream_buffer_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
s/decoder_//
| |
277 // MFC input buffer we're presently filling. | |
278 int decoder_current_input_buffer_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
ditto s/decoder_// here and below.
sheu
2012/10/30 09:05:03
I was trying to mark these as more closely "decode
| |
279 // We track the number of buffer decode tasks we have scheduled, since each | |
280 // task execution should complete one buffer. If we fall behind (due to | |
281 // resource backpressure, etc.), we'll have to schedule more to catch up. | |
282 int decoder_decode_buffer_tasks_scheduled_; | |
283 // Buffers in the pipe, five-by-five. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
lolwat?
sheu
2012/10/30 09:05:03
http://www.youtube.com/watch?v=fOZk--oZdQk
(My re
| |
284 int decoder_frames_inflight_; | |
285 // Buffers unreturned by the VDA | |
286 int decoder_frames_unreturned_; | |
287 // Are we waiting for a flush notification? | |
288 bool decoder_notify_flush_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Can you make a pass and align these variable names
sheu
2012/10/30 09:05:03
Done.
| |
289 | |
290 // This thread handles notifications of V4L2 device changes. | |
291 base::Thread device_thread_; | |
292 | |
293 // GL state | |
294 gfx::GLContext* gl_context_; | |
295 | |
296 // Make our context current before running any EGL entry points. | |
297 base::Callback<bool(void)> make_context_current_; | |
298 | |
299 // The codec we'll be decoding for. | |
300 media::VideoCodecProfile video_codec_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
if you passed this to CreateMfcInputBuffers as a p
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
it's a profile, not a codec.
sheu
2012/10/30 09:05:03
Done.
| |
301 | |
302 // EGL state | |
303 EGLContext egl_context_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
you could assign these in the ctor and avoid the n
sheu
2012/10/30 09:05:03
A little forward-thinking: I'd like to standardize
| |
304 EGLDisplay egl_display_; | |
305 | |
306 // Input queue for decoder_thread_: BitstreamBuffers in. | |
307 std::deque<linked_ptr<BitstreamBufferRecord> > decode_input_queue_; | |
308 | |
309 // Completed decode buffers, waiting for MSC. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
typo: MSC
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
I'm a bit confused about what makes an input decod
sheu
2012/10/30 09:05:03
Done. The bit about "decode_output_mfc_input_queu
| |
310 std::deque<int> decode_output_mfc_input_queue_; | |
311 | |
312 // MFC decode device. | |
313 int mfc_fd_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
Use base::ScopedFD, per IM convo.
sheu
2012/10/30 09:05:03
Done.
| |
314 // MFC input buffer state. | |
315 bool mfc_input_streamon_; | |
316 int mfc_input_buffer_count_; | |
317 std::deque<int> mfc_free_input_buffers_; | |
318 // Mapping to track MFC input buffers. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
There's a lot of "map" commentary/variable-naming
sheu
2012/10/30 09:05:03
I tweaked the comment a bit. I think the fact tha
| |
319 std::vector<MfcInputRecord> mfc_input_buffer_map_; | |
320 | |
321 // MFC output buffer state. | |
322 bool mfc_output_streamon_; | |
323 int mfc_output_buffer_count_; | |
324 std::deque<int> mfc_free_output_buffers_; | |
325 // Mapping to track MFC output buffers. | |
326 std::vector<MfcOutputRecord> mfc_output_buffer_map_; | |
327 size_t mfc_output_buffer_size_[2]; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
doco what the [2] is about.
sheu
2012/10/30 09:05:03
Done.
| |
328 uint32 mfc_output_buffer_pixelformat_; | |
329 | |
330 // Completed MFC outputs, waiting for GSC. | |
331 std::deque<int> mfc_output_gsc_input_queue_; | |
332 | |
333 // GSC decode device. | |
334 int gsc_fd_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
scoped
sheu
2012/10/30 09:05:03
Done.
| |
335 | |
336 // GSC input buffer state. | |
337 bool gsc_input_streamon_; | |
338 int gsc_input_buffer_count_; | |
339 std::deque<int> gsc_free_input_buffers_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
does this really need to be a FIFO or could it jus
sheu
2012/10/30 09:05:03
Done.
| |
340 // Mapping to track GSC input buffers. | |
341 std::vector<GscInputRecord> gsc_input_buffer_map_; | |
342 | |
343 // GSC output buffer state. | |
344 bool gsc_output_streamon_; | |
345 int gsc_output_buffer_count_; | |
346 // GSC output buffers enqueued to device and ready, but not yet in use. | |
347 int gsc_output_buffer_prepared_count_; | |
348 // GSC output buffers enqueued to device, total. | |
349 int gsc_output_buffer_queued_count_; | |
350 std::deque<int> gsc_free_output_buffers_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
ditto
sheu
2012/10/30 09:05:03
This one does have to be a FIFO -- before we can r
| |
351 // Mapping to track GSC output buffers. | |
352 std::vector<GscOutputRecord> gsc_output_buffer_map_; | |
353 | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
ISTM there's a ton of repetition among the above 4
sheu
2012/10/30 09:05:03
The only things really common to them are *_stream
| |
354 // Output picture size. | |
355 unsigned int frame_width_; | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
gfx::Size
sheu
2012/10/30 09:05:03
Done.
| |
356 unsigned int frame_height_; | |
357 | |
358 // "private" API pointers as exported by libmali, that we require. | |
Ami GONE FROM CHROMIUM
2012/10/29 23:30:16
see above; l.358-369 belong only in the .cc file I
sheu
2012/10/30 09:05:03
Done.
| |
359 static void* libmali_handle; | |
360 static EGLBoolean(*mali_egl_image_get_buffer_ext_phandle)( | |
361 EGLImageKHR image, EGLint *attribs, void *phandle); | |
362 | |
363 // Entry points for uncommon EGL extensions we use. | |
364 static EGLSyncKHR (*egl_create_sync_khr)( | |
365 EGLDisplay dpy, EGLenum type, const EGLint* attrib_list); | |
366 static EGLBoolean (*egl_destroy_sync_khr)( | |
367 EGLDisplay dpy, EGLSyncKHR sync); | |
368 static EGLint (*egl_client_wait_sync_khr)( | |
369 EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout); | |
370 | |
371 DISALLOW_COPY_AND_ASSIGN(ExynosVideoDecodeAccelerator); | |
372 }; | |
373 | |
374 #endif // CONTENT_COMMON_GPU_MEDIA_EXYNOS_VIDEO_DECODE_ACCELERATOR_H_ | |
OLD | NEW |