Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Side by Side Diff: webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java

Issue 3000153002: Optimize HardwareVideoDecoder by doing all frame conversions in C++. (Closed)
Patch Set: Rebase Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « webrtc/sdk/android/BUILD.gn ('k') | webrtc/sdk/android/src/java/org/webrtc/NV12Buffer.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
11 package org.webrtc; 11 package org.webrtc;
12 12
13 import android.annotation.TargetApi; 13 import android.annotation.TargetApi;
14 import android.graphics.Matrix;
15 import android.media.MediaCodec; 14 import android.media.MediaCodec;
16 import android.media.MediaCodecInfo.CodecCapabilities; 15 import android.media.MediaCodecInfo.CodecCapabilities;
17 import android.media.MediaFormat; 16 import android.media.MediaFormat;
18 import android.os.SystemClock; 17 import android.os.SystemClock;
19 import android.view.Surface; 18 import android.view.Surface;
20 import java.io.IOException; 19 import java.io.IOException;
21 import java.nio.ByteBuffer; 20 import java.nio.ByteBuffer;
22 import java.util.Arrays;
23 import java.util.Deque; 21 import java.util.Deque;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.LinkedBlockingDeque; 22 import java.util.concurrent.LinkedBlockingDeque;
26 import org.webrtc.ThreadUtils.ThreadChecker; 23 import org.webrtc.ThreadUtils.ThreadChecker;
27 24
28 /** Android hardware video decoder. */ 25 /** Android hardware video decoder. */
29 @TargetApi(16) 26 @TargetApi(16)
30 @SuppressWarnings("deprecation") // Cannot support API 16 without using deprecat ed methods. 27 @SuppressWarnings("deprecation") // Cannot support API 16 without using deprecat ed methods.
31 class HardwareVideoDecoder 28 class HardwareVideoDecoder
32 implements VideoDecoder, SurfaceTextureHelper.OnTextureFrameAvailableListene r { 29 implements VideoDecoder, SurfaceTextureHelper.OnTextureFrameAvailableListene r {
33 private static final String TAG = "HardwareVideoDecoder"; 30 private static final String TAG = "HardwareVideoDecoder";
34 31
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after
295 @Override 292 @Override
296 public String getImplementationName() { 293 public String getImplementationName() {
297 return "HardwareVideoDecoder: " + codecName; 294 return "HardwareVideoDecoder: " + codecName;
298 } 295 }
299 296
300 @Override 297 @Override
301 public VideoCodecStatus release() { 298 public VideoCodecStatus release() {
302 // TODO(sakal): This is not called on the correct thread but is still called synchronously. 299 // TODO(sakal): This is not called on the correct thread but is still called synchronously.
303 // Re-enable the check once this is called on the correct thread. 300 // Re-enable the check once this is called on the correct thread.
304 // decoderThreadChecker.checkIsOnValidThread(); 301 // decoderThreadChecker.checkIsOnValidThread();
302 if (!running) {
303 Logging.d(TAG, "release: Decoder is not running.");
304 return VideoCodecStatus.OK;
305 }
305 try { 306 try {
306 // The outputThread actually stops and releases the codec once running is false. 307 // The outputThread actually stops and releases the codec once running is false.
307 running = false; 308 running = false;
308 if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIM EOUT_MS)) { 309 if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIM EOUT_MS)) {
309 // Log an exception to capture the stack trace and turn it into a TIMEOU T error. 310 // Log an exception to capture the stack trace and turn it into a TIMEOU T error.
310 Logging.e(TAG, "Media encoder release timeout", new RuntimeException()); 311 Logging.e(TAG, "Media decoder release timeout", new RuntimeException());
311 return VideoCodecStatus.TIMEOUT; 312 return VideoCodecStatus.TIMEOUT;
312 } 313 }
313 if (shutdownException != null) { 314 if (shutdownException != null) {
314 // Log the exception and turn it into an error. Wrap the exception in a new exception to 315 // Log the exception and turn it into an error. Wrap the exception in a new exception to
315 // capture both the output thread's stack trace and this thread's stack trace. 316 // capture both the output thread's stack trace and this thread's stack trace.
316 Logging.e(TAG, "Media encoder release error", new RuntimeException(shutd ownException)); 317 Logging.e(TAG, "Media decoder release error", new RuntimeException(shutd ownException));
317 shutdownException = null; 318 shutdownException = null;
318 return VideoCodecStatus.ERROR; 319 return VideoCodecStatus.ERROR;
319 } 320 }
320 } finally { 321 } finally {
321 codec = null; 322 codec = null;
322 callback = null; 323 callback = null;
323 outputThread = null; 324 outputThread = null;
324 frameInfos.clear(); 325 frameInfos.clear();
325 if (surface != null) { 326 if (surface != null) {
326 surface.release(); 327 surface.release();
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 447
447 if (info.size < stride * height * 3 / 2 && sliceHeight == height && stride > width) { 448 if (info.size < stride * height * 3 / 2 && sliceHeight == height && stride > width) {
448 // Some codecs (Exynos) report an incorrect stride. Correct it here. 449 // Some codecs (Exynos) report an incorrect stride. Correct it here.
449 // Expected size == stride * height * 3 / 2. A bit of algebra gives the c orrect stride as 450 // Expected size == stride * height * 3 / 2. A bit of algebra gives the c orrect stride as
450 // 2 * size / (3 * height). 451 // 2 * size / (3 * height).
451 stride = info.size * 2 / (height * 3); 452 stride = info.size * 2 / (height * 3);
452 } 453 }
453 454
454 ByteBuffer buffer = codec.getOutputBuffers()[result]; 455 ByteBuffer buffer = codec.getOutputBuffers()[result];
455 buffer.position(info.offset); 456 buffer.position(info.offset);
456 buffer.limit(info.size); 457 buffer.limit(info.offset + info.size);
458 buffer = buffer.slice();
459 final VideoFrame.Buffer frameBuffer;
457 460
458 final VideoFrame.I420Buffer frameBuffer;
459
460 // TODO(mellem): As an optimization, use libyuv via JNI to copy/reformattin g data.
461 if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar) { 461 if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar) {
462 if (sliceHeight % 2 == 0) { 462 if (sliceHeight % 2 == 0) {
463 frameBuffer = 463 frameBuffer = wrapI420Buffer(buffer, result, stride, sliceHeight, width, height);
464 createBufferFromI420(buffer, result, info.offset, stride, sliceHeigh t, width, height);
465 } else { 464 } else {
466 frameBuffer = I420BufferImpl.allocate(width, height); 465 // WebRTC rounds chroma plane size conversions up so we have to repeat t he last row.
467 // Optimal path is not possible because we have to copy the last rows of U- and V-planes. 466 frameBuffer = copyI420Buffer(buffer, result, stride, sliceHeight, width, height);
468 copyI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, h eight);
469 codec.releaseOutputBuffer(result, false);
470 } 467 }
471 } else { 468 } else {
472 frameBuffer = I420BufferImpl.allocate(width, height);
473 // All other supported color formats are NV12. 469 // All other supported color formats are NV12.
474 nv12ToI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, h eight); 470 frameBuffer = wrapNV12Buffer(buffer, result, stride, sliceHeight, width, h eight);
475 codec.releaseOutputBuffer(result, false);
476 } 471 }
477 472
478 long presentationTimeNs = info.presentationTimeUs * 1000; 473 long presentationTimeNs = info.presentationTimeUs * 1000;
479 VideoFrame frame = new VideoFrame(frameBuffer, rotation, presentationTimeNs) ; 474 VideoFrame frame = new VideoFrame(frameBuffer, rotation, presentationTimeNs) ;
480 475
481 // Note that qp is parsed on the C++ side. 476 // Note that qp is parsed on the C++ side.
482 callback.onDecodedFrame(frame, decodeTimeMs, null /* qp */); 477 callback.onDecodedFrame(frame, decodeTimeMs, null /* qp */);
483 frame.release(); 478 frame.release();
484 } 479 }
485 480
481 private VideoFrame.Buffer wrapNV12Buffer(ByteBuffer buffer, int outputBufferIn dex, int stride,
482 int sliceHeight, int width, int height) {
483 synchronized (activeOutputBuffersLock) {
484 activeOutputBuffers++;
485 }
486
487 return new NV12Buffer(width, height, stride, sliceHeight, buffer, () -> {
488 codec.releaseOutputBuffer(outputBufferIndex, false);
489 synchronized (activeOutputBuffersLock) {
490 activeOutputBuffers--;
491 activeOutputBuffersLock.notifyAll();
492 }
493 });
494 }
495
496 private VideoFrame.Buffer copyI420Buffer(ByteBuffer buffer, int outputBufferIn dex, int stride,
497 int sliceHeight, int width, int height) {
498 final int uvStride = stride / 2;
499
500 final int yPos = 0;
501 final int uPos = yPos + stride * sliceHeight;
502 final int uEnd = uPos + uvStride * (sliceHeight / 2);
503 final int vPos = uPos + uvStride * sliceHeight / 2;
504 final int vEnd = vPos + uvStride * (sliceHeight / 2);
505
506 VideoFrame.I420Buffer frameBuffer = I420BufferImpl.allocate(width, height);
507
508 ByteBuffer dataY = frameBuffer.getDataY();
509 dataY.position(0); // Ensure we are in the beginning.
510 buffer.position(yPos);
511 buffer.limit(uPos);
512 dataY.put(buffer);
513 dataY.position(0); // Go back to beginning.
514
515 ByteBuffer dataU = frameBuffer.getDataU();
516 dataU.position(0); // Ensure we are in the beginning.
517 buffer.position(uPos);
518 buffer.limit(uEnd);
519 dataU.put(buffer);
520 if (sliceHeight % 2 != 0) {
521 buffer.position(uEnd - uvStride); // Repeat the last row.
522 dataU.put(buffer);
523 }
524 dataU.position(0); // Go back to beginning.
525
526 ByteBuffer dataV = frameBuffer.getDataU();
527 dataV.position(0); // Ensure we are in the beginning.
528 buffer.position(vPos);
529 buffer.limit(vEnd);
530 dataV.put(buffer);
531 if (sliceHeight % 2 != 0) {
532 buffer.position(vEnd - uvStride); // Repeat the last row.
533 dataV.put(buffer);
534 }
535 dataV.position(0); // Go back to beginning.
536
537 codec.releaseOutputBuffer(outputBufferIndex, false);
538
539 return frameBuffer;
540 }
541
542 private VideoFrame.Buffer wrapI420Buffer(ByteBuffer buffer, int outputBufferIn dex, int stride,
543 int sliceHeight, int width, int height) {
544 final int uvStride = stride / 2;
545
546 final int yPos = 0;
547 final int uPos = yPos + stride * sliceHeight;
548 final int uEnd = uPos + uvStride * (sliceHeight / 2);
549 final int vPos = uPos + uvStride * sliceHeight / 2;
550 final int vEnd = vPos + uvStride * (sliceHeight / 2);
551
552 synchronized (activeOutputBuffersLock) {
553 activeOutputBuffers++;
554 }
555
556 Runnable releaseCallback = () -> {
557 codec.releaseOutputBuffer(outputBufferIndex, false);
558 synchronized (activeOutputBuffersLock) {
559 activeOutputBuffers--;
560 activeOutputBuffersLock.notifyAll();
561 }
562 };
563
564 buffer.position(yPos);
565 buffer.limit(uPos);
566 ByteBuffer dataY = buffer.slice();
567
568 buffer.position(uPos);
569 buffer.limit(uEnd);
570 ByteBuffer dataU = buffer.slice();
571
572 buffer.position(vPos);
573 buffer.limit(vEnd);
574 ByteBuffer dataV = buffer.slice();
575
576 return new I420BufferImpl(
577 width, height, dataY, stride, dataU, uvStride, dataV, uvStride, releaseC allback);
578 }
579
486 private void reformat(MediaFormat format) { 580 private void reformat(MediaFormat format) {
487 outputThreadChecker.checkIsOnValidThread(); 581 outputThreadChecker.checkIsOnValidThread();
488 Logging.d(TAG, "Decoder format changed: " + format.toString()); 582 Logging.d(TAG, "Decoder format changed: " + format.toString());
489 final int newWidth; 583 final int newWidth;
490 final int newHeight; 584 final int newHeight;
491 if (format.containsKey(MEDIA_FORMAT_KEY_CROP_LEFT) 585 if (format.containsKey(MEDIA_FORMAT_KEY_CROP_LEFT)
492 && format.containsKey(MEDIA_FORMAT_KEY_CROP_RIGHT) 586 && format.containsKey(MEDIA_FORMAT_KEY_CROP_RIGHT)
493 && format.containsKey(MEDIA_FORMAT_KEY_CROP_BOTTOM) 587 && format.containsKey(MEDIA_FORMAT_KEY_CROP_BOTTOM)
494 && format.containsKey(MEDIA_FORMAT_KEY_CROP_TOP)) { 588 && format.containsKey(MEDIA_FORMAT_KEY_CROP_TOP)) {
495 newWidth = 1 + format.getInteger(MEDIA_FORMAT_KEY_CROP_RIGHT) 589 newWidth = 1 + format.getInteger(MEDIA_FORMAT_KEY_CROP_RIGHT)
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
581 } 675 }
582 676
583 private boolean isSupportedColorFormat(int colorFormat) { 677 private boolean isSupportedColorFormat(int colorFormat) {
584 for (int supported : MediaCodecUtils.DECODER_COLOR_FORMATS) { 678 for (int supported : MediaCodecUtils.DECODER_COLOR_FORMATS) {
585 if (supported == colorFormat) { 679 if (supported == colorFormat) {
586 return true; 680 return true;
587 } 681 }
588 } 682 }
589 return false; 683 return false;
590 } 684 }
591
592 private VideoFrame.I420Buffer createBufferFromI420(final ByteBuffer buffer,
593 final int outputBufferIndex, final int offset, final int stride, final int sliceHeight,
594 final int width, final int height) {
595 final int uvStride = stride / 2;
596 final int chromaWidth = (width + 1) / 2;
597 final int chromaHeight = (height + 1) / 2;
598
599 final int yPos = offset;
600 final int uPos = yPos + stride * sliceHeight;
601 final int vPos = uPos + uvStride * sliceHeight / 2;
602
603 synchronized (activeOutputBuffersLock) {
604 activeOutputBuffers++;
605 }
606
607 Runnable callback = new Runnable() {
608 @Override
609 public void run() {
610 codec.releaseOutputBuffer(outputBufferIndex, false);
611 synchronized (activeOutputBuffersLock) {
612 activeOutputBuffers--;
613 activeOutputBuffersLock.notifyAll();
614 }
615 }
616 };
617
618 buffer.position(yPos);
619 buffer.limit(uPos);
620 ByteBuffer dataY = buffer.slice();
621
622 buffer.position(uPos);
623 buffer.limit(vPos);
624 ByteBuffer dataU = buffer.slice();
625
626 buffer.position(vPos);
627 buffer.limit(vPos + uvStride * sliceHeight / 2);
628 ByteBuffer dataV = buffer.slice();
629
630 return new I420BufferImpl(
631 width, height, dataY, stride, dataU, uvStride, dataV, uvStride, callback );
632 }
633
634 private static void copyI420(ByteBuffer src, int offset, VideoFrame.I420Buffer frameBuffer,
635 int stride, int sliceHeight, int width, int height) {
636 int uvStride = stride / 2;
637 int chromaWidth = (width + 1) / 2;
638 // Note that hardware truncates instead of rounding. WebRTC expects roundin g, so the last
639 // row will be duplicated if the sliceHeight is odd.
640 int chromaHeight = (sliceHeight % 2 == 0) ? (height + 1) / 2 : height / 2;
641
642 int yPos = offset;
643 int uPos = yPos + stride * sliceHeight;
644 int vPos = uPos + uvStride * sliceHeight / 2;
645
646 copyPlane(
647 src, yPos, stride, frameBuffer.getDataY(), 0, frameBuffer.getStrideY(), width, height);
648 copyPlane(src, uPos, uvStride, frameBuffer.getDataU(), 0, frameBuffer.getStr ideU(), chromaWidth,
649 chromaHeight);
650 copyPlane(src, vPos, uvStride, frameBuffer.getDataV(), 0, frameBuffer.getStr ideV(), chromaWidth,
651 chromaHeight);
652
653 // If the sliceHeight is odd, duplicate the last rows of chroma. Copy the l ast row of the U and
654 // V channels and append them at the end of each channel.
655 if (sliceHeight % 2 != 0) {
656 int strideU = frameBuffer.getStrideU();
657 int endU = chromaHeight * strideU;
658 copyRow(frameBuffer.getDataU(), endU - strideU, frameBuffer.getDataU(), en dU, chromaWidth);
659 int strideV = frameBuffer.getStrideV();
660 int endV = chromaHeight * strideV;
661 copyRow(frameBuffer.getDataV(), endV - strideV, frameBuffer.getDataV(), en dV, chromaWidth);
662 }
663 }
664
665 private static void nv12ToI420(ByteBuffer src, int offset, VideoFrame.I420Buff er frameBuffer,
666 int stride, int sliceHeight, int width, int height) {
667 int yPos = offset;
668 int uvPos = yPos + stride * sliceHeight;
669 int chromaWidth = (width + 1) / 2;
670 int chromaHeight = (height + 1) / 2;
671
672 copyPlane(
673 src, yPos, stride, frameBuffer.getDataY(), 0, frameBuffer.getStrideY(), width, height);
674
675 // Split U and V rows.
676 int dstUPos = 0;
677 int dstVPos = 0;
678 for (int i = 0; i < chromaHeight; ++i) {
679 for (int j = 0; j < chromaWidth; ++j) {
680 frameBuffer.getDataU().put(dstUPos + j, src.get(uvPos + j * 2));
681 frameBuffer.getDataV().put(dstVPos + j, src.get(uvPos + j * 2 + 1));
682 }
683 dstUPos += frameBuffer.getStrideU();
684 dstVPos += frameBuffer.getStrideV();
685 uvPos += stride;
686 }
687 }
688
689 private static void copyPlane(ByteBuffer src, int srcPos, int srcStride, ByteB uffer dst,
690 int dstPos, int dstStride, int width, int height) {
691 for (int i = 0; i < height; ++i) {
692 copyRow(src, srcPos, dst, dstPos, width);
693 srcPos += srcStride;
694 dstPos += dstStride;
695 }
696 }
697
698 private static void copyRow(ByteBuffer src, int srcPos, ByteBuffer dst, int ds tPos, int width) {
699 for (int i = 0; i < width; ++i) {
700 dst.put(dstPos + i, src.get(srcPos + i));
701 }
702 }
703 } 685 }
OLDNEW
« no previous file with comments | « webrtc/sdk/android/BUILD.gn ('k') | webrtc/sdk/android/src/java/org/webrtc/NV12Buffer.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698