Android音视频处理—通过MediaMuexr合成MP4文件

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

1.MP4基本概念
MP4文件中的所有数据都装在box(QuickTime中为atom)中,也就是说MP4文件由若干个box组成,每个box有类型和长度,可以将box理解为一个数据对象块。box中可以包含另一个box,这种box称为container box。一个MP4文件首先会有且只有一个“ftyp”类型的box,作为MP4格式的标志并包含关于文件的一些信息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒体的metadata信息;MP4文件的媒体数据包含在“mdat”类型的box(Midia
Data Box)中,该类型的box也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结构由metadata进行描述。
下面是一些概念:
track 表示一些sample的集合,对于媒体数据来说,track表示一个视频或音频序列。
hint track 这个特殊的track并不包含媒体数据,而是包含了一些将其他数据track打包成流媒体的指示信息。
sample 对于非hint track来说,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频,它们统称sample。对于hint track,sample定义一个或多个流媒体包的格式。
sample table 指明sampe时序和物理布局的表。
chunk 一个track的几个sample组成的单元。

2. h264
h264帧的类型主要有

typedef enum {
 NALU_TYPE_SLICE    = 1,
 NALU_TYPE_DPA      = 2,
 NALU_TYPE_DPB      = 3,
 NALU_TYPE_DPC      = 4,
 NALU_TYPE_IDR      = 5,
 NALU_TYPE_SEI      = 6,
 NALU_TYPE_SPS      = 7,
 NALU_TYPE_PPS      = 8,
 NALU_TYPE_AUD      = 9,
 NALU_TYPE_EOSEQ    = 10,
 NALU_TYPE_EOSTREAM = 11,
 NALU_TYPE_FILL     = 12,
#if (MVC_EXTENSION_ENABLE)
 NALU_TYPE_PREFIX   = 14,
 NALU_TYPE_SUB_SPS  = 15,
 NALU_TYPE_SLC_EXT  = 20,
 NALU_TYPE_VDRD     = 24  // View and Dependency Representation Delimiter NAL Unit
#endif
} NaluType;

在Android中能够涉及到的就SPS、PPS和IDR三种类型的帧数据,其中

SPS(序列参数集Sequence Parameter Set)是H.264码流第一个 NALU

PPS(图像参数集Picture Parameter Set)是H.264码流第二个 NALU是

IDR(即时解码器刷新)是H.264码流第三个 NALU

3.Android音视频处理
MediaMuxer类主要用于将音频和视频数据进行混合生成多媒体文件(如:mp4文件),而MediaExtractor则刚好相反,主要用于多媒体文件的音视频数据的分离。

Demo下载:https://github.com/haibaobao9/MediaDemo

该Demo主要功能是通过MediaExtractor将MP4文件分离成音频和视频然后使用MediaMuxer将视频数据合成单独的MP4,使用是需要将project中input.mp4文件放入SD卡根目录。

1)MediaExtractor
该类主要用于音视频混合数据的分离,接口比较简单,首先要通过setDataSource(String path)函数设置数据源,数据源可以是本地文件地址,也可以使用HTTP协议的网络码流地址。
然后,可以通过下面的代码块,来获取码流的详细信息,如:MimeType,分辨率、编码格式、码率、帧率等等。

int videoTrackIndex = -1;
int audioTrackIndex = -1;

for(int i = 0; i < mMediaExtractor.getTrackCount(); i++) {

    //获取码流的详细格式/配置信息
    MediaFormat format = mMediaExtractor.getTrackFormat(i);

    String mime = format.getString(MediaFormat.KEY_MIME);
    if(mime.startsWith("video/")) {
        videoTrackIndex = i;
    }
    else if(mime.startsWith("audio/")) {
        audioTrackIndex = i;
    }

    ....
}

获取到媒体文件的详细信息之后,就可以选择指定的通道,并分离和读取数据了:

mMediaExtractor.selectTrack(videoTrackIndex); //选择读取视频数据
while(true) {
    int sampleSize = mMediaExtractor.readSampleData(buffer, 0);  //读取一帧数据
    if(sampleSize < 0) {
        break;
    }
    mMediaExtractor.advance(); //移动到下一帧
}

mMediaExtractor.release(); //读取结束后,要记得释放资源

2)MediaMuxer

该类主要用于将音频和视频进行混合生成多媒体文件,创建该类对象,需要传入输出的文件位置以及格式,构造函数如下:

public MediaMuxer(String path, int format);

创建对象之后,一个比较重要的操作就是addTrack(),添加数据通道,该函数需要传入MediaFormat对象,MediaFormat即媒体格式类,用于描述媒体的格式参数,如视频帧率、音频采样率等。

核心代码:

 protected boolean process() throws IOException {

      mMediaExtractor = new MediaExtractor();          
      mMediaExtractor.setDataSource(SDCARD_PATH+"/input.mp4");                
              
      int mVideoTrackIndex = -1;
      int framerate = 0;
      for(int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
          MediaFormat format = mMediaExtractor.getTrackFormat(i);
          String mime = format.getString(MediaFormat.KEY_MIME);
          if(!mime.startsWith("video/")) {                
              continue;
          }
          framerate = format.getInteger(MediaFormat.KEY_FRAME_RATE);            
          mMediaExtractor.selectTrack(i);
          mMediaMuxer = new MediaMuxer(SDCARD_PATH+"/ouput.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
          mVideoTrackIndex = mMediaMuxer.addTrack(format);  
          mMediaMuxer.start();
      }
      
      if(mMediaMuxer == null) {
          return false;
      }
      
      BufferInfo info = new BufferInfo();
      info.presentationTimeUs = 0;
      ByteBuffer buffer = ByteBuffer.allocate(500*1024);        
      while(true) {
          int sampleSize = mMediaExtractor.readSampleData(buffer, 0);
          if(sampleSize < 0) {
              break;
          }
          mMediaExtractor.advance();
          info.offset = 0;
          info.size = sampleSize;
          info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;        
          info.presentationTimeUs += 1000*1000/framerate;
          mMediaMuxer.writeSampleData(mVideoTrackIndex,buffer,info);
      }

      mMediaExtractor.release();
      
      mMediaMuxer.stop();
      mMediaMuxer.release();
      
      return true;
  }

但是实际开发过程中绝大多数情况下都是直接通过h264的frame数据合成MP4,这就要求我们不能通过MediaExtractor.getTrackFormat()解析得到的MediaFormat对象,而是只能手动创建MediaFormat对象。

MediaFormat format = MediaFormat.createVideoFormat("video/avc",320,240);

手动创建MediaFormat对象后必须在writeSampleData之前将SPS和PPS的数据分别设置成"csd-0"和"csd-1"两个参数

byte[] csd0 = {x,x,x,x,x,x,x...}
byte[] csd1 = {x,x,x,x,x,x,x...}

format.setByteBuffer("csd-0",ByteBuffer.wrap(csd0));
format.setByteBuffer("csd-1",ByteBuffer.wrap(csd1));

设置完成后开始调用writeSampleData开始写入文件数据。

BufferInfo info = new BufferInfo();
info.offset = 0;
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs = timestamp;
mMediaMuxer.writeSampleData(mVideoTrackIndex,buffer,info);

info.size是填入数据的大小,即拿到帧数据byte数组的长度
info.flags是判断当前帧是否为关键帧,即当frame type == 5时填入true即可
info.presentationTimeUs 为正确的时间戳。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值