Android Multimedia框架总结(二十三)MediaCodec补充及MediaMuxer引入(附案例)

前言:前面几章都是分析MediaCodec相关源码,有收到提问,说MediaCodec到底是硬解码还是软解码?看下今天的Agenda:

  • MediaCodec到底是硬解码还是软解码
  • MediaMuxer初识
  • MediaMuxer与MediaExtractor进行剪辑视频
  • 效果图
  • 布局实现
  • 逻辑实现
  • log输出过程

MediaCodec到底是硬解码还是软解码?

MediaCodec 调用的是在系统中register的解码器,硬件厂商会把自己的硬解码器register进来,就是硬解,如果他register一个软解码器,则是软解。

MediaCodec并不是真正的codec,真正codec是在openMax,要保证是硬解,在MediaCodec里有接口可以枚举所有解码器,每种编码可能都有多个解码器,区分哪个是软解哪个是硬解就行。

MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");

应用里面如果接收的是H264编码数据,所以我选取的是video/avc,我们可以看一下MediaCodec.createDecoderByType()枚举了哪些解编码器:

public static MediaCodec createDecoderByType(String type) {    return new MediaCodec(type, true /* nameIsType */, false /* encoder */);}

可以看到选的”video/avc” - H.264/AVC video是一种H264的解码方式,但并不能证明我使用的就一定是硬解码

我们先来看一下Android系统中解码器的命名,软解码器通常是以OMX.google开头的。硬解码器通常是以OMX.[hardware_vendor]开头的,比如TI的解码器是以OMX.TI开头的。当然还有一些不遵守这个命名规范的,不以OMX.开头的,那也会被认为是软解码器。

判断规则见frameworks/av/media/libstagefright/OMXCodec.cpp:

static bool IsSoftwareCodec(const char *componentName) {    if (!strncmp("OMX.google.", componentName, 11)) {        return true;    }    if (!strncmp("OMX.", componentName, 4)) {        return false;    }    return true;}

其实MediaCodec调用的是在系统中注册的解码器,系统中存在的解码器可以很多,但能够被应用使用的解码器是根据配置来的,即/system/etc/media_codecc.xml。这个文件一般由硬件或者系统的生产厂家在build整个系统的时候提供,一般是保存在代码的device/[company]/[codename]目录下的,例如device/samsung/tuna/media_codecs.xml。这个文件配置了系统中有哪些可用的codec以及,这些codec对应的媒体文件类型。在这个文件里面,系统里面提供的软硬codec都需要被列出来。

也就是说,如果系统里面实际上包含了某个codec,但是并没有被配置在这个文件里,那么应用程序也无法使用到。

在这个配置文件里面,如果出现多个codec对应同样类型的媒体格式的时候,这些codec都会被保留起来。当系统使用的时候,将会选择第一个匹配的codec。除非是指明了要软解码还是硬解码,但是Android的framework层为上层提供服务的AwesomePlayer中在处理音频和视频的时候,对到底是选择软解还是硬解的参数没有设置。所以虽然底层是支持选择的,但是对于上层使用MediaPlayer的Java程序来说,还是只能接受默认的codec选取规则。

但是Android提供的命令行程序/system/bin/stagefright在播放音频文件的时候,倒是可以根据参数来选择到底使用软解码还是硬解码,但是该工具只支持播放音频,不支持播放视频。

一般来说,如果系统里面有对应的媒体硬件解码器的话,系统开发人员应该是会配置在media_codecs.xml中,所以大多数情况下,如果有硬件解码器,那么我们总是会使用到硬件解码器。极少数情况下,硬件解码器存在,但不配置,我猜只可能是这个硬解码器还有bug,暂时还不适合发布,所以不用使用。

MediaMuxer初识

今天主要介绍MediaMuxer,在Android的多媒体类中,MediaMuxer用于将音频和视频进行混合生成多媒体文件。缺点是目前只能支持一个audio track和一个video track,而且仅支持mp4输出。 通过一个案列来了解MediaMuxer,以便后续过程分析,这个案例是进行一个音视频剪辑。就是一段正常的音视频文件,剪辑其中一个片段。在我的手机上有一段叫《节目.mp4》音视频文件。想剪辑其中精华部分,从2秒到12秒的视频。如下为效果图

效果图:

输入剪辑时间点,可以动态设置,并不是写死,剪辑时长,也是可以动态设置。

节目_output.mp4就是剪辑后的音视频文件。

逻辑实现:

