本文转自EasyDarwin开源团队John的博客:http://blog.csdn.net/jyt0551/article/details/72787095

Android平台的MediaMuxer是个非常好的录像库,它能将H.264视频+AAC音频存储成.mp4格式的文件,而且稳定性、同步效果都非常好。

MediaMuxer在安卓版的EasyPlayer和EasyPusher都用到了该方法来进行本地录像。作者也写过两篇针对性的博客来做介绍,参考:

http://blog.csdn.net/jyt0551/article/details/60152344

http://blog.csdn.net/jyt0551/article/details/58714595

MediaMuxer的接口定义相对而言比较简单,调用过程如下图所示。

Created with Raphaël 2.1.0创建MediaMuxerAddVideoTrack、AddAudioTrackstart视频还是音频?writeVideoSamplestop releaseMP4 文件writeAudioSampleyesno

简单来说,就是创建对象、添加音视频轨道、开始、持续写入音视频数据、关闭这样一个过程。

遗憾的是,MediaMuxer并不支持对除AAC以外的音频编码格式的封装,然而在安防行业里G711音频格式的数据是大多数设备的默认编码格式。

如何支持G711格式的数据呢?其实换种思路就会豁然开朗,我们可以先把G711数据解码成PCM,再用MediaCodec编码成AAC,这样曲线存储^_^。不光是G711,所有的音频编码格式都可以这样做哈哈。。

所以前面的流程图里,writeAudioSample的部分就变成这样了:

Created with Raphaël 2.1.0音频AAC格式数据?writeAudioSampledecodepcmencodeaacyesno

下面是将解码后的PCM数据塞入Muxer的代码片段。

package org.easydarwin.audio;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.util.Log;

import org.easydarwin.video.EasyMuxer;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * 对EasyMuxer的扩展。支持对PCM格式的音频打包。
 */
public class EasyAACMuxer extends EasyMuxer {
    MediaCodec mMediaCodec;
    String TAG = "EasyAACMuxer";

    protected MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
    protected ByteBuffer[] mBuffers = null;

    private MediaFormat mAudioFormat;

    public EasyAACMuxer(String path, long durationMillis) {
        super(path, durationMillis);
    }

    @Override
    public synchronized void addTrack(MediaFormat format, boolean isVideo) {
        super.addTrack(format, isVideo);
        if (!isVideo){
            mAudioFormat = format;
        }
    }

    public synchronized void pumpPCMStream(byte []pcm, int length, long timeUs) throws IOException {
        if (mMediaCodec == null) {// 启动AAC编码器。这里用MediaCodec来编码
            if (mAudioFormat == null) return;
            mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
            Log.i(TAG, String.valueOf(mAudioFormat));
            mAudioFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
            mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 16000);
//            mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 320);

            mMediaCodec.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();
            mBuffers = mMediaCodec.getOutputBuffers();
        }
        int index = 0;
        // 将pcm编码成AAC
        do {
            index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 1000);
            if (index >= 0) {
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    continue;
                }
                if (mBufferInfo.presentationTimeUs == 0){
                    continue;
                }
                if (VERBOSE) Log.d(TAG,String.format("dequeueOutputBuffer data length:%d,tmUS:%d", mBufferInfo.size, mBufferInfo.presentationTimeUs));
                ByteBuffer outputBuffer = mBuffers[index];
                // ok,编码成功了。将AAC数据写入muxer.
                pumpStream(outputBuffer, mBufferInfo, false);
                mMediaCodec.releaseOutputBuffer(index, false);
            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                mBuffers = mMediaCodec.getOutputBuffers();
            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                Log.v(TAG, "output format changed...");
                MediaFormat newFormat = mMediaCodec.getOutputFormat();
                Log.v(TAG, "output format changed..." + newFormat);
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                Log.v(TAG, "No buffer available...");
            } else {
                Log.e(TAG, "Message: " + index);
            }
        } while (index >= 0 && !Thread.currentThread().isInterrupted());

        final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
        do {
            index = mMediaCodec.dequeueInputBuffer(1000);
            if (index >= 0) {
                inputBuffers[index].clear();
                inputBuffers[index].put(pcm, 0, length);
                if (VERBOSE) Log.d(TAG,String.format("queueInputBuffer pcm data length:%d,tmUS:%d", length, timeUs));
                mMediaCodec.queueInputBuffer(index, 0, length, timeUs, 0);
            }
        }
        while (!Thread.currentThread().isInterrupted() && index < 0);
    }

    @Override
    public synchronized void release() {
        if (mMediaCodec != null) mMediaCodec.release();
        mMediaCodec = null;
        super.release();
    }
}

一切都在代码中,不再过多解释,至此结束。

更多代码请查看EasyPlayer Github:https://github.com/EasyDarwin/EasyPlayer

获取更多信息

邮件:support@easydarwin.org

WEB:www.EasyDarwin.org

Copyright © EasyDarwin.org 2012-2017

