Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. | 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ | 9 */ |
| 10 | 10 |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 117 this.rotation = rotation; | 117 this.rotation = rotation; |
| 118 this.presentationTimestampUs = presentationTimestampUs; | 118 this.presentationTimestampUs = presentationTimestampUs; |
| 119 this.decodeTimeMs = decodeTimeMs; | 119 this.decodeTimeMs = decodeTimeMs; |
| 120 } | 120 } |
| 121 } | 121 } |
| 122 | 122 |
| 123 // Metadata for the last frame rendered to the texture. Only accessed on the texture helper's | 123 // Metadata for the last frame rendered to the texture. Only accessed on the texture helper's |
| 124 // thread. | 124 // thread. |
| 125 private DecodedTextureMetadata renderedTextureMetadata; | 125 private DecodedTextureMetadata renderedTextureMetadata; |
| 126 | 126 |
| 127 private final Object callbackLock = new Object(); | |
|
magjed_webrtc
2017/08/24 10:52:24
Please get rid of the lock and use SurfaceTextureH
sakal
2017/08/24 11:24:00
Done.
| |
| 127 // Decoding proceeds asynchronously. This callback returns decoded frames to the caller. | 128 // Decoding proceeds asynchronously. This callback returns decoded frames to the caller. |
| 128 private Callback callback; | 129 private Callback callback; |
| 129 | 130 |
| 130 private MediaCodec codec = null; | 131 private MediaCodec codec = null; |
| 131 | 132 |
| 132 HardwareVideoDecoder( | 133 HardwareVideoDecoder( |
| 133 String codecName, VideoCodecType codecType, int colorFormat, EglBase.Conte xt sharedContext) { | 134 String codecName, VideoCodecType codecType, int colorFormat, EglBase.Conte xt sharedContext) { |
| 134 if (!isSupportedColorFormat(colorFormat)) { | 135 if (!isSupportedColorFormat(colorFormat)) { |
| 135 throw new IllegalArgumentException("Unsupported color format: " + colorFor mat); | 136 throw new IllegalArgumentException("Unsupported color format: " + colorFor mat); |
| 136 } | 137 } |
| 137 this.codecName = codecName; | 138 this.codecName = codecName; |
| 138 this.codecType = codecType; | 139 this.codecType = codecType; |
| 139 this.colorFormat = colorFormat; | 140 this.colorFormat = colorFormat; |
| 140 this.sharedContext = sharedContext; | 141 this.sharedContext = sharedContext; |
| 141 this.frameInfos = new LinkedBlockingDeque<>(); | 142 this.frameInfos = new LinkedBlockingDeque<>(); |
| 142 } | 143 } |
| 143 | 144 |
| 144 @Override | 145 @Override |
| 145 public VideoCodecStatus initDecode(Settings settings, Callback callback) { | 146 public VideoCodecStatus initDecode(Settings settings, Callback callback) { |
| 146 this.decoderThreadChecker = new ThreadChecker(); | 147 this.decoderThreadChecker = new ThreadChecker(); |
| 147 return initDecodeInternal(settings.width, settings.height, callback); | 148 |
| 149 synchronized (callbackLock) { | |
| 150 this.callback = callback; | |
| 151 } | |
| 152 if (sharedContext != null) { | |
| 153 surfaceTextureHelper = SurfaceTextureHelper.create("decoder-texture-thread ", sharedContext); | |
| 154 surface = new Surface(surfaceTextureHelper.getSurfaceTexture()); | |
| 155 surfaceTextureHelper.startListening(this); | |
| 156 } | |
| 157 return initDecodeInternal(settings.width, settings.height); | |
| 148 } | 158 } |
| 149 | 159 |
| 150 private VideoCodecStatus initDecodeInternal(int width, int height, Callback ca llback) { | 160 private VideoCodecStatus initDecodeInternal(int width, int height) { |
| 151 decoderThreadChecker.checkIsOnValidThread(); | 161 decoderThreadChecker.checkIsOnValidThread(); |
| 162 Logging.d(TAG, "initDecodeInternal"); | |
| 152 if (outputThread != null) { | 163 if (outputThread != null) { |
| 153 Logging.e(TAG, "initDecodeInternal called while the codec is already runni ng"); | 164 Logging.e(TAG, "initDecodeInternal called while the codec is already runni ng"); |
| 154 return VideoCodecStatus.ERROR; | 165 return VideoCodecStatus.ERROR; |
| 155 } | 166 } |
| 156 | 167 |
| 157 // Note: it is not necessary to initialize dimensions under the lock, since the output thread | 168 // Note: it is not necessary to initialize dimensions under the lock, since the output thread |
| 158 // is not running. | 169 // is not running. |
| 159 this.callback = callback; | |
| 160 this.width = width; | 170 this.width = width; |
| 161 this.height = height; | 171 this.height = height; |
| 162 | 172 |
| 163 stride = width; | 173 stride = width; |
| 164 sliceHeight = height; | 174 sliceHeight = height; |
| 165 hasDecodedFirstFrame = false; | 175 hasDecodedFirstFrame = false; |
| 166 keyFrameRequired = true; | 176 keyFrameRequired = true; |
| 167 | 177 |
| 168 try { | 178 try { |
| 169 codec = MediaCodec.createByCodecName(codecName); | 179 codec = MediaCodec.createByCodecName(codecName); |
| 170 } catch (IOException | IllegalArgumentException e) { | 180 } catch (IOException | IllegalArgumentException e) { |
| 171 Logging.e(TAG, "Cannot create media decoder " + codecName); | 181 Logging.e(TAG, "Cannot create media decoder " + codecName); |
| 172 return VideoCodecStatus.ERROR; | 182 return VideoCodecStatus.ERROR; |
| 173 } | 183 } |
| 174 try { | 184 try { |
| 175 MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), w idth, height); | 185 MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), w idth, height); |
| 176 if (sharedContext == null) { | 186 if (sharedContext == null) { |
| 177 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); | 187 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); |
| 178 } else { | |
| 179 surfaceTextureHelper = SurfaceTextureHelper.create("decoder-texture-thre ad", sharedContext); | |
| 180 surface = new Surface(surfaceTextureHelper.getSurfaceTexture()); | |
| 181 surfaceTextureHelper.startListening(this); | |
| 182 } | 188 } |
| 183 codec.configure(format, surface, null, 0); | 189 codec.configure(format, surface, null, 0); |
| 184 codec.start(); | 190 codec.start(); |
| 185 } catch (IllegalStateException e) { | 191 } catch (IllegalStateException e) { |
| 186 Logging.e(TAG, "initDecode failed", e); | 192 Logging.e(TAG, "initDecode failed", e); |
| 187 release(); | 193 release(); |
| 188 return VideoCodecStatus.ERROR; | 194 return VideoCodecStatus.ERROR; |
| 189 } | 195 } |
| 190 | |
| 191 running = true; | 196 running = true; |
| 192 outputThread = createOutputThread(); | 197 outputThread = createOutputThread(); |
| 193 outputThread.start(); | 198 outputThread.start(); |
| 194 | 199 |
| 200 Logging.d(TAG, "initDecodeInternal done"); | |
| 195 return VideoCodecStatus.OK; | 201 return VideoCodecStatus.OK; |
| 196 } | 202 } |
| 197 | 203 |
| 198 @Override | 204 @Override |
| 199 public VideoCodecStatus decode(EncodedImage frame, DecodeInfo info) { | 205 public VideoCodecStatus decode(EncodedImage frame, DecodeInfo info) { |
| 200 decoderThreadChecker.checkIsOnValidThread(); | 206 decoderThreadChecker.checkIsOnValidThread(); |
| 201 if (codec == null || callback == null) { | 207 synchronized (callbackLock) { |
| 202 return VideoCodecStatus.UNINITIALIZED; | 208 if (codec == null || callback == null) { |
| 209 Logging.d(TAG, "decode uninitalized, codec: " + codec + ", callback: " + callback); | |
| 210 return VideoCodecStatus.UNINITIALIZED; | |
| 211 } | |
| 203 } | 212 } |
| 204 | 213 |
| 205 if (frame.buffer == null) { | 214 if (frame.buffer == null) { |
| 206 Logging.e(TAG, "decode() - no input data"); | 215 Logging.e(TAG, "decode() - no input data"); |
| 207 return VideoCodecStatus.ERR_PARAMETER; | 216 return VideoCodecStatus.ERR_PARAMETER; |
| 208 } | 217 } |
| 209 | 218 |
| 210 int size = frame.buffer.remaining(); | 219 int size = frame.buffer.remaining(); |
| 211 if (size == 0) { | 220 if (size == 0) { |
| 212 Logging.e(TAG, "decode() - input buffer empty"); | 221 Logging.e(TAG, "decode() - input buffer empty"); |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 292 @Override | 301 @Override |
| 293 public String getImplementationName() { | 302 public String getImplementationName() { |
| 294 return "HardwareVideoDecoder: " + codecName; | 303 return "HardwareVideoDecoder: " + codecName; |
| 295 } | 304 } |
| 296 | 305 |
| 297 @Override | 306 @Override |
| 298 public VideoCodecStatus release() { | 307 public VideoCodecStatus release() { |
| 299 // TODO(sakal): This is not called on the correct thread but is still called synchronously. | 308 // TODO(sakal): This is not called on the correct thread but is still called synchronously. |
| 300 // Re-enable the check once this is called on the correct thread. | 309 // Re-enable the check once this is called on the correct thread. |
| 301 // decoderThreadChecker.checkIsOnValidThread(); | 310 // decoderThreadChecker.checkIsOnValidThread(); |
| 311 Logging.d(TAG, "release"); | |
| 312 try { | |
| 313 return releaseInternal(); | |
| 314 } finally { | |
| 315 synchronized (callbackLock) { | |
| 316 callback = null; | |
| 317 } | |
| 318 frameInfos.clear(); | |
| 319 if (surface != null) { | |
| 320 surface.release(); | |
| 321 surface = null; | |
| 322 surfaceTextureHelper.stopListening(); | |
| 323 surfaceTextureHelper.dispose(); | |
| 324 surfaceTextureHelper = null; | |
| 325 } | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 private VideoCodecStatus releaseInternal() { | |
| 302 if (!running) { | 330 if (!running) { |
| 303 Logging.d(TAG, "release: Decoder is not running."); | 331 Logging.d(TAG, "release: Decoder is not running."); |
| 304 return VideoCodecStatus.OK; | 332 return VideoCodecStatus.OK; |
| 305 } | 333 } |
| 306 try { | 334 try { |
| 307 // The outputThread actually stops and releases the codec once running is false. | 335 // The outputThread actually stops and releases the codec once running is false. |
| 308 running = false; | 336 running = false; |
| 309 if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIM EOUT_MS)) { | 337 if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIM EOUT_MS)) { |
| 310 // Log an exception to capture the stack trace and turn it into a TIMEOU T error. | 338 // Log an exception to capture the stack trace and turn it into a TIMEOU T error. |
| 311 Logging.e(TAG, "Media decoder release timeout", new RuntimeException()); | 339 Logging.e(TAG, "Media decoder release timeout", new RuntimeException()); |
| 312 return VideoCodecStatus.TIMEOUT; | 340 return VideoCodecStatus.TIMEOUT; |
| 313 } | 341 } |
| 314 if (shutdownException != null) { | 342 if (shutdownException != null) { |
| 315 // Log the exception and turn it into an error. Wrap the exception in a new exception to | 343 // Log the exception and turn it into an error. Wrap the exception in a new exception to |
| 316 // capture both the output thread's stack trace and this thread's stack trace. | 344 // capture both the output thread's stack trace and this thread's stack trace. |
| 317 Logging.e(TAG, "Media decoder release error", new RuntimeException(shutd ownException)); | 345 Logging.e(TAG, "Media decoder release error", new RuntimeException(shutd ownException)); |
| 318 shutdownException = null; | 346 shutdownException = null; |
| 319 return VideoCodecStatus.ERROR; | 347 return VideoCodecStatus.ERROR; |
| 320 } | 348 } |
| 321 } finally { | 349 } finally { |
| 322 codec = null; | 350 codec = null; |
| 323 callback = null; | |
| 324 outputThread = null; | 351 outputThread = null; |
| 325 frameInfos.clear(); | |
| 326 if (surface != null) { | |
| 327 surface.release(); | |
| 328 surface = null; | |
| 329 surfaceTextureHelper.stopListening(); | |
| 330 surfaceTextureHelper.dispose(); | |
| 331 surfaceTextureHelper = null; | |
| 332 } | |
| 333 } | 352 } |
| 334 return VideoCodecStatus.OK; | 353 return VideoCodecStatus.OK; |
| 335 } | 354 } |
| 336 | 355 |
| 337 private VideoCodecStatus reinitDecode(int newWidth, int newHeight) { | 356 private VideoCodecStatus reinitDecode(int newWidth, int newHeight) { |
| 338 decoderThreadChecker.checkIsOnValidThread(); | 357 decoderThreadChecker.checkIsOnValidThread(); |
| 339 VideoCodecStatus status = release(); | 358 VideoCodecStatus status = release(); |
| 340 if (status != VideoCodecStatus.OK) { | 359 if (status != VideoCodecStatus.OK) { |
| 341 return status; | 360 return status; |
| 342 } | 361 } |
| 343 return initDecodeInternal(newWidth, newHeight, callback); | 362 return initDecodeInternal(newWidth, newHeight); |
| 344 } | 363 } |
| 345 | 364 |
| 346 private Thread createOutputThread() { | 365 private Thread createOutputThread() { |
| 347 return new Thread("HardwareVideoDecoder.outputThread") { | 366 return new Thread("HardwareVideoDecoder.outputThread") { |
| 348 @Override | 367 @Override |
| 349 public void run() { | 368 public void run() { |
| 350 outputThreadChecker = new ThreadChecker(); | 369 outputThreadChecker = new ThreadChecker(); |
| 351 while (running) { | 370 while (running) { |
| 352 deliverDecodedFrame(); | 371 deliverDecodedFrame(); |
| 353 } | 372 } |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 416 } | 435 } |
| 417 | 436 |
| 418 @Override | 437 @Override |
| 419 public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) { | 438 public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) { |
| 420 VideoFrame.TextureBuffer oesBuffer = surfaceTextureHelper.createTextureBuffe r( | 439 VideoFrame.TextureBuffer oesBuffer = surfaceTextureHelper.createTextureBuffe r( |
| 421 renderedTextureMetadata.width, renderedTextureMetadata.height, | 440 renderedTextureMetadata.width, renderedTextureMetadata.height, |
| 422 RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix)); | 441 RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix)); |
| 423 | 442 |
| 424 VideoFrame frame = new VideoFrame(oesBuffer, renderedTextureMetadata.rotatio n, | 443 VideoFrame frame = new VideoFrame(oesBuffer, renderedTextureMetadata.rotatio n, |
| 425 renderedTextureMetadata.presentationTimestampUs * 1000); | 444 renderedTextureMetadata.presentationTimestampUs * 1000); |
| 426 callback.onDecodedFrame(frame, renderedTextureMetadata.decodeTimeMs, null /* qp */); | 445 synchronized (callbackLock) { |
| 446 if (callback != null) { | |
| 447 callback.onDecodedFrame(frame, renderedTextureMetadata.decodeTimeMs, nul l /* qp */); | |
| 448 } | |
| 449 } | |
| 427 frame.release(); | 450 frame.release(); |
| 428 } | 451 } |
| 429 | 452 |
| 430 private void deliverByteFrame( | 453 private void deliverByteFrame( |
| 431 int result, MediaCodec.BufferInfo info, int rotation, Integer decodeTimeMs ) { | 454 int result, MediaCodec.BufferInfo info, int rotation, Integer decodeTimeMs ) { |
| 432 // Load dimensions from shared memory under the dimension lock. | 455 // Load dimensions from shared memory under the dimension lock. |
| 433 int width, height, stride, sliceHeight; | 456 int width, height, stride, sliceHeight; |
| 434 synchronized (dimensionLock) { | 457 synchronized (dimensionLock) { |
| 435 width = this.width; | 458 width = this.width; |
| 436 height = this.height; | 459 height = this.height; |
| (...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 640 Logging.e(TAG, "Media decoder stop failed", e); | 663 Logging.e(TAG, "Media decoder stop failed", e); |
| 641 } | 664 } |
| 642 try { | 665 try { |
| 643 codec.release(); | 666 codec.release(); |
| 644 } catch (Exception e) { | 667 } catch (Exception e) { |
| 645 Logging.e(TAG, "Media decoder release failed", e); | 668 Logging.e(TAG, "Media decoder release failed", e); |
| 646 // Propagate exceptions caught during release back to the main thread. | 669 // Propagate exceptions caught during release back to the main thread. |
| 647 shutdownException = e; | 670 shutdownException = e; |
| 648 } | 671 } |
| 649 codec = null; | 672 codec = null; |
| 650 callback = null; | 673 synchronized (callbackLock) { |
| 674 callback = null; | |
| 675 } | |
| 651 outputThread = null; | 676 outputThread = null; |
| 652 frameInfos.clear(); | 677 frameInfos.clear(); |
| 653 Logging.d(TAG, "Release on output thread done"); | 678 Logging.d(TAG, "Release on output thread done"); |
| 654 } | 679 } |
| 655 | 680 |
| 656 private void waitOutputBuffersReleasedOnOutputThread() { | 681 private void waitOutputBuffersReleasedOnOutputThread() { |
| 657 outputThreadChecker.checkIsOnValidThread(); | 682 outputThreadChecker.checkIsOnValidThread(); |
| 658 synchronized (activeOutputBuffersLock) { | 683 synchronized (activeOutputBuffersLock) { |
| 659 while (activeOutputBuffers > 0) { | 684 while (activeOutputBuffers > 0) { |
| 660 Logging.d(TAG, "Waiting for all frames to be released."); | 685 Logging.d(TAG, "Waiting for all frames to be released."); |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 676 | 701 |
| 677 private boolean isSupportedColorFormat(int colorFormat) { | 702 private boolean isSupportedColorFormat(int colorFormat) { |
| 678 for (int supported : MediaCodecUtils.DECODER_COLOR_FORMATS) { | 703 for (int supported : MediaCodecUtils.DECODER_COLOR_FORMATS) { |
| 679 if (supported == colorFormat) { | 704 if (supported == colorFormat) { |
| 680 return true; | 705 return true; |
| 681 } | 706 } |
| 682 } | 707 } |
| 683 return false; | 708 return false; |
| 684 } | 709 } |
| 685 } | 710 } |
| OLD | NEW |