package com.hejunlin.videoclip;import android.media.MediaCodec;import android.media.MediaExtractor;import android.media.MediaFormat;import android.media.MediaMuxer;import android.util.Log;import java.nio.ByteBuffer;/** * Created by 逆流的鱼yuiop on 16/12/18. * blog : http://blog.csdn.net/hejjunlin */public class VideoClip {    private final static String TAG = "VideoClip";    private MediaExtractor mMediaExtractor;    private MediaFormat mMediaFormat;    private MediaMuxer mMediaMuxer;    private String mime = null;    public boolean clipVideo(String url, long clipPoint, long clipDuration) {        int videoTrackIndex = -1;        int audioTrackIndex = -1;        int videoMaxInputSize = 0;        int audioMaxInputSize = 0;        int sourceVTrack = 0;        int sourceATrack = 0;        long videoDuration, audioDuration;        Log.d(TAG, ">> url : " + url);        //创建分离器        mMediaExtractor = new MediaExtractor();        try {            //设置文件路径            mMediaExtractor.setDataSource(url);            //创建合成器            mMediaMuxer = new MediaMuxer(url.substring(0, url.lastIndexOf(".")) + "_output.mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);        } catch (Exception e) {            Log.e(TAG, "error path" + e.getMessage());        }        //获取每个轨道的信息        for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {            try {                mMediaFormat = mMediaExtractor.getTrackFormat(i);                mime = mMediaFormat.getString(MediaFormat.KEY_MIME);                if (mime.startsWith("video/")) {                    sourceVTrack = i;                    int width = mMediaFormat.getInteger(MediaFormat.KEY_WIDTH);                    int height = mMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);                    videoMaxInputSize = mMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);                    videoDuration = mMediaFormat.getLong(MediaFormat.KEY_DURATION);                    //检测剪辑点和剪辑时长是否正确                    if (clipPoint >= videoDuration) {                        Log.e(TAG, "clip point is error!");                        return false;                    }                    if ((clipDuration != 0) && ((clipDuration + clipPoint) >= videoDuration)) {                        Log.e(TAG, "clip duration is error!");                        return false;                    }                    Log.d(TAG, "width and height is " + width + " " + height                            + ";maxInputSize is " + videoMaxInputSize                            + ";duration is " + videoDuration                    );                    //向合成器添加视频轨                    videoTrackIndex = mMediaMuxer.addTrack(mMediaFormat);                } else if (mime.startsWith("audio/")) {                    sourceATrack = i;                    int sampleRate = mMediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);                    int channelCount = mMediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);                    audioMaxInputSize = mMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);                    audioDuration = mMediaFormat.getLong(MediaFormat.KEY_DURATION);                    Log.d(TAG, "sampleRate is " + sampleRate                            + ";channelCount is " + channelCount                            + ";audioMaxInputSize is " + audioMaxInputSize                            + ";audioDuration is " + audioDuration                    );                    //添加音轨                    audioTrackIndex = mMediaMuxer.addTrack(mMediaFormat);                }                Log.d(TAG, "file mime is " + mime);            } catch (Exception e) {                Log.e(TAG, " read error " + e.getMessage());            }        }        //分配缓冲        ByteBuffer inputBuffer = ByteBuffer.allocate(videoMaxInputSize);        //根据官方文档的解释MediaMuxer的start一定要在addTrack之后        mMediaMuxer.start();        //视频处理部分        mMediaExtractor.selectTrack(sourceVTrack);        MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();        videoInfo.presentationTimeUs = 0;        long videoSampleTime;        //获取源视频相邻帧之间的时间间隔。(1)        {            mMediaExtractor.readSampleData(inputBuffer, 0);            //skip first I frame            if (mMediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC)                mMediaExtractor.advance();            mMediaExtractor.readSampleData(inputBuffer, 0);            long firstVideoPTS = mMediaExtractor.getSampleTime();            mMediaExtractor.advance();            mMediaExtractor.readSampleData(inputBuffer, 0);            long SecondVideoPTS = mMediaExtractor.getSampleTime();            videoSampleTime = Math.abs(SecondVideoPTS - firstVideoPTS);            Log.d(TAG, "videoSampleTime is " + videoSampleTime);        }        //选择起点        mMediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);        while (true) {            int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);            if (sampleSize < 0) {                //这里一定要释放选择的轨道,不然另一个轨道就无法选中了                mMediaExtractor.unselectTrack(sourceVTrack);                break;            }            int trackIndex = mMediaExtractor.getSampleTrackIndex();            //获取时间戳            long presentationTimeUs = mMediaExtractor.getSampleTime();            //获取帧类型,只能识别是否为I帧            int sampleFlag = mMediaExtractor.getSampleFlags();            Log.d(TAG, "trackIndex is " + trackIndex                    + ";presentationTimeUs is " + presentationTimeUs                    + ";sampleFlag is " + sampleFlag                    + ";sampleSize is " + sampleSize);            //剪辑时间到了就跳出            if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {                mMediaExtractor.unselectTrack(sourceVTrack);                break;            }            mMediaExtractor.advance();            videoInfo.offset = 0;            videoInfo.size = sampleSize;            videoInfo.flags = sampleFlag;            mMediaMuxer.writeSampleData(videoTrackIndex, inputBuffer, videoInfo);            videoInfo.presentationTimeUs += videoSampleTime;//presentationTimeUs;        }        //音频部分        mMediaExtractor.selectTrack(sourceATrack);        MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();        audioInfo.presentationTimeUs = 0;        long audioSampleTime;        //获取音频帧时长        {            mMediaExtractor.readSampleData(inputBuffer, 0);            //skip first sample            if (mMediaExtractor.getSampleTime() == 0)                mMediaExtractor.advance();            mMediaExtractor.readSampleData(inputBuffer, 0);            long firstAudioPTS = mMediaExtractor.getSampleTime();            mMediaExtractor.advance();            mMediaExtractor.readSampleData(inputBuffer, 0);            long SecondAudioPTS = mMediaExtractor.getSampleTime();            audioSampleTime = Math.abs(SecondAudioPTS - firstAudioPTS);            Log.d(TAG, "AudioSampleTime is " + audioSampleTime);        }        mMediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_CLOSEST_SYNC);        while (true) {            int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);            if (sampleSize < 0) {                mMediaExtractor.unselectTrack(sourceATrack);                break;            }            int trackIndex = mMediaExtractor.getSampleTrackIndex();            long presentationTimeUs = mMediaExtractor.getSampleTime();            Log.d(TAG, "trackIndex is " + trackIndex                    + ";presentationTimeUs is " + presentationTimeUs);            if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {                mMediaExtractor.unselectTrack(sourceATrack);                break;            }            mMediaExtractor.advance();            audioInfo.offset = 0;            audioInfo.size = sampleSize;            mMediaMuxer.writeSampleData(audioTrackIndex, inputBuffer, audioInfo);            audioInfo.presentationTimeUs += audioSampleTime;//presentationTimeUs;        }        //全部写完后释放MediaMuxer和MediaExtractor        mMediaMuxer.stop();        mMediaMuxer.release();        mMediaExtractor.release();        mMediaExtractor = null;        return true;    }}

ClipActivity

package com.hejunlin.videoclip;import android.annotation.TargetApi;import android.os.Build;import android.os.Bundle;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.EditText;/** * Created by 逆流的鱼yuiop on 16/12/18. * blog : http://blog.csdn.net/hejjunlin */public class ClipActivity extends AppCompatActivity implements View.OnClickListener {    private Button mButton;    private EditText mCutDuration;    private EditText mCutPoint;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main1);        mButton = (Button) findViewById(R.id.button);        mCutDuration = (EditText) findViewById(R.id.et_cutduration);        mCutPoint = (EditText)findViewById(R.id.et_cutpoint);        mButton.setOnClickListener(this);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    @Override    public void onClick(View v) {        new VideoClip().clipVideo(                Environment.getExternalStorageDirectory() + "/" + "节目.mp4",                Integer.parseInt(mCutPoint.getText().toString())*1000*1000,                Integer.parseInt(mCutDuration.getText().toString())*1000*1000);    }}

原文发布于微信公众号 - 何俊林(DriodDeveloper)

原文发表时间:2016-12-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android中高级开发

Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

2211
来自专栏james大数据架构

在Android中调用WebService

某些情况下我们可能需要与Mysql或者Oracle数据库进行数据交互,有些朋友的第一反应就是直接在Android中加载驱动然后进行数据的增删改查。我个人不推荐这...

2645
来自专栏向治洪

listview的工作原理

/**      * Unsorted views that can be used by the adapter as a convert vi...

21810
来自专栏小鄧子的技术博客专栏

RecyclerView技术栈参考资料:

随着2014年Google IO的召开,Android L Preview版随之发布,对于开发着来说,带来了性能上的改善,而对于消费者来说,得到了体验上的提升。...

971
来自专栏潇涧技术专栏

Head First Android SwipeRefreshLayout

本文内容和代码参考自Implementing Swipe to Refresh, an Android Material Design UI Pattern,原...

1022
来自专栏何俊林

Android支付实践(二)之微信支付详解与Demo

前言:集成支持宝和微信支付,是公司获取收益的最主要的部分,这两大巨头几乎在支付业务上不可或缺,今天看下Simon_Crystin独家授权本公众号发步的Andro...

2326
来自专栏向治洪

android来电归属地提醒

现在市面上常用的一些拨号软件的一个功能,来电归属地。拨号的时候,会在拨号界面出现一个号码归属地的小框框。效果如下:而且这个小窗体还可以自定义风格,并且可以自由移...

2527
来自专栏Android干货

关于Activity销毁,而绘制UI的子线程未销毁出现的问题

3716
来自专栏Android群英传

不需要再手写 onSaveInstanceState 了,因为你的时间非常值钱

901
来自专栏向治洪

android wheelview实现三级城市选择

很早之前看淘宝就有了ios那种的城市选择控件,当时也看到网友有分享,不过那个写的很烂,后来(大概是去年吧),我们公司有这么一个项目,当时用的还是网上比较流行的那...

4306

扫码关注云+社区

领取腾讯云代金券