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 |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |