blob: a6f7da3f61d0fa9e8d5476ba1d7eddf9e33f86cf [file] [log] [blame]
glaznev@webrtc.org18c92472015-02-18 18:42:55 +00001/*
2 * libjingle
3 * Copyright 2015 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28
29#include <vector>
30
31#include "talk/app/webrtc/java/jni/androidmediadecoder_jni.h"
32#include "talk/app/webrtc/java/jni/androidmediacodeccommon.h"
33#include "talk/app/webrtc/java/jni/classreferenceholder.h"
34#include "talk/app/webrtc/java/jni/native_handle_impl.h"
35#include "webrtc/base/bind.h"
36#include "webrtc/base/checks.h"
37#include "webrtc/base/logging.h"
38#include "webrtc/base/thread.h"
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000039#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
40#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
41#include "webrtc/system_wrappers/interface/tick_util.h"
42#include "third_party/libyuv/include/libyuv/convert.h"
43#include "third_party/libyuv/include/libyuv/convert_from.h"
44#include "third_party/libyuv/include/libyuv/video_common.h"
45
46using rtc::Bind;
47using rtc::Thread;
48using rtc::ThreadManager;
49using rtc::scoped_ptr;
50
51using webrtc::CodecSpecificInfo;
52using webrtc::DecodedImageCallback;
53using webrtc::EncodedImage;
Miguel Casas-Sanchez47650702015-05-29 17:21:40 -070054using webrtc::VideoFrame;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000055using webrtc::RTPFragmentationHeader;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000056using webrtc::TickTime;
57using webrtc::VideoCodec;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000058using webrtc::VideoCodecType;
59using webrtc::kVideoCodecH264;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000060using webrtc::kVideoCodecVP8;
61
62namespace webrtc_jni {
63
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000064class MediaCodecVideoDecoder : public webrtc::VideoDecoder,
65 public rtc::MessageHandler {
66 public:
Alex Glaznev4d2f4d12015-09-01 15:04:13 -070067 explicit MediaCodecVideoDecoder(
68 JNIEnv* jni, VideoCodecType codecType, jobject render_egl_context);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000069 virtual ~MediaCodecVideoDecoder();
70
glaznev@webrtc.org18c92472015-02-18 18:42:55 +000071 int32_t InitDecode(const VideoCodec* codecSettings, int32_t numberOfCores)
72 override;
73
74 int32_t Decode(
75 const EncodedImage& inputImage, bool missingFrames,
76 const RTPFragmentationHeader* fragmentation,
77 const CodecSpecificInfo* codecSpecificInfo = NULL,
78 int64_t renderTimeMs = -1) override;
79
80 int32_t RegisterDecodeCompleteCallback(DecodedImageCallback* callback)
81 override;
82
83 int32_t Release() override;
84
85 int32_t Reset() override;
86 // rtc::MessageHandler implementation.
87 void OnMessage(rtc::Message* msg) override;
88
89 private:
90 // CHECK-fail if not running on |codec_thread_|.
91 void CheckOnCodecThread();
92
93 int32_t InitDecodeOnCodecThread();
94 int32_t ReleaseOnCodecThread();
95 int32_t DecodeOnCodecThread(const EncodedImage& inputImage);
96 // Deliver any outputs pending in the MediaCodec to our |callback_| and return
97 // true on success.
98 bool DeliverPendingOutputs(JNIEnv* jni, int dequeue_timeout_us);
Alex Glaznev782671f2015-06-12 16:40:44 -070099 int32_t ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000100
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000101 // Type of video codec.
102 VideoCodecType codecType_;
103
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000104 bool key_frame_required_;
105 bool inited_;
Alex Glaznev782671f2015-06-12 16:40:44 -0700106 bool sw_fallback_required_;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000107 bool use_surface_;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000108 VideoCodec codec_;
Miguel Casas-Sanchez47650702015-05-29 17:21:40 -0700109 VideoFrame decoded_image_;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000110 NativeHandleImpl native_handle_;
111 DecodedImageCallback* callback_;
112 int frames_received_; // Number of frames received by decoder.
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000113 int frames_decoded_; // Number of frames decoded by decoder.
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000114 int64_t start_time_ms_; // Start time for statistics.
115 int current_frames_; // Number of frames in the current statistics interval.
116 int current_bytes_; // Encoded bytes in the current statistics interval.
117 int current_decoding_time_ms_; // Overall decoding time in the current second
118 uint32_t max_pending_frames_; // Maximum number of pending input frames
119 std::vector<int32_t> timestamps_;
120 std::vector<int64_t> ntp_times_ms_;
121 std::vector<int64_t> frame_rtc_times_ms_; // Time when video frame is sent to
122 // decoder input.
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000123 int32_t output_timestamp_; // Last output frame timestamp from timestamps_ Q.
124 int64_t output_ntp_time_ms_; // Last output frame ntp time from
125 // ntp_times_ms_ queue.
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000126
127 // State that is constant for the lifetime of this object once the ctor
128 // returns.
129 scoped_ptr<Thread> codec_thread_; // Thread on which to operate MediaCodec.
130 ScopedGlobalRef<jclass> j_media_codec_video_decoder_class_;
131 ScopedGlobalRef<jobject> j_media_codec_video_decoder_;
132 jmethodID j_init_decode_method_;
133 jmethodID j_release_method_;
134 jmethodID j_dequeue_input_buffer_method_;
135 jmethodID j_queue_input_buffer_method_;
136 jmethodID j_dequeue_output_buffer_method_;
137 jmethodID j_release_output_buffer_method_;
138 // MediaCodecVideoDecoder fields.
139 jfieldID j_input_buffers_field_;
140 jfieldID j_output_buffers_field_;
141 jfieldID j_color_format_field_;
142 jfieldID j_width_field_;
143 jfieldID j_height_field_;
144 jfieldID j_stride_field_;
145 jfieldID j_slice_height_field_;
146 jfieldID j_surface_texture_field_;
147 jfieldID j_textureID_field_;
148 // MediaCodecVideoDecoder.DecoderOutputBufferInfo fields.
149 jfieldID j_info_index_field_;
150 jfieldID j_info_offset_field_;
151 jfieldID j_info_size_field_;
152 jfieldID j_info_presentation_timestamp_us_field_;
153
154 // Global references; must be deleted in Release().
155 std::vector<jobject> input_buffers_;
156 jobject surface_texture_;
157 jobject previous_surface_texture_;
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700158
159 // Render EGL context - owned by factory, should not be allocated/destroyed
160 // by VideoDecoder.
161 jobject render_egl_context_;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000162};
163
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000164MediaCodecVideoDecoder::MediaCodecVideoDecoder(
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700165 JNIEnv* jni, VideoCodecType codecType, jobject render_egl_context) :
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000166 codecType_(codecType),
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700167 render_egl_context_(render_egl_context),
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000168 key_frame_required_(true),
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000169 inited_(false),
Alex Glaznev782671f2015-06-12 16:40:44 -0700170 sw_fallback_required_(false),
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000171 surface_texture_(NULL),
172 previous_surface_texture_(NULL),
173 codec_thread_(new Thread()),
174 j_media_codec_video_decoder_class_(
175 jni,
176 FindClass(jni, "org/webrtc/MediaCodecVideoDecoder")),
177 j_media_codec_video_decoder_(
178 jni,
179 jni->NewObject(*j_media_codec_video_decoder_class_,
180 GetMethodID(jni,
181 *j_media_codec_video_decoder_class_,
182 "<init>",
183 "()V"))) {
184 ScopedLocalRefFrame local_ref_frame(jni);
185 codec_thread_->SetName("MediaCodecVideoDecoder", NULL);
186 CHECK(codec_thread_->Start()) << "Failed to start MediaCodecVideoDecoder";
187
188 j_init_decode_method_ = GetMethodID(
189 jni, *j_media_codec_video_decoder_class_, "initDecode",
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000190 "(Lorg/webrtc/MediaCodecVideoDecoder$VideoCodecType;"
Magnus Jedvert207370f2015-09-16 12:32:21 +0200191 "IILandroid/opengl/EGLContext;)Z");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000192 j_release_method_ =
193 GetMethodID(jni, *j_media_codec_video_decoder_class_, "release", "()V");
194 j_dequeue_input_buffer_method_ = GetMethodID(
195 jni, *j_media_codec_video_decoder_class_, "dequeueInputBuffer", "()I");
196 j_queue_input_buffer_method_ = GetMethodID(
197 jni, *j_media_codec_video_decoder_class_, "queueInputBuffer", "(IIJ)Z");
198 j_dequeue_output_buffer_method_ = GetMethodID(
199 jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer",
200 "(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;");
201 j_release_output_buffer_method_ = GetMethodID(
Magnus Jedvert207370f2015-09-16 12:32:21 +0200202 jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(I)Z");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000203
204 j_input_buffers_field_ = GetFieldID(
205 jni, *j_media_codec_video_decoder_class_,
206 "inputBuffers", "[Ljava/nio/ByteBuffer;");
207 j_output_buffers_field_ = GetFieldID(
208 jni, *j_media_codec_video_decoder_class_,
209 "outputBuffers", "[Ljava/nio/ByteBuffer;");
210 j_color_format_field_ = GetFieldID(
211 jni, *j_media_codec_video_decoder_class_, "colorFormat", "I");
212 j_width_field_ = GetFieldID(
213 jni, *j_media_codec_video_decoder_class_, "width", "I");
214 j_height_field_ = GetFieldID(
215 jni, *j_media_codec_video_decoder_class_, "height", "I");
216 j_stride_field_ = GetFieldID(
217 jni, *j_media_codec_video_decoder_class_, "stride", "I");
218 j_slice_height_field_ = GetFieldID(
219 jni, *j_media_codec_video_decoder_class_, "sliceHeight", "I");
220 j_textureID_field_ = GetFieldID(
221 jni, *j_media_codec_video_decoder_class_, "textureID", "I");
222 j_surface_texture_field_ = GetFieldID(
223 jni, *j_media_codec_video_decoder_class_, "surfaceTexture",
224 "Landroid/graphics/SurfaceTexture;");
225
226 jclass j_decoder_output_buffer_info_class = FindClass(jni,
227 "org/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo");
228 j_info_index_field_ = GetFieldID(
229 jni, j_decoder_output_buffer_info_class, "index", "I");
230 j_info_offset_field_ = GetFieldID(
231 jni, j_decoder_output_buffer_info_class, "offset", "I");
232 j_info_size_field_ = GetFieldID(
233 jni, j_decoder_output_buffer_info_class, "size", "I");
234 j_info_presentation_timestamp_us_field_ = GetFieldID(
235 jni, j_decoder_output_buffer_info_class, "presentationTimestampUs", "J");
236
237 CHECK_EXCEPTION(jni) << "MediaCodecVideoDecoder ctor failed";
Magnus Jedvert207370f2015-09-16 12:32:21 +0200238 use_surface_ = (render_egl_context_ != NULL);
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700239 ALOGD("MediaCodecVideoDecoder ctor. Use surface: %d", use_surface_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000240 memset(&codec_, 0, sizeof(codec_));
241 AllowBlockingCalls();
242}
243
244MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
245 // Call Release() to ensure no more callbacks to us after we are deleted.
246 Release();
247 // Delete global references.
248 JNIEnv* jni = AttachCurrentThreadIfNeeded();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000249 if (previous_surface_texture_ != NULL) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000250 jni->DeleteGlobalRef(previous_surface_texture_);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000251 }
252 if (surface_texture_ != NULL) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000253 jni->DeleteGlobalRef(surface_texture_);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000254 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000255}
256
257int32_t MediaCodecVideoDecoder::InitDecode(const VideoCodec* inst,
258 int32_t numberOfCores) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700259 ALOGD("InitDecode.");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000260 if (inst == NULL) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000261 ALOGE("NULL VideoCodec instance");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000262 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
263 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000264 // Factory should guard against other codecs being used with us.
265 CHECK(inst->codecType == codecType_) << "Unsupported codec " <<
266 inst->codecType << " for " << codecType_;
267
Alex Glaznev782671f2015-06-12 16:40:44 -0700268 if (sw_fallback_required_) {
269 ALOGE("InitDecode() - fallback to SW decoder");
270 return WEBRTC_VIDEO_CODEC_OK;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000271 }
272 // Save VideoCodec instance for later.
273 if (&codec_ != inst) {
274 codec_ = *inst;
275 }
276 codec_.maxFramerate = (codec_.maxFramerate >= 1) ? codec_.maxFramerate : 1;
277
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000278 // Call Java init.
279 return codec_thread_->Invoke<int32_t>(
280 Bind(&MediaCodecVideoDecoder::InitDecodeOnCodecThread, this));
281}
282
283int32_t MediaCodecVideoDecoder::InitDecodeOnCodecThread() {
284 CheckOnCodecThread();
285 JNIEnv* jni = AttachCurrentThreadIfNeeded();
286 ScopedLocalRefFrame local_ref_frame(jni);
Alex Glaznev782671f2015-06-12 16:40:44 -0700287 ALOGD("InitDecodeOnCodecThread Type: %d. %d x %d. Fps: %d.",
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000288 (int)codecType_, codec_.width, codec_.height,
Alex Glaznev782671f2015-06-12 16:40:44 -0700289 codec_.maxFramerate);
290
291 // Release previous codec first if it was allocated before.
292 int ret_val = ReleaseOnCodecThread();
293 if (ret_val < 0) {
294 ALOGE("Release failure: %d - fallback to SW codec", ret_val);
295 sw_fallback_required_ = true;
296 return WEBRTC_VIDEO_CODEC_ERROR;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000297 }
298
Alex Glaznev782671f2015-06-12 16:40:44 -0700299 // Always start with a complete key frame.
300 key_frame_required_ = true;
301 frames_received_ = 0;
302 frames_decoded_ = 0;
303
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000304 jobject j_video_codec_enum = JavaEnumFromIndex(
305 jni, "MediaCodecVideoDecoder$VideoCodecType", codecType_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000306 bool success = jni->CallBooleanMethod(
307 *j_media_codec_video_decoder_,
308 j_init_decode_method_,
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000309 j_video_codec_enum,
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000310 codec_.width,
311 codec_.height,
Magnus Jedvert207370f2015-09-16 12:32:21 +0200312 use_surface_ ? render_egl_context_ : nullptr);
Alex Glaznev782671f2015-06-12 16:40:44 -0700313 if (CheckException(jni) || !success) {
314 ALOGE("Codec initialization error - fallback to SW codec.");
315 sw_fallback_required_ = true;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000316 return WEBRTC_VIDEO_CODEC_ERROR;
317 }
318 inited_ = true;
319
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000320 switch (codecType_) {
321 case kVideoCodecVP8:
322 max_pending_frames_ = kMaxPendingFramesVp8;
323 break;
324 case kVideoCodecH264:
325 max_pending_frames_ = kMaxPendingFramesH264;
326 break;
327 default:
328 max_pending_frames_ = 0;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000329 }
330 start_time_ms_ = GetCurrentTimeMs();
331 current_frames_ = 0;
332 current_bytes_ = 0;
333 current_decoding_time_ms_ = 0;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000334 output_timestamp_ = 0;
335 output_ntp_time_ms_ = 0;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000336 timestamps_.clear();
337 ntp_times_ms_.clear();
338 frame_rtc_times_ms_.clear();
339
340 jobjectArray input_buffers = (jobjectArray)GetObjectField(
341 jni, *j_media_codec_video_decoder_, j_input_buffers_field_);
342 size_t num_input_buffers = jni->GetArrayLength(input_buffers);
343 input_buffers_.resize(num_input_buffers);
344 for (size_t i = 0; i < num_input_buffers; ++i) {
345 input_buffers_[i] =
346 jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i));
Alex Glaznev782671f2015-06-12 16:40:44 -0700347 if (CheckException(jni)) {
348 ALOGE("NewGlobalRef error - fallback to SW codec.");
349 sw_fallback_required_ = true;
350 return WEBRTC_VIDEO_CODEC_ERROR;
351 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000352 }
353
354 if (use_surface_) {
355 jobject surface_texture = GetObjectField(
356 jni, *j_media_codec_video_decoder_, j_surface_texture_field_);
357 if (previous_surface_texture_ != NULL) {
358 jni->DeleteGlobalRef(previous_surface_texture_);
359 }
360 previous_surface_texture_ = surface_texture_;
361 surface_texture_ = jni->NewGlobalRef(surface_texture);
362 }
363 codec_thread_->PostDelayed(kMediaCodecPollMs, this);
364
365 return WEBRTC_VIDEO_CODEC_OK;
366}
367
368int32_t MediaCodecVideoDecoder::Release() {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000369 ALOGD("DecoderRelease request");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000370 return codec_thread_->Invoke<int32_t>(
371 Bind(&MediaCodecVideoDecoder::ReleaseOnCodecThread, this));
372}
373
374int32_t MediaCodecVideoDecoder::ReleaseOnCodecThread() {
375 if (!inited_) {
376 return WEBRTC_VIDEO_CODEC_OK;
377 }
378 CheckOnCodecThread();
379 JNIEnv* jni = AttachCurrentThreadIfNeeded();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000380 ALOGD("DecoderReleaseOnCodecThread: Frames received: %d.", frames_received_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000381 ScopedLocalRefFrame local_ref_frame(jni);
382 for (size_t i = 0; i < input_buffers_.size(); i++) {
383 jni->DeleteGlobalRef(input_buffers_[i]);
384 }
385 input_buffers_.clear();
386 jni->CallVoidMethod(*j_media_codec_video_decoder_, j_release_method_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000387 inited_ = false;
Alex Glaznev782671f2015-06-12 16:40:44 -0700388 rtc::MessageQueueManager::Clear(this);
389 if (CheckException(jni)) {
390 ALOGE("Decoder release exception");
391 return WEBRTC_VIDEO_CODEC_ERROR;
392 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000393 return WEBRTC_VIDEO_CODEC_OK;
394}
395
396void MediaCodecVideoDecoder::CheckOnCodecThread() {
397 CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread())
398 << "Running on wrong thread!";
399}
400
Alex Glaznev782671f2015-06-12 16:40:44 -0700401int32_t MediaCodecVideoDecoder::ProcessHWErrorOnCodecThread() {
402 CheckOnCodecThread();
403 int ret_val = ReleaseOnCodecThread();
404 if (ret_val < 0) {
405 ALOGE("ProcessHWError: Release failure");
406 }
407 if (codecType_ == kVideoCodecH264) {
408 // For now there is no SW H.264 which can be used as fallback codec.
409 // So try to restart hw codec for now.
410 ret_val = InitDecodeOnCodecThread();
411 ALOGE("Reset H.264 codec done. Status: %d", ret_val);
412 if (ret_val == WEBRTC_VIDEO_CODEC_OK) {
413 // H.264 codec was succesfully reset - return regular error code.
414 return WEBRTC_VIDEO_CODEC_ERROR;
415 } else {
416 // Fail to restart H.264 codec - return error code which should stop the
417 // call.
418 return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
419 }
420 } else {
421 sw_fallback_required_ = true;
422 ALOGE("Return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE");
423 return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
424 }
425}
426
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000427int32_t MediaCodecVideoDecoder::Decode(
428 const EncodedImage& inputImage,
429 bool missingFrames,
430 const RTPFragmentationHeader* fragmentation,
431 const CodecSpecificInfo* codecSpecificInfo,
432 int64_t renderTimeMs) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700433 if (sw_fallback_required_) {
434 ALOGE("Decode() - fallback to SW codec");
435 return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000436 }
437 if (callback_ == NULL) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700438 ALOGE("Decode() - callback_ is NULL");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000439 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
440 }
441 if (inputImage._buffer == NULL && inputImage._length > 0) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700442 ALOGE("Decode() - inputImage is incorrect");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000443 return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
444 }
Alex Glaznev782671f2015-06-12 16:40:44 -0700445 if (!inited_) {
446 ALOGE("Decode() - decoder is not initialized");
447 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
448 }
449
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000450 // Check if encoded frame dimension has changed.
451 if ((inputImage._encodedWidth * inputImage._encodedHeight > 0) &&
452 (inputImage._encodedWidth != codec_.width ||
453 inputImage._encodedHeight != codec_.height)) {
454 codec_.width = inputImage._encodedWidth;
455 codec_.height = inputImage._encodedHeight;
Alex Glaznev782671f2015-06-12 16:40:44 -0700456 int32_t ret = InitDecode(&codec_, 1);
457 if (ret < 0) {
458 ALOGE("InitDecode failure: %d - fallback to SW codec", ret);
459 sw_fallback_required_ = true;
460 return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
461 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000462 }
463
464 // Always start with a complete key frame.
465 if (key_frame_required_) {
466 if (inputImage._frameType != webrtc::kKeyFrame) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700467 ALOGE("Decode() - key frame is required");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000468 return WEBRTC_VIDEO_CODEC_ERROR;
469 }
470 if (!inputImage._completeFrame) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700471 ALOGE("Decode() - complete frame is required");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000472 return WEBRTC_VIDEO_CODEC_ERROR;
473 }
474 key_frame_required_ = false;
475 }
476 if (inputImage._length == 0) {
477 return WEBRTC_VIDEO_CODEC_ERROR;
478 }
479
480 return codec_thread_->Invoke<int32_t>(Bind(
481 &MediaCodecVideoDecoder::DecodeOnCodecThread, this, inputImage));
482}
483
484int32_t MediaCodecVideoDecoder::DecodeOnCodecThread(
485 const EncodedImage& inputImage) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000486 CheckOnCodecThread();
487 JNIEnv* jni = AttachCurrentThreadIfNeeded();
488 ScopedLocalRefFrame local_ref_frame(jni);
489
490 // Try to drain the decoder and wait until output is not too
491 // much behind the input.
492 if (frames_received_ > frames_decoded_ + max_pending_frames_) {
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000493 ALOGV("Received: %d. Decoded: %d. Wait for output...",
494 frames_received_, frames_decoded_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000495 if (!DeliverPendingOutputs(jni, kMediaCodecTimeoutMs * 1000)) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700496 ALOGE("DeliverPendingOutputs error");
497 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000498 }
499 if (frames_received_ > frames_decoded_ + max_pending_frames_) {
500 ALOGE("Output buffer dequeue timeout");
Alex Glaznev782671f2015-06-12 16:40:44 -0700501 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000502 }
503 }
504
505 // Get input buffer.
506 int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_decoder_,
507 j_dequeue_input_buffer_method_);
Alex Glaznev782671f2015-06-12 16:40:44 -0700508 if (CheckException(jni) || j_input_buffer_index < 0) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000509 ALOGE("dequeueInputBuffer error");
Alex Glaznev782671f2015-06-12 16:40:44 -0700510 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000511 }
512
513 // Copy encoded data to Java ByteBuffer.
514 jobject j_input_buffer = input_buffers_[j_input_buffer_index];
515 uint8* buffer =
516 reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer));
517 CHECK(buffer) << "Indirect buffer??";
518 int64 buffer_capacity = jni->GetDirectBufferCapacity(j_input_buffer);
Alex Glaznev782671f2015-06-12 16:40:44 -0700519 if (CheckException(jni) || buffer_capacity < inputImage._length) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000520 ALOGE("Input frame size %d is bigger than buffer size %d.",
521 inputImage._length, buffer_capacity);
Alex Glaznev782671f2015-06-12 16:40:44 -0700522 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000523 }
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000524 jlong timestamp_us = (frames_received_ * 1000000) / codec_.maxFramerate;
525 ALOGV("Decoder frame in # %d. Type: %d. Buffer # %d. TS: %lld. Size: %d",
526 frames_received_, inputImage._frameType, j_input_buffer_index,
527 timestamp_us / 1000, inputImage._length);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000528 memcpy(buffer, inputImage._buffer, inputImage._length);
529
530 // Save input image timestamps for later output.
531 frames_received_++;
532 current_bytes_ += inputImage._length;
533 timestamps_.push_back(inputImage._timeStamp);
534 ntp_times_ms_.push_back(inputImage.ntp_time_ms_);
535 frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
536
537 // Feed input to decoder.
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000538 bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_,
539 j_queue_input_buffer_method_,
540 j_input_buffer_index,
541 inputImage._length,
542 timestamp_us);
Alex Glaznev782671f2015-06-12 16:40:44 -0700543 if (CheckException(jni) || !success) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000544 ALOGE("queueInputBuffer error");
Alex Glaznev782671f2015-06-12 16:40:44 -0700545 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000546 }
547
548 // Try to drain the decoder
549 if (!DeliverPendingOutputs(jni, 0)) {
550 ALOGE("DeliverPendingOutputs error");
Alex Glaznev782671f2015-06-12 16:40:44 -0700551 return ProcessHWErrorOnCodecThread();
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000552 }
553
554 return WEBRTC_VIDEO_CODEC_OK;
555}
556
557bool MediaCodecVideoDecoder::DeliverPendingOutputs(
558 JNIEnv* jni, int dequeue_timeout_us) {
559 if (frames_received_ <= frames_decoded_) {
560 // No need to query for output buffers - decoder is drained.
561 return true;
562 }
563 // Get decoder output.
564 jobject j_decoder_output_buffer_info = jni->CallObjectMethod(
565 *j_media_codec_video_decoder_,
566 j_dequeue_output_buffer_method_,
567 dequeue_timeout_us);
Alex Glaznev782671f2015-06-12 16:40:44 -0700568 if (CheckException(jni)) {
569 return false;
570 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000571 if (IsNull(jni, j_decoder_output_buffer_info)) {
572 return true;
573 }
574
575 // Extract output buffer info from Java DecoderOutputBufferInfo.
576 int output_buffer_index =
577 GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_);
578 if (output_buffer_index < 0) {
579 ALOGE("dequeueOutputBuffer error : %d", output_buffer_index);
580 return false;
581 }
582 int output_buffer_offset =
583 GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_);
584 int output_buffer_size =
585 GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_);
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000586 long output_timestamps_ms = GetLongField(jni, j_decoder_output_buffer_info,
587 j_info_presentation_timestamp_us_field_) / 1000;
Alex Glaznev782671f2015-06-12 16:40:44 -0700588 if (CheckException(jni)) {
589 return false;
590 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000591
592 // Get decoded video frame properties.
593 int color_format = GetIntField(jni, *j_media_codec_video_decoder_,
594 j_color_format_field_);
595 int width = GetIntField(jni, *j_media_codec_video_decoder_, j_width_field_);
596 int height = GetIntField(jni, *j_media_codec_video_decoder_, j_height_field_);
597 int stride = GetIntField(jni, *j_media_codec_video_decoder_, j_stride_field_);
598 int slice_height = GetIntField(jni, *j_media_codec_video_decoder_,
599 j_slice_height_field_);
600 int texture_id = GetIntField(jni, *j_media_codec_video_decoder_,
601 j_textureID_field_);
602
603 // Extract data from Java ByteBuffer and create output yuv420 frame -
604 // for non surface decoding only.
605 if (!use_surface_) {
606 if (output_buffer_size < width * height * 3 / 2) {
607 ALOGE("Insufficient output buffer size: %d", output_buffer_size);
608 return false;
609 }
610 jobjectArray output_buffers = reinterpret_cast<jobjectArray>(GetObjectField(
611 jni, *j_media_codec_video_decoder_, j_output_buffers_field_));
612 jobject output_buffer =
613 jni->GetObjectArrayElement(output_buffers, output_buffer_index);
614 uint8_t* payload = reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(
615 output_buffer));
Alex Glaznev782671f2015-06-12 16:40:44 -0700616 if (CheckException(jni)) {
617 return false;
618 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000619 payload += output_buffer_offset;
620
621 // Create yuv420 frame.
622 if (color_format == COLOR_FormatYUV420Planar) {
623 decoded_image_.CreateFrame(
hbos@webrtc.org93d9d652015-03-16 13:26:00 +0000624 payload,
625 payload + (stride * slice_height),
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000626 payload + (5 * stride * slice_height / 4),
627 width, height,
628 stride, stride / 2, stride / 2);
629 } else {
630 // All other supported formats are nv12.
631 decoded_image_.CreateEmptyFrame(width, height, width,
632 width / 2, width / 2);
633 libyuv::NV12ToI420(
634 payload, stride,
635 payload + stride * slice_height, stride,
636 decoded_image_.buffer(webrtc::kYPlane),
637 decoded_image_.stride(webrtc::kYPlane),
638 decoded_image_.buffer(webrtc::kUPlane),
639 decoded_image_.stride(webrtc::kUPlane),
640 decoded_image_.buffer(webrtc::kVPlane),
641 decoded_image_.stride(webrtc::kVPlane),
642 width, height);
643 }
644 }
645
646 // Get frame timestamps from a queue.
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000647 if (timestamps_.size() > 0) {
648 output_timestamp_ = timestamps_.front();
649 timestamps_.erase(timestamps_.begin());
650 }
651 if (ntp_times_ms_.size() > 0) {
652 output_ntp_time_ms_ = ntp_times_ms_.front();
653 ntp_times_ms_.erase(ntp_times_ms_.begin());
654 }
655 int64_t frame_decoding_time_ms = 0;
656 if (frame_rtc_times_ms_.size() > 0) {
657 frame_decoding_time_ms = GetCurrentTimeMs() - frame_rtc_times_ms_.front();
658 frame_rtc_times_ms_.erase(frame_rtc_times_ms_.begin());
659 }
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000660 ALOGV("Decoder frame out # %d. %d x %d. %d x %d. Color: 0x%x. TS: %ld."
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000661 " DecTime: %lld", frames_decoded_, width, height, stride, slice_height,
glaznev@webrtc.orga4623d22015-02-25 00:02:50 +0000662 color_format, output_timestamps_ms, frame_decoding_time_ms);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000663
664 // Return output buffer back to codec.
665 bool success = jni->CallBooleanMethod(
666 *j_media_codec_video_decoder_,
667 j_release_output_buffer_method_,
Magnus Jedvert207370f2015-09-16 12:32:21 +0200668 output_buffer_index);
Alex Glaznev782671f2015-06-12 16:40:44 -0700669 if (CheckException(jni) || !success) {
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000670 ALOGE("releaseOutputBuffer error");
671 return false;
672 }
673
674 // Calculate and print decoding statistics - every 3 seconds.
675 frames_decoded_++;
676 current_frames_++;
677 current_decoding_time_ms_ += frame_decoding_time_ms;
678 int statistic_time_ms = GetCurrentTimeMs() - start_time_ms_;
679 if (statistic_time_ms >= kMediaCodecStatisticsIntervalMs &&
680 current_frames_ > 0) {
681 ALOGD("Decoder bitrate: %d kbps, fps: %d, decTime: %d for last %d ms",
682 current_bytes_ * 8 / statistic_time_ms,
683 (current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms,
684 current_decoding_time_ms_ / current_frames_, statistic_time_ms);
685 start_time_ms_ = GetCurrentTimeMs();
686 current_frames_ = 0;
687 current_bytes_ = 0;
688 current_decoding_time_ms_ = 0;
689 }
690
691 // Callback - output decoded frame.
692 int32_t callback_status = WEBRTC_VIDEO_CODEC_OK;
693 if (use_surface_) {
694 native_handle_.SetTextureObject(surface_texture_, texture_id);
Peter Boströmeb66e802015-06-05 11:08:03 +0200695 VideoFrame texture_image(new rtc::RefCountedObject<JniNativeHandleBuffer>(
696 &native_handle_, width, height),
697 output_timestamp_, 0, webrtc::kVideoRotation_0);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000698 texture_image.set_ntp_time_ms(output_ntp_time_ms_);
magjed@webrtc.org2056ee32015-03-16 13:46:52 +0000699 callback_status = callback_->Decoded(texture_image);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000700 } else {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000701 decoded_image_.set_timestamp(output_timestamp_);
702 decoded_image_.set_ntp_time_ms(output_ntp_time_ms_);
magjed@webrtc.org2056ee32015-03-16 13:46:52 +0000703 callback_status = callback_->Decoded(decoded_image_);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000704 }
705 if (callback_status > 0) {
706 ALOGE("callback error");
707 }
708
709 return true;
710}
711
712int32_t MediaCodecVideoDecoder::RegisterDecodeCompleteCallback(
713 DecodedImageCallback* callback) {
714 callback_ = callback;
715 return WEBRTC_VIDEO_CODEC_OK;
716}
717
718int32_t MediaCodecVideoDecoder::Reset() {
719 ALOGD("DecoderReset");
720 if (!inited_) {
721 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
722 }
723 return InitDecode(&codec_, 1);
724}
725
726void MediaCodecVideoDecoder::OnMessage(rtc::Message* msg) {
727 JNIEnv* jni = AttachCurrentThreadIfNeeded();
728 ScopedLocalRefFrame local_ref_frame(jni);
729 if (!inited_) {
730 return;
731 }
732 // We only ever send one message to |this| directly (not through a Bind()'d
733 // functor), so expect no ID/data.
734 CHECK(!msg->message_id) << "Unexpected message!";
735 CHECK(!msg->pdata) << "Unexpected message!";
736 CheckOnCodecThread();
737
738 if (!DeliverPendingOutputs(jni, 0)) {
Alex Glaznev782671f2015-06-12 16:40:44 -0700739 ALOGE("OnMessage: DeliverPendingOutputs error");
740 ProcessHWErrorOnCodecThread();
741 return;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000742 }
743 codec_thread_->PostDelayed(kMediaCodecPollMs, this);
744}
745
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700746MediaCodecVideoDecoderFactory::MediaCodecVideoDecoderFactory() :
747 render_egl_context_(NULL) {
748 ALOGD("MediaCodecVideoDecoderFactory ctor");
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000749 JNIEnv* jni = AttachCurrentThreadIfNeeded();
750 ScopedLocalRefFrame local_ref_frame(jni);
751 jclass j_decoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoDecoder");
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000752 supported_codec_types_.clear();
753
754 bool is_vp8_hw_supported = jni->CallStaticBooleanMethod(
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000755 j_decoder_class,
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000756 GetStaticMethodID(jni, j_decoder_class, "isVp8HwSupported", "()Z"));
Alex Glaznev782671f2015-06-12 16:40:44 -0700757 if (CheckException(jni)) {
758 is_vp8_hw_supported = false;
759 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000760 if (is_vp8_hw_supported) {
761 ALOGD("VP8 HW Decoder supported.");
762 supported_codec_types_.push_back(kVideoCodecVP8);
763 }
764
765 bool is_h264_hw_supported = jni->CallStaticBooleanMethod(
766 j_decoder_class,
767 GetStaticMethodID(jni, j_decoder_class, "isH264HwSupported", "()Z"));
Alex Glaznev782671f2015-06-12 16:40:44 -0700768 if (CheckException(jni)) {
769 is_h264_hw_supported = false;
770 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000771 if (is_h264_hw_supported) {
772 ALOGD("H264 HW Decoder supported.");
773 supported_codec_types_.push_back(kVideoCodecH264);
774 }
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000775}
776
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700777MediaCodecVideoDecoderFactory::~MediaCodecVideoDecoderFactory() {
778 ALOGD("MediaCodecVideoDecoderFactory dtor");
779 if (render_egl_context_) {
780 JNIEnv* jni = AttachCurrentThreadIfNeeded();
781 jni->DeleteGlobalRef(render_egl_context_);
782 render_egl_context_ = NULL;
783 }
784}
785
786void MediaCodecVideoDecoderFactory::SetEGLContext(
787 JNIEnv* jni, jobject render_egl_context) {
788 ALOGD("MediaCodecVideoDecoderFactory::SetEGLContext");
789 if (render_egl_context_) {
790 jni->DeleteGlobalRef(render_egl_context_);
791 render_egl_context_ = NULL;
792 }
793 if (!IsNull(jni, render_egl_context)) {
794 render_egl_context_ = jni->NewGlobalRef(render_egl_context);
795 if (CheckException(jni)) {
796 ALOGE("error calling NewGlobalRef for EGL Context.");
797 render_egl_context_ = NULL;
798 } else {
799 jclass j_egl_context_class = FindClass(jni, "android/opengl/EGLContext");
800 if (!jni->IsInstanceOf(render_egl_context_, j_egl_context_class)) {
801 ALOGE("Wrong EGL Context.");
802 jni->DeleteGlobalRef(render_egl_context_);
803 render_egl_context_ = NULL;
804 }
805 }
806 }
807 if (render_egl_context_ == NULL) {
808 ALOGW("NULL VideoDecoder EGL context - HW surface decoding is disabled.");
809 }
810}
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000811
812webrtc::VideoDecoder* MediaCodecVideoDecoderFactory::CreateVideoDecoder(
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000813 VideoCodecType type) {
814 if (supported_codec_types_.empty()) {
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700815 ALOGE("No HW video decoder for type %d.", (int)type);
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000816 return NULL;
817 }
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700818 for (VideoCodecType codec_type : supported_codec_types_) {
819 if (codec_type == type) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000820 ALOGD("Create HW video decoder for type %d.", (int)type);
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700821 return new MediaCodecVideoDecoder(
822 AttachCurrentThreadIfNeeded(), type, render_egl_context_);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000823 }
824 }
Alex Glaznev4d2f4d12015-09-01 15:04:13 -0700825 ALOGE("Can not find HW video decoder for type %d.", (int)type);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000826 return NULL;
glaznev@webrtc.org18c92472015-02-18 18:42:55 +0000827}
828
829void MediaCodecVideoDecoderFactory::DestroyVideoDecoder(
830 webrtc::VideoDecoder* decoder) {
831 delete decoder;
832}
833
834} // namespace webrtc_jni
835