代码先锋网 代码片段及技术文章聚合

MediaExtractor、MediaMuxer 分离和合成 mp4

MediaExtractor

视音频分离器,将一些格式的视频分离出视频轨道和音频轨道。
主要流程,也是主要的 API:

  • setDataSource(String path):设置数据源
  • getTrackCount():获取通道数
  • getTrackFormat(int index):获取通道格式
  • readSampleData(ByteBuffer byteBuf, int offset):读取指定通道中的数据
  • getSampleTime():获取当前时间戳
  • advance():下一帧
  • release():释放资源

MediaMuxer

视音频合成器,将视频和音频合成相应的格式

  • MediaMuxer(String path, int format):初始化输出文件名称和输出文件的格式:mpeg4
  • addTrack(MediaFormat format):添加轨道,返回 track Index,会在 writeSampleData 中使用
  • start():开始合成文件
  • writeSampleData(int, ByteBuffer, MediaCodec.BufferInfo):写数据
  • stop():停止合成文件
  • release():释放资源
大体流程:

以下是分离 mp4 音频和视频的源码及注释:

    private String seperateMedia(String fileName, boolean isAudio) {
        String type = isAudio ? "audio/" : "video/";
        MediaExtractor mMediaExtractor = new MediaExtractor();;
        MediaMuxer mMediaMuxer = null;
        try {
            mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            // 获取assert中的资源文件
            AssetFileDescriptor fileDescriptor = getAssetFileSource();
            // 设置资源文件
            mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
            int mMediaIndex = 0;
            for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
                //获取码流的详细格式/配置信息
                MediaFormat format = mMediaExtractor.getTrackFormat(i);
                String mine = format.getString(MediaFormat.KEY_MIME);
                // 查找音频:"audio/" 或者视频:"video/"的轨道
                if (mine.startsWith(type)) {
                    mMediaIndex = i;
                    break;
                }
            }
            // 选择感兴趣的轨道
            mMediaExtractor.selectTrack(mMediaIndex);
            // 获取通道格式,可以自己新建,但是有坑
            MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);
            int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);
            // 当采集视频的使用,需要获取帧率,音频轨道没有这个参数
            int framerate = isAudio ? 0 : mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
            mMediaMuxer.start();

            ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
            int readSize = 0;
            // writeSampleData需要BufferInfo参数
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
                bufferInfo.size = readSize;
                bufferInfo.flags = mMediaExtractor.getSampleFlags(); //设置为关键帧等
                bufferInfo.offset = 0;
                if (!isAudio) { // 时间戳,音频和视频的处理方式不一样
                    bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;
                } else {
                    bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
                }
                mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);
                Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );
                mMediaExtractor.advance(); //下一帧
            }
            return "success";
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(mMediaExtractor != null ) {
                mMediaExtractor.release();
                mMediaExtractor = null;
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.stop();
                mMediaMuxer.release();
                mMediaMuxer = null;
            }
        }
        return null;
    }

坑点:

bufferInfo 信息中需要写入正确的 presentationTimeUs,而看网上的参考基本都是 MediaExtractor.getSampleTime(),但在实际使用中会异常报错,也有使用通过计算前两个关键帧的差值,然后逐步增加,因为而且 presentationTimeUs 是需要递增的,但是实际上最后分离出来的视频模糊卡顿的现象。

Android 中如何提取和生成 mp4 文件 中看到根据帧率来计算 presentationTimeUs,效果比较正常,结合音频使用 mMediaExtractor.getSampleTime();

视频: int frameRate=mediaFormat=getInteger(MediaFormat.KEY_FRAME_RATE); //1s 的帧数
bufferInfo.presentationTimeUs = 1000 * 1000/frameRate; // 对于帧率为 x f/s 的视频而言,时间戳的间隔就是 1000/x ms
音频: bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();

最近发现两个问题:
1. 为什么 getSampleTime() 不是逐步递增的?时间戳难道不是应该持续上升的吗?
2. 在某些机型上出现没有 MediaFormat.KEY_FRAME_RATE,该怎么计算 presentationTimeUs?

源码

