/* * Copyright 2019 LinkedIn Corporation * All Rights Reserved. * * Licensed under the BSD 2-Clause License (the "License"). See License in the project root for * license information. */ package com.linkedin.android.litr.exception; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.util.Log; import androidx.annotation.RequiresApi; import java.util.Arrays; public class TrackTranscoderException extends MediaTransformationException { private static final String TAG = TrackTranscoderException.class.getName(); private static final String DECODER_FORMAT_NOT_FOUND_ERROR_TEXT = "Failed to create decoder codec."; private static final String DECODER_CONFIGURATION_ERROR_TEXT = "Failed to configure decoder codec."; private static final String ENCODER_FORMAT_NOT_FOUND_ERROR_TEXT = "Failed to create encoder codec."; private static final String ENCODER_CONFIGURATION_ERROR_TEXT = "Failed to configure encoder codec."; private static final String DECODER_NOT_FOUND_ERROR_TEXT = "No decoder found."; private static final String ENCODER_NOT_FOUND_ERROR_TEXT = "No encoder found."; private static final String CODEC_IN_RELEASED_STATE_ERROR_TEXT = "Codecs are in released state."; private static final String SOURCE_TRACK_MIME_TYPE_NOT_FOUND_ERROR_TEXT = "Mime type not found for the source track."; private static final String NO_TRACKS_FOUND_ERROR_TEXT = "No tracks found."; private static final String INTERNAL_CODEC_ERROR_TEXT = "Internal codec error occurred."; private static final String NO_FRAME_AVAILABLE_ERROR_TEXT = "No frame available for specified tag"; private static final String DECODER_NOT_PROVIDED_TEXT = "Decoder is not provided"; private static final String ENCODER_NOT_PROVIDED_TEXT = "Encoder is not provided"; private static final String RENDERER_NOT_PROVIDED_TEXT = "Renderer is not provided"; @NonNull private final Error error; @Nullable private final MediaFormat mediaFormat; @Nullable private final MediaCodec mediaCodec; @Nullable private final MediaCodecList mediaCodecList; // TODO Add track number to this exception to pass track info when track transcoders are in progress. public enum Error { DECODER_FORMAT_NOT_FOUND(DECODER_FORMAT_NOT_FOUND_ERROR_TEXT), DECODER_CONFIGURATION_ERROR(DECODER_CONFIGURATION_ERROR_TEXT), ENCODER_FORMAT_NOT_FOUND(ENCODER_FORMAT_NOT_FOUND_ERROR_TEXT), ENCODER_CONFIGURATION_ERROR(ENCODER_CONFIGURATION_ERROR_TEXT), DECODER_NOT_FOUND(DECODER_NOT_FOUND_ERROR_TEXT), ENCODER_NOT_FOUND(ENCODER_NOT_FOUND_ERROR_TEXT), CODEC_IN_RELEASED_STATE(CODEC_IN_RELEASED_STATE_ERROR_TEXT), SOURCE_TRACK_MIME_TYPE_NOT_FOUND(SOURCE_TRACK_MIME_TYPE_NOT_FOUND_ERROR_TEXT), NO_TRACKS_FOUND(NO_TRACKS_FOUND_ERROR_TEXT), INTERNAL_CODEC_ERROR(INTERNAL_CODEC_ERROR_TEXT), NO_FRAME_AVAILABLE(NO_FRAME_AVAILABLE_ERROR_TEXT), DECODER_NOT_PROVIDED(DECODER_NOT_PROVIDED_TEXT), ENCODER_NOT_PROVIDED(ENCODER_NOT_PROVIDED_TEXT), RENDERER_NOT_PROVIDED(RENDERER_NOT_PROVIDED_TEXT); private final String message; Error(String message) { this.message = message; } } public TrackTranscoderException(@NonNull Error error) { this(error, null, null, null); } public TrackTranscoderException(@NonNull Error error, @NonNull Throwable cause) { this(error, null, null, null, cause); } public TrackTranscoderException(@NonNull Error error, @Nullable MediaFormat sourceFormat, @Nullable MediaCodec mediaCodec, @Nullable MediaCodecList mediaCodecList) { this(error, sourceFormat, mediaCodec, mediaCodecList, null); } public TrackTranscoderException(@NonNull Error error, @Nullable MediaFormat sourceFormat, @Nullable MediaCodec mediaCodec, @Nullable MediaCodecList mediaCodecList, @Nullable Throwable cause) { super(cause); this.error = error; this.mediaFormat = sourceFormat; this.mediaCodec = mediaCodec; this.mediaCodecList = mediaCodecList; } @NonNull public Error getError() { return error; } @Override @NonNull public String getMessage() { return error.message; } @Override public String toString() { String ret = super.toString() + '\n'; if (mediaFormat != null) { ret += "Media format: " + mediaFormat.toString() + '\n'; } if (mediaCodec != null) { ret += "Selected media codec info: " + convertMediaCodecInfoToString(mediaCodec) + '\n'; } if (mediaCodecList != null) { ret += "Available media codec info list (Name, IsEncoder, Supported Types): "+ convertMediaCodecListToString(mediaCodecList); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getCause() != null) { ret += "Diagnostic info: " + getExceptionDiagnosticInfo(getCause()); } return ret; } @NonNull private String convertMediaCodecListToString(@NonNull MediaCodecList mediaCodecList) { StringBuilder builder = new StringBuilder(); try { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { // TODO filter supported codecs for the mime type for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) { if (mediaCodecInfo != null) { builder.append('\n').append(convertMediaCodecInfoToString(mediaCodecInfo)); } } } else { Log.e(TAG, "Failed to retrieve media codec info below API level 21."); } } catch (IllegalStateException e) { Log.e(TAG, "Failed to retrieve media codec info.", e); } return builder.toString(); } @NonNull private String convertMediaCodecInfoToString(@NonNull MediaCodec mediaCodec) { try { return convertMediaCodecInfoToString(mediaCodec.getCodecInfo()); } catch (IllegalStateException e) { Log.e(TAG, "Failed to retrieve media codec info."); } return ""; } @NonNull private String convertMediaCodecInfoToString(@NonNull MediaCodecInfo mediaCodecInfo) { return "MediaCodecInfo: " + mediaCodecInfo.getName() + ',' + mediaCodecInfo.isEncoder() + ',' + Arrays.asList(mediaCodecInfo.getSupportedTypes()).toString(); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) @Nullable private String getExceptionDiagnosticInfo(@Nullable Throwable cause) { if (!(cause instanceof MediaCodec.CodecException)) { return null; } return ((MediaCodec.CodecException) cause).getDiagnosticInfo(); } }