EasyPlayer实现Android MediaMuxer录像MP4(支持G711/AAC/G726音频)的更多相关文章

  1. java攻城狮之路(Android篇)--MP3 MP4、拍照、国际化、样式主题、图片移动和缩放

    一.MP3播放器 查看Android API文档可以看到MediaPlayer状态转换图: 练习: package com.shellway.mp3player; import java.io.Fil ...

  2. Android:让WebView支持<input type=”file”…>元素

    最近在做一个活动页面:用户上传一张图片进行缩放.旋转后点击下一步填写内容后生成图片! 做好后经过各种测试是没有问题的,基本没有什么明显BUG,流程都能走通,但是嵌入到APP后,问题就来了! 在IOS上 ...

  3. Android 2.3 不支持印度文

    Android 2.3 不支持印度文(hindi),即使你指定了相关的字符串也不行,它们一律显示为方格. 实际上,你在系统的语言设置界面也可以看到,印度文一行也是被显示为方格(既然是方格,如何知道它是 ...

  4. 为Cocos2d-x的Android平台加入Protobuffer支持

    为Cocos2d-x的Android平台加入Protobuffer支持 分类: 工作2013-11-27 18:00 386人阅读 评论(1) 收藏 举报 cocos2d-xandroid平台交叉编译 ...

  5. 图解IntelliJ IDEA 13版本对Android SQLite数据库的支持

    IntelliJ IDEA 13版本的重要构建之一是支持Android程序开发.当然对Android SQLite数据库的支持也就成为了Android开发者对IntelliJ IDEA 13版本的绝对 ...

  6. CEF3编译添加mp4支持(对应CefSharp63.0.3),chromium63(3239),附release下载

    编译环境需求(3239版本) win7或更高,64位 vs2017 15.3.2+ 默认位置安装 不需要安装附带的win10sdk,sdk单独装 Windows 10.0.15063.468 SDK ...

  7. 【树莓派】【转】树莓派3装Android 6.0,支持Wi-Fi和蓝牙

    树莓派3装Android 6.0,支持Wi-Fi和蓝牙 相信对于许多树莓派初学者(包括我)来说,Android系统的确是一个不错的选择.但国内这方面资源稀缺,经本人FQ苦寻,找到了老外的树莓派Andr ...

  8. EasyPlayer RTSP Android安卓播放器修复播放画面卡在第一帧bug

    本文转自EasyDarwin团队成员John的博客:http://blog.csdn.net/jyt0551/article/details/75717097 最近发现某些手机在运行EasyPlaye ...

  9. EasyPlayer RTSP Android安卓播放器实现视频源快速切换

    EasyPlayer现在支持多视频源快速切换了,我们介绍一下是如何实现的. 这个需求通常应用在一个客户端需要查看多个视频源的情况,比如多个监控场景轮播. 由于EasyPlayer的播放端已经放在Fra ...

随机推荐

  1. python16_day15【Django入门】

    一.Django基本 1.什么是框架 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表 ...

  2. Excel常见操作,重复数据,去除数据关联

    Eecel对一个数据进行操作后按住右下角的十字架往下拉就可以对下面的操作进行相同 的操作,所以只需先对一个数据进行操作,再拉下来就可以了 通过公式处理的数据跟其它数据有关联 需要对这些数据进行去除它们 ...

  3. 将php数组存取到本地文件

    存数组: <?php $data = array( "a" => "aaaaaa", "b" => "bbbbb ...

  4. 详解HTTP中GET与POST的区别,不是你看过的标准答案!

    防吐槽声明:本文适合程序员新人,自认阅文无数.技术超叼的大神不用看. GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含 ...

  5. Canvas:绘制路径

    Canvas:绘制路径 绘制路径 图形的基本元素是路径.路径是[通过不同颜色和宽度的线段或曲线相连形成的不同形状的]点的集合.一个路径,甚至一个子路径,都是闭合的. 使用路径绘制图形需要一些额外的步骤 ...

  6. Linux系统配置VI或VIM的技巧

    Linux系统配置VI或VIM的技巧作者:IT专家网论坛出处:IT专家网论坛2008-10-28 11:08配置VI和VIM的颜色显示,使它能够高亮度显示一些特别的单词,这对编写程序很有用⋯⋯ 1.V ...

  7. seven habits of highly effective people 高效能人士的七个习惯

    习惯的模型 : dependent 依赖  -- independent 独立自主 --interdependent  互相依赖 1: be  proactive 主动积极 what you can ...

  8. with as (cte common table expression) 公共表表达式

    SQL中 with as 的用法——使用公用表表达式(CTE)  公用表表达式 (CTE) 可以认为是在单个 SELECT.INSERT.UPDATE.DELETE 或 CREATE VIEW 语句的 ...

  9. 【笔记】Maven使用入门

    参考<maven实战> 1.编写POM 2.编写主代码 3.编写测试代码 4.打包和运行 具体如下: 1.编写POM. <!-- XML头,指定了该xml文档的版本和编辑方式 --& ...

  10. D3学习之动画和变换

    D3学习之动画和变换 ##(17.02.27-02.28) 主要学习到了D3对动画和缓动函数的一些应用,结合前面的选择器.监听事件.自定义插值器等,拓展了动画的效果和样式. 主要内容 单元素动画 多元 ...