public class MediaExtractorActivity extends AppCompatActivity implements View.OnClickListener{
    private static String TAG = MediaExtractorActivity.class.getSimpleName();
    public static String[] STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mediaextractor);
        findViewById(R.id.seperate_audio_btn).setOnClickListener(this);
        findViewById(R.id.seperate_media_btn).setOnClickListener(this);
        findViewById(R.id.muxer_btn).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, STORAGE, 2);
            return;
        }

        switch (view.getId()) {
            case R.id.seperate_audio_btn:
                new MediaAsyncTask().execute(1);
                break;
            case R.id.seperate_media_btn:
                new MediaAsyncTask().execute(2);
                break;
            case R.id.muxer_btn:
                new MediaAsyncTask().execute(3);
                break;
            default:
                break;
        }

        findViewById(R.id.seperate_audio_btn).setEnabled(false);
        findViewById(R.id.seperate_media_btn).setEnabled(false);
        findViewById(R.id.muxer_btn).setEnabled(false);
    }

    private class MediaAsyncTask extends AsyncTask<Integer, Integer, String> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Toast.makeText(MediaExtractorActivity.this, "开始转化", Toast.LENGTH_SHORT).show();
        }

        @Override
        protected String doInBackground(Integer... param) {
            if (param.length < 1){
                return null;
            }
            switch (param[0]) {
                case 1:
                    return seperateMedia("audio.mp4", true);
                case 2:
                    return seperateMedia("video.mp4", false);
                case 3:
                    return muxerMediaAndAudio("video.mp4","audio.mp4", "result.mp4");
                default:
                    return null;
            }
        }

        @Override
        protected void onPostExecute(String s) {
            findViewById(R.id.seperate_audio_btn).setEnabled(true);
            findViewById(R.id.seperate_media_btn).setEnabled(true);
            findViewById(R.id.muxer_btn).setEnabled(true);
            if (s != null) {
                Toast.makeText(MediaExtractorActivity.this, "转化完成 " + s, Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(MediaExtractorActivity.this, "转化失败", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private String muxerMediaAndAudio(String mediaFileName, String audioFileName, String resultName) {
        File mediaFile = new File(getSDPath(), mediaFileName);
        File audioFile = new File(getSDPath(), audioFileName);
        if (!mediaFile.exists() || !audioFile.exists()) {
            return "音视频文件不存在";
        }
        MediaMuxer mMediaMuxer = null;
        MediaExtractor mMediaExtractor = new MediaExtractor();
        MediaExtractor mAudioExtractor = new MediaExtractor();
        try {
            mMediaMuxer = new MediaMuxer(getSDPath() + "/" + resultName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            mMediaExtractor.setDataSource(mediaFile.getAbsolutePath());
            mAudioExtractor.setDataSource(audioFile.getAbsolutePath());

            int mMediaTrack = 0;
            int mAudioTrack = 0;

            for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
                String mine = mMediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);
                if (mine.startsWith("video/")) {
                    mMediaTrack = i;
                    break;
                }
            }
            for (int i = 0; i < mAudioExtractor.getTrackCount(); i++) {
                String mine = mAudioExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);
                if (mine.startsWith("audio/")) {
                    mAudioTrack = i;
                    break;
                }
            }
            mMediaExtractor.selectTrack(mMediaTrack);
            mAudioExtractor.selectTrack(mAudioTrack);

            MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaTrack);
            MediaFormat audioFormat = mAudioExtractor.getTrackFormat(mAudioTrack);

            MediaCodec.BufferInfo mediaBufferInfo = new MediaCodec.BufferInfo();
            MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();

            int mediaMuxerTrack = mMediaMuxer.addTrack(mediaFormat);
            int audioMuxerTrack = mMediaMuxer.addTrack(audioFormat);

            mMediaMuxer.start();
            ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
            int readSize = 0;

            int mMediaFramerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
            while ((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
                mediaBufferInfo.presentationTimeUs += 1000 * 1000 / mMediaFramerate;
                mediaBufferInfo.offset = 0;
                mediaBufferInfo.flags = mMediaExtractor.getSampleFlags();
                mediaBufferInfo.size = readSize;
                mMediaMuxer.writeSampleData(mediaMuxerTrack, byteBuffer, mediaBufferInfo);
                mMediaExtractor.advance();
            }

            while ((readSize = mAudioExtractor.readSampleData(byteBuffer, 0)) > 0) {
                audioBufferInfo.presentationTimeUs = mAudioExtractor.getSampleTime();
                audioBufferInfo.offset = 0;
                audioBufferInfo.flags = mAudioExtractor.getSampleFlags();
                audioBufferInfo.size = readSize;
                mMediaMuxer.writeSampleData(audioMuxerTrack, byteBuffer, audioBufferInfo);
                mAudioExtractor.advance();
            }
            mMediaMuxer.stop();
            return getSDPath() + resultName;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(mMediaExtractor != null ) {
                mMediaExtractor.release();
                mMediaExtractor = null;
            }
            if(mAudioExtractor != null ) {
                mAudioExtractor.release();
                mAudioExtractor = null;
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.release();
                mMediaMuxer = null;
            }
        }
        return null;
    }

    private String seperateMedia(String fileName, boolean isAudio) {
        String type = isAudio ? "audio/" : "video/";
        MediaExtractor mMediaExtractor = new MediaExtractor();;
        MediaMuxer mMediaMuxer = null;
        try {
            mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            // 获取assert中的资源文件
            AssetFileDescriptor fileDescriptor = getAssetFileSource();
            // 设置资源文件
            mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
            int mMediaIndex = 0;
            for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
                //获取码流的详细格式/配置信息
                MediaFormat format = mMediaExtractor.getTrackFormat(i);
                String mine = format.getString(MediaFormat.KEY_MIME);
                // 查找音频:"audio/" 或者视频:"video/"的轨道
                if (mine.startsWith(type)) {
                    mMediaIndex = i;
                    break;
                }
            }
            // 选择感兴趣的轨道
            mMediaExtractor.selectTrack(mMediaIndex);
            // 获取通道格式,可以自己新建,但是有坑
            MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);
            int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);
            // 当采集视频的使用,需要获取帧率,音频轨道没有这个参数
            int framerate = 0;
            if (mediaFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
                framerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
            }
            mMediaMuxer.start();

            ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);
            int readSize = 0;
            // writeSampleData需要BufferInfo参数
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {
                bufferInfo.size = readSize;
                bufferInfo.flags = mMediaExtractor.getSampleFlags(); //设置为关键帧等
                bufferInfo.offset = 0;
                if (framerate != 0) { // 时间戳,音频和视频的处理方式不一样
                    bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;
                } else {
                    bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
                }
                mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);
                Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );
                mMediaExtractor.advance(); //下一帧
            }
            mMediaMuxer.stop();
            return "success";
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(mMediaExtractor != null ) {
                mMediaExtractor.release();
                mMediaExtractor = null;
            }
            if(mMediaMuxer != null) {
                mMediaMuxer.release();
                mMediaMuxer = null;
            }
        }
        return null;
    }

    private AssetFileDescriptor getAssetFileSource() throws IOException {
        AssetManager manager = getAssets();
        return manager.openFd("screen_recorder.mp4");
    }

    public static String getSDPath() {
        // 判断是否挂载
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return Environment.getExternalStorageDirectory().getAbsolutePath();
        }
        return Environment.getRootDirectory().getAbsolutePath();
    }
}

