/** * Generates a test video file, saving it as VideoChunks. We generate frames with GL to avoid * having to deal with multiple YUV formats. * * @return true on success, false on "soft" failure */ private boolean generateVideoFile(VideoChunks output) { if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight); MediaCodec encoder = null; InputSurface inputSurface = null; try { MediaCodecInfo codecInfo = selectCodec(MIME_TYPE); if (codecInfo == null) { // Don't fail CTS if they don't have an AVC codec (not here, anyway). Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); return false; } if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName()); // We avoid the device-specific limitations on width and height by using values that // are multiples of 16, which all tested devices seem to be able to handle. MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); // Set some properties. Failing to specify some of these can cause the MediaCodec // configure() call to throw an unhelpful exception. format.setInteger( MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); if (VERBOSE) Log.d(TAG, "format: " + format); output.setMediaFormat(format); // Create a MediaCodec for the desired codec, then configure it as an encoder with // our desired properties. encoder = MediaCodec.createByCodecName(codecInfo.getName()); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); inputSurface = new InputSurface(encoder.createInputSurface()); inputSurface.makeCurrent(); encoder.start(); generateVideoData(encoder, inputSurface, output); } finally { if (encoder != null) { if (VERBOSE) Log.d(TAG, "releasing encoder"); encoder.stop(); encoder.release(); if (VERBOSE) Log.d(TAG, "released encoder"); } if (inputSurface != null) { inputSurface.release(); } } return true; }
/** * select the first codec that match a specific MIME type * * @param mimeType * @return */ private static final MediaCodecInfo selectAudioCodec(final String mimeType) { if (DEBUG) Log.v(TAG, "selectAudioCodec:"); MediaCodecInfo result = null; // get the list of available codecs final int numCodecs = MediaCodecList.getCodecCount(); LOOP: for (int i = 0; i < numCodecs; i++) { final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); if (!codecInfo.isEncoder()) { // skipp decoder continue; } final String[] types = codecInfo.getSupportedTypes(); for (int j = 0; j < types.length; j++) { if (DEBUG) Log.i(TAG, "supportedType:" + codecInfo.getName() + ",MIME=" + types[j]); if (types[j].equalsIgnoreCase(mimeType)) { if (result == null) { result = codecInfo; break LOOP; } } } } return result; }
@Override protected void prepare() throws IOException { if (DEBUG) Log.v(TAG, "prepare:"); mTrackIndex = -1; mMuxerStarted = mIsEOS = false; // prepare MediaCodec for AAC encoding of audio data from inernal mic. final MediaCodecInfo audioCodecInfo = selectAudioCodec(MIME_TYPE); if (audioCodecInfo == null) { Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); return; } if (DEBUG) Log.i(TAG, "selected codec: " + audioCodecInfo.getName()); final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1); audioFormat.setInteger( MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO); audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); // audioFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, inputFile.length()); // audioFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs ); if (DEBUG) Log.i(TAG, "format: " + audioFormat); mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mMediaCodec.start(); if (DEBUG) Log.i(TAG, "prepare finishing"); if (mListener != null) { try { mListener.onPrepared(this); } catch (final Exception e) { Log.e(TAG, "prepare:", e); } } }
private static DecoderProperties findDecoder(String mime, String[] supportedCodecPrefixes) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { return null; // MediaCodec.setParameters is missing. } for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); if (info.isEncoder()) { continue; } String name = null; for (String mimeType : info.getSupportedTypes()) { if (mimeType.equals(mime)) { name = info.getName(); break; } } if (name == null) { continue; // No HW support in this codec; try the next one. } Logging.v(TAG, "Found candidate decoder " + name); // Check if this is supported decoder. boolean supportedCodec = false; for (String codecPrefix : supportedCodecPrefixes) { if (name.startsWith(codecPrefix)) { supportedCodec = true; break; } } if (!supportedCodec) { continue; } // Check if codec supports either yuv420 or nv12. CodecCapabilities capabilities = info.getCapabilitiesForType(mime); for (int colorFormat : capabilities.colorFormats) { Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); } for (int supportedColorFormat : supportedColorList) { for (int codecColorFormat : capabilities.colorFormats) { if (codecColorFormat == supportedColorFormat) { // Found supported HW decoder. Logging.d( TAG, "Found target decoder " + name + ". Color: 0x" + Integer.toHexString(codecColorFormat)); return new DecoderProperties(name, codecColorFormat); } } } } return null; // No HW decoder. }
// We declare this method as explicitly throwing Exception // since some bad decoders can throw IllegalArgumentExceptions unexpectedly // and we want to be sure all callers are handling this possibility @SuppressWarnings("RedundantThrows") private static MediaCodecInfo findKnownSafeDecoder(String mimeType, int requiredProfile) throws Exception { for (MediaCodecInfo codecInfo : getMediaCodecList()) { // Skip encoders if (codecInfo.isEncoder()) { continue; } // Check for explicitly blacklisted decoders if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) { LimeLog.info("Skipping blacklisted decoder: " + codecInfo.getName()); continue; } // Find a decoder that supports the requested video format for (String mime : codecInfo.getSupportedTypes()) { if (mime.equalsIgnoreCase(mimeType)) { LimeLog.info("Examining decoder capabilities of " + codecInfo.getName()); CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime); if (requiredProfile != -1) { for (CodecProfileLevel profile : caps.profileLevels) { if (profile.profile == requiredProfile) { LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile"); return codecInfo; } } LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile"); } else { return codecInfo; } } } } return null; }
private static MediaCodecInfo findPreferredDecoder() { // This is a different algorithm than the other findXXXDecoder functions, // because we want to evaluate the decoders in our list's order // rather than MediaCodecList's order for (String preferredDecoder : preferredDecoders) { for (MediaCodecInfo codecInfo : getMediaCodecList()) { // Skip encoders if (codecInfo.isEncoder()) { continue; } // Check for preferred decoders if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) { LimeLog.info("Preferred decoder choice is " + codecInfo.getName()); return codecInfo; } } } return null; }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) public static IjkMediaCodecInfo setupCandidate(MediaCodecInfo codecInfo, String mimeType) { if (codecInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return null; String name = codecInfo.getName(); if (TextUtils.isEmpty(name)) return null; name = name.toLowerCase(Locale.US); int rank = RANK_NO_SENSE; if (!name.startsWith("omx.")) { rank = RANK_NON_STANDARD; } else if (name.startsWith("omx.pv")) { rank = RANK_SOFTWARE; } else if (name.startsWith("omx.google.")) { rank = RANK_SOFTWARE; } else if (name.startsWith("omx.ffmpeg.")) { rank = RANK_SOFTWARE; } else if (name.startsWith("omx.k3.ffmpeg.")) { rank = RANK_SOFTWARE; } else if (name.startsWith("omx.avcodec.")) { rank = RANK_SOFTWARE; } else if (name.startsWith("omx.ittiam.")) { // unknown codec in qualcomm SoC rank = RANK_NO_SENSE; } else if (name.startsWith("omx.mtk.")) { // 1. MTK only works on 4.3 and above // 2. MTK works on MIUI 6 (4.2.1) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) rank = RANK_NO_SENSE; else rank = RANK_TESTED; } else { Integer knownRank = getKnownCodecList().get(name); if (knownRank != null) { rank = knownRank; } else { try { CodecCapabilities cap = codecInfo.getCapabilitiesForType(mimeType); if (cap != null) rank = RANK_ACCEPTABLE; else rank = RANK_LAST_CHANCE; } catch (Throwable e) { rank = RANK_LAST_CHANCE; } } } IjkMediaCodecInfo candidate = new IjkMediaCodecInfo(); candidate.mCodecInfo = codecInfo; candidate.mRank = rank; candidate.mMimeType = mimeType; return candidate; }
public static CodecInfo getCodecInfo(MediaCodecInfo codecInfo) { String[] types = codecInfo.getSupportedTypes(); for (String type : types) { try { if (type.equals(MEDIA_CODEC_TYPE_H263)) return new H263CodecInfo(codecInfo); else if (type.equals(MEDIA_CODEC_TYPE_H264)) return new H264CodecInfo(codecInfo); else if (type.equals(MEDIA_CODEC_TYPE_VP8)) return new VP8CodecInfo(codecInfo); } catch (IllegalArgumentException e) { logger.error( "Error initializing codec info: " + codecInfo.getName() + ", type: " + type, e); } } return null; }
private static boolean hasCodecForMime(boolean encoder, String mime) { for (MediaCodecInfo info : sMCL.getCodecInfos()) { if (encoder != info.isEncoder()) { continue; } for (String type : info.getSupportedTypes()) { if (type.equalsIgnoreCase(mime)) { Log.i(TAG, "found codec " + info.getName() + " for mime " + mime); return true; } } } return false; }
public static MediaCodecInfo findFirstDecoder(String mimeType) { for (MediaCodecInfo codecInfo : getMediaCodecList()) { // Skip encoders if (codecInfo.isEncoder()) { continue; } // Check for explicitly blacklisted decoders if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) { LimeLog.info("Skipping blacklisted decoder: " + codecInfo.getName()); continue; } // Find a decoder that supports the specified video format for (String mime : codecInfo.getSupportedTypes()) { if (mime.equalsIgnoreCase(mimeType)) { LimeLog.info("First decoder choice is " + codecInfo.getName()); return codecInfo; } } } return null; }
@Override public String toString() { StringBuilder colorStr = new StringBuilder("\ncolors:\n"); for (int i = 0; i < colors.size(); i++) { colorStr.append(colors.get(i)); if (i != colors.size() - 1) colorStr.append(", \n"); } StringBuilder plStr = new StringBuilder("\nprofiles:\n"); ProfileLevel[] profiles = getProfileLevels(); for (int i = 0; i < profiles.length; i++) { plStr.append(profiles[i].toString()); if (i != profiles.length - 1) plStr.append(", \n"); } return codecInfo.getName() + "(" + getLibjitsiEncoding() + ")" + colorStr + plStr; }
@SuppressWarnings("RedundantThrows") public static String dumpDecoders() throws Exception { String str = ""; for (MediaCodecInfo codecInfo : getMediaCodecList()) { // Skip encoders if (codecInfo.isEncoder()) { continue; } str += "Decoder: " + codecInfo.getName() + "\n"; for (String type : codecInfo.getSupportedTypes()) { str += "\t" + type + "\n"; CodecCapabilities caps = codecInfo.getCapabilitiesForType(type); for (CodecProfileLevel profile : caps.profileLevels) { str += "\t\t" + profile.profile + " " + profile.level + "\n"; } } } return str; }
static { bannedYuvCodecs = new ArrayList<String>(); // Banned H264 encoders/decoders // Crashes bannedYuvCodecs.add("OMX.SEC.avc.enc"); bannedYuvCodecs.add("OMX.SEC.h263.enc"); // Don't support 3.1 profile used by Jitsi bannedYuvCodecs.add("OMX.Nvidia.h264.decode"); // bannedYuvCodecs.add("OMX.SEC.avc.dec"); // Banned VP8 encoders/decoders bannedYuvCodecs.add("OMX.SEC.vp8.dec"); // This one works only for res 176x144 bannedYuvCodecs.add("OMX.google.vpx.encoder"); for (int codecIndex = 0, codecCount = MediaCodecList.getCodecCount(); codecIndex < codecCount; codecIndex++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(codecIndex); logger.info( "Discovered codec: " + codecInfo.getName() + "/" + Arrays.toString(codecInfo.getSupportedTypes())); CodecInfo ci = CodecInfo.getCodecInfo(codecInfo); if (ci != null) { codecs.add(ci); ci.setBanned(bannedYuvCodecs.contains(ci.getName())); } } logger.info("Selected H264 encoder: " + getCodecForType(MEDIA_CODEC_TYPE_H264, true)); logger.info("Selected H264 decoder: " + getCodecForType(MEDIA_CODEC_TYPE_H264, false)); logger.info("Selected H263 encoder: " + getCodecForType(MEDIA_CODEC_TYPE_H263, true)); logger.info("Selected H263 decoder: " + getCodecForType(MEDIA_CODEC_TYPE_H263, false)); logger.info("Selected VP8 encoder: " + getCodecForType(MEDIA_CODEC_TYPE_VP8, true)); logger.info("Selected VP8 decoder: " + getCodecForType(MEDIA_CODEC_TYPE_VP8, false)); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mSurfaceView = (SurfaceView) findViewById(R.id.surface_view); // mSurfaceView.setBackgroundColor(Color.RED); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener( new View.OnClickListener() { private VideoPlayer mVideoPlayer = null; @Override public void onClick(View view) { if (mVideoPlayer != null) { mVideoPlayer.stop(); } mVideoPlayer = new VideoPlayer( MainActivity.mSelectedEncoder, mSurfaceView.getHolder().getSurface(), BASE_FOLDER + MainActivity.mSelectedFile); VideoPlayer.PlayTask task = new VideoPlayer.PlayTask(mVideoPlayer); task.execute(); // Snackbar.make(view, "Replace with your own action", // Snackbar.LENGTH_LONG) // .setAction("Action", null).show(); } }); List<String> decodersNames = new ArrayList<String>(); for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); Log.d(TAG, "onCreate: info " + i + info.getName()); if (!info.isEncoder() && (info.getName().contains("avc") || info.getName().contains("AVC") || info.getName().contains("h264") || info.getName().contains("H264"))) { decodersNames.add(info.getName()); } // mEncodersNames. = info.getName(); // String[] types = info.getSupportedTypes(); // String allTypes = ""; // for ( String type: types ) // { // allTypes += type + "; "; // } // Log.d(TAG, "XXXXXX - " + // "Name: " + info.getName() + ", " + // "Types: " + allTypes + ", " + // (info.isEncoder() ? "Encoder" : "Decoder")); } { mEncodersNames = new String[decodersNames.size()]; decodersNames.toArray(mEncodersNames); Spinner spinner = (Spinner) findViewById(R.id.listEncoders); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mEncodersNames); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // Apply the adapter to the spinner. spinner.setAdapter(adapter); spinner.setOnItemSelectedListener(this); } { File f = new File(new String(BASE_FOLDER)); File filesDir = f; // getFilesDir() Log.d(TAG, "searching for files in " + filesDir.toString()); mFilesNames = com.android.grafika.MiscUtils.getFiles(filesDir, "*"); Log.d(TAG, "files: " + mFilesNames.toString()); for (String file : mFilesNames) { Log.d(TAG, file); } Spinner spinner = (Spinner) findViewById(R.id.listFiles); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mFilesNames); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // Apply the adapter to the spinner. spinner.setAdapter(adapter); spinner.setOnItemSelectedListener(this); } }
/** * Returns codec name that can be used to obtain <tt>MediaCodec</tt>. * * @return codec name that can be used to obtain <tt>MediaCodec</tt>. */ public String getName() { return codecInfo.getName(); }