Home | History | Annotate | Download | only in decoder
      1 /*
      2  * Copyright 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package androidx.media.filterfw.decoder;
     17 
     18 import android.annotation.TargetApi;
     19 import android.graphics.SurfaceTexture;
     20 import android.media.MediaCodec;
     21 import android.media.MediaCodec.BufferInfo;
     22 import android.media.MediaCodecInfo;
     23 import android.media.MediaCodecInfo.CodecCapabilities;
     24 import android.media.MediaCodecList;
     25 import android.media.MediaFormat;
     26 import android.util.SparseIntArray;
     27 import androidx.media.filterfw.ColorSpace;
     28 import androidx.media.filterfw.Frame;
     29 import androidx.media.filterfw.FrameImage2D;
     30 import androidx.media.filterfw.PixelUtils;
     31 
     32 import java.io.IOException;
     33 import java.nio.ByteBuffer;
     34 import java.util.Arrays;
     35 import java.util.HashSet;
     36 import java.util.Set;
     37 import java.util.TreeMap;
     38 
     39 /**
     40  * {@link TrackDecoder} that decodes a video track and renders the frames onto a
     41  * {@link SurfaceTexture}.
     42  *
     43  * This implementation purely uses CPU based methods to decode and color-convert the frames.
     44  */
     45 @TargetApi(16)
     46 public class CpuVideoTrackDecoder extends VideoTrackDecoder {
     47 
     48     private static final int COLOR_FORMAT_UNSET = -1;
     49 
     50     private final int mWidth;
     51     private final int mHeight;
     52 
     53     private int mColorFormat = COLOR_FORMAT_UNSET;
     54     private long mCurrentPresentationTimeUs;
     55     private ByteBuffer mDecodedBuffer;
     56     private ByteBuffer mUnrotatedBytes;
     57 
     58     protected CpuVideoTrackDecoder(int trackIndex, MediaFormat format, Listener listener) {
     59         super(trackIndex, format, listener);
     60 
     61         mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
     62         mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
     63     }
     64 
     65     @Override
     66     protected MediaCodec initMediaCodec(MediaFormat format) {
     67         // Find a codec for our video that can output to one of our supported color-spaces
     68         MediaCodec mediaCodec = findDecoderCodec(format, new int[] {
     69                 CodecCapabilities.COLOR_Format32bitARGB8888,
     70                 CodecCapabilities.COLOR_FormatYUV420Planar});
     71         if (mediaCodec == null) {
     72             throw new RuntimeException(
     73                     "Could not find a suitable decoder for format: " + format + "!");
     74         }
     75         mediaCodec.configure(format, null, null, 0);
     76         return mediaCodec;
     77     }
     78 
     79     @Override
     80     protected boolean onDataAvailable(
     81             MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info) {
     82 
     83         mCurrentPresentationTimeUs = info.presentationTimeUs;
     84         mDecodedBuffer = buffers[bufferIndex];
     85 
     86         if (mColorFormat == -1) {
     87             mColorFormat = codec.getOutputFormat().getInteger(MediaFormat.KEY_COLOR_FORMAT);
     88         }
     89 
     90         markFrameAvailable();
     91         notifyListener();
     92 
     93         // Wait for the grab before we release this buffer.
     94         waitForFrameGrab();
     95 
     96         codec.releaseOutputBuffer(bufferIndex, false);
     97 
     98         return false;
     99     }
    100 
    101     @Override
    102     protected void copyFrameDataTo(FrameImage2D outputVideoFrame, int rotation) {
    103         // Calculate output dimensions
    104         int outputWidth = mWidth;
    105         int outputHeight = mHeight;
    106         if (needSwapDimension(rotation)) {
    107             outputWidth = mHeight;
    108             outputHeight = mWidth;
    109         }
    110 
    111         // Create output frame
    112         outputVideoFrame.resize(new int[] {outputWidth, outputHeight});
    113         outputVideoFrame.setTimestamp(mCurrentPresentationTimeUs * 1000);
    114         ByteBuffer outBytes = outputVideoFrame.lockBytes(Frame.MODE_WRITE);
    115 
    116         // Set data
    117         if (rotation == MediaDecoder.ROTATE_NONE) {
    118             convertImage(mDecodedBuffer, outBytes, mColorFormat, mWidth, mHeight);
    119         } else {
    120             if (mUnrotatedBytes == null) {
    121                 mUnrotatedBytes = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
    122             }
    123             // TODO: This could be optimized by including the rotation in the color conversion.
    124             convertImage(mDecodedBuffer, mUnrotatedBytes, mColorFormat, mWidth, mHeight);
    125             copyRotate(mUnrotatedBytes, outBytes, rotation);
    126         }
    127         outputVideoFrame.unlock();
    128     }
    129 
    130     /**
    131      * Copy the input data to the output data applying the specified rotation.
    132      *
    133      * @param input The input image data
    134      * @param output Buffer for the output image data
    135      * @param rotation The rotation to apply
    136      */
    137     private void copyRotate(ByteBuffer input, ByteBuffer output, int rotation) {
    138         int offset;
    139         int pixStride;
    140         int rowStride;
    141         switch (rotation) {
    142             case MediaDecoder.ROTATE_NONE:
    143                 offset = 0;
    144                 pixStride = 1;
    145                 rowStride = mWidth;
    146                 break;
    147             case MediaDecoder.ROTATE_90_LEFT:
    148                 offset = (mWidth - 1) * mHeight;
    149                 pixStride = -mHeight;
    150                 rowStride = 1;
    151                 break;
    152             case MediaDecoder.ROTATE_90_RIGHT:
    153                 offset = mHeight - 1;
    154                 pixStride = mHeight;
    155                 rowStride = -1;
    156                 break;
    157             case MediaDecoder.ROTATE_180:
    158                 offset = mWidth * mHeight - 1;
    159                 pixStride = -1;
    160                 rowStride = -mWidth;
    161                 break;
    162             default:
    163                 throw new IllegalArgumentException("Unsupported rotation " + rotation + "!");
    164         }
    165         PixelUtils.copyPixels(input, output, mWidth, mHeight, offset, pixStride, rowStride);
    166     }
    167 
    168     /**
    169      * Looks for a codec with the specified requirements.
    170      *
    171      * The set of codecs will be filtered down to those that meet the following requirements:
    172      * <ol>
    173      *   <li>The codec is a decoder.</li>
    174      *   <li>The codec can decode a video of the specified format.</li>
    175      *   <li>The codec can decode to one of the specified color formats.</li>
    176      * </ol>
    177      * If multiple codecs are found, the one with the preferred color-format is taken. Color format
    178      * preference is determined by the order of their appearance in the color format array.
    179      *
    180      * @param format The format the codec must decode.
    181      * @param requiredColorFormats Array of target color spaces ordered by preference.
    182      * @return A codec that meets the requirements, or null if no such codec was found.
    183      */
    184     private static MediaCodec findDecoderCodec(MediaFormat format, int[] requiredColorFormats) {
    185         TreeMap<Integer, String> candidateCodecs = new TreeMap<Integer, String>();
    186         SparseIntArray colorPriorities = intArrayToPriorityMap(requiredColorFormats);
    187         for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
    188             // Get next codec
    189             MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
    190 
    191             // Check that this is a decoder
    192             if (info.isEncoder()) {
    193                 continue;
    194             }
    195 
    196             // Check if this codec can decode the video in question
    197             String requiredType = format.getString(MediaFormat.KEY_MIME);
    198             String[] supportedTypes = info.getSupportedTypes();
    199             Set<String> typeSet = new HashSet<String>(Arrays.asList(supportedTypes));
    200 
    201             // Check if it can decode to one of the required color formats
    202             if (typeSet.contains(requiredType)) {
    203                 CodecCapabilities capabilities = info.getCapabilitiesForType(requiredType);
    204                 for (int supportedColorFormat : capabilities.colorFormats) {
    205                     if (colorPriorities.indexOfKey(supportedColorFormat) >= 0) {
    206                         int priority = colorPriorities.get(supportedColorFormat);
    207                         candidateCodecs.put(priority, info.getName());
    208                     }
    209                 }
    210             }
    211         }
    212 
    213         // Pick the best codec (with the highest color priority)
    214         if (candidateCodecs.isEmpty()) {
    215             return null;
    216         } else {
    217             String bestCodec = candidateCodecs.firstEntry().getValue();
    218             try {
    219                 return MediaCodec.createByCodecName(bestCodec);
    220             } catch (IOException e) {
    221                 throw new RuntimeException(
    222                         "failed to create codec for "
    223                         + bestCodec, e);
    224             }
    225         }
    226     }
    227 
    228     private static SparseIntArray intArrayToPriorityMap(int[] values) {
    229         SparseIntArray result = new SparseIntArray();
    230         for (int priority = 0; priority < values.length; ++priority) {
    231             result.append(values[priority], priority);
    232         }
    233         return result;
    234     }
    235 
    236     private static void convertImage(
    237             ByteBuffer input, ByteBuffer output, int colorFormat, int width, int height) {
    238         switch (colorFormat) {
    239             case CodecCapabilities.COLOR_Format32bitARGB8888:
    240                 ColorSpace.convertArgb8888ToRgba8888(input, output, width, height);
    241                 break;
    242             case CodecCapabilities.COLOR_FormatYUV420Planar:
    243                 ColorSpace.convertYuv420pToRgba8888(input, output, width, height);
    244                 break;
    245             default:
    246                 throw new RuntimeException("Unsupported color format: " + colorFormat + "!");
    247         }
    248     }
    249 
    250 }
    251