参考:
http://blog.51cto.com/ticktick/1710743

版权声明:本文为qq_15893929原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_15893929/article/details/81514550

智能推荐

Android在NDK JNI中使用MediaMuxer合成mp4的流程

前言:找了很久,很少有NDK层使用合成mp4的例程,由于我们使用的是实时流合成mp4的文件,基本所有的视频流都是在C++操作的。因此写个笔记记录一下对实时流合成mp4格式视频。 1、要使用的对象: 2、创建对象:这里需要注意的是,一定要设置好视频流的vps/sps/pps数据。否则无法生生mp4文件。其中h265只需要使用csd_0。 3、写入数据:循环写入数据即可。这里写入的数据是h264/h2...

Android音视频开发-入门(四):使用 MediaExtractor 和 MediaMuxer API 解析和封装 mp4 文件...

好时光 春分已过,下个时节就是清明了,送上古人小诗一首: 咏柳 【作者】贺知章 【朝代】唐 碧玉妆成一树高,万条垂下绿丝绦。 不知细叶谁裁出,二月春风似剪刀。 MediaExtractor 抽取视频不含音频 MediaExtractor 抽取音频不含视频 MediaExtractor 抽取视频音频文件,然后利用MediaMuxer合成 以上代码全部测试通过,欢迎交流!!!...

Android音视频专题(四) MediaExtractor 和 MediaMuxer API,知道如何解析和封装 mp4 文件

一、MediaExtractor API介绍 MediaExtractor的作用是把音频和视频的数据进行分离。 主要API介绍: setDataSource(String path):即可以设置本地文件又可以设置网络文件 getTrackCount():得到源文件通道数  getTrackFormat(int index):获取指定(index)的通道格式 getSampleTime()...

Android分离合成音视频(用MediaExtractor和MediaMuxer)

前言: 最近在做类似小咖秀的视频录制功能,也就是俗称的对嘴型表演,录制视频我用的是三方SDK,但是视频合成就需要自己搞了,在网上搜了挺多资料,国内国外网站看了不少,踩了很多坑,总算整出来了,在此分享给大家,希望对以后要做类似功能的兄弟们有所帮助! 需求: 将视频一的音频提取出来,视频二的视频图像提取出来,然后把它们合成新的视频。 工具准备: 视频的分离合成我主要用到了MediaExtractor和...

Android音视频任务列表之(四)——学习 Android 平台的 MediaExtractor 和 MediaMuxer API,知道如何解析和封装 mp4 文件

一.主要使用的方法 MediaExtractor 1.setDataSource(String path) 设置数据源 2.getTrackFormat(int index) 获取指定索引通道的数据配置参数 3.selectTrack(int index) 选择轨道 4. getSampleTime() 获取当前pts时间戳,获取不到这返回-1 5.readSampleData(@NonNull ...

猜你喜欢

MediaMuxer+MediaCodec生成MP4视频报错

项目中遇到一个问题,现象是录制视频时,录制一段时间后应用会crash。我们项目中是用MediaMuxer+MediaCodec来录制,最后生成一个MP4格式视频。 关键报错信息如下: 可以看到是native报错,直接去找项目源码(https://android.googlesource.com/platform/frameworks/av/ 这里面可以看到framework的代码): 发现并没有相...

Android音视频处理之MediaMuxer(MP4)

http://www.jianshu.com/p/aeadf260258a 在Android中,可以使用MediaMuxer来封装编码后的视频流和音频流到mp4容器中: MediaMuxer facilitates muxing elementary streams. Currently supports mp4 or webm file as the output and at most one...

音视频开发之旅(五)MediaExtractor MediaMuxer 实现视频的解封装与合成

目录 MediaExtractor MediaMuxer 能做什么 视频解封装和合成的API以及流程介绍 三个实践(视频解封装提取纯音轨和视频轨文件、再合成新视频、给视频换个背景音) 遇到的问题 收获 一、有什么实际应用 在我们日常使用短视频软件的时候,对视频的裁剪,拼凑,加入背景是很常用的操作,这些功能是如何实现的呐?其实是将视频多信道的分离出来,比如音轨和视频轨道分隔出来,可以做到二次合成。 ...

yuv,pcm合成mp4

代码涉及音频编码,视频编码,pts计算,可参考前面的几篇博文,已经模块化 这里pts计算直接使用outputStream的时间基,不存在inputStream转outputStream时间基的问题。因为yuv和pcm都是裸数据,no pts。所以直接写pts值就行。如果是从aac和h264合成mp4那么就需要做输入输出流的时间基转换了。关于时间基的理解和转换,参考上篇深入理解pts,dts,tim...

Android多媒体之使用MediaExtractor与MediaMuxer分离音视频

MediaExtractor与MediaMuxer工具可以完美的将视频中的音视频分开来,可以对视频进行再配音合成达到自己的需求: 1.准备工作:声明基础变量 2.开始分离音视频: 该方法接受视频路径与所要分离出的媒体文件格式:audio或者video,根据媒体格式进行操作。 3.分离视频: 4.分离出音频: 5.释放资源: 转载于:https://my.oschina.net/fangfeiAI/...