EasyPusher是一款非常棒的推送客户端。稳定、高效、低延迟,音视频同步等都特别好。装在安卓上可作为一款单兵设备来用。说到单兵,在项目中通常都需要边传边录的功能,因此后来EasyPusher也加入了该特性。该文章将结合代码来阐述下这个功能是如何实现的。
EasyPusher在设置里增加了相关选项来方便开启和关闭本地录像功能,如下图所示,在设置里勾选后就可以推送的同时进行录像了。
EasyPusher用来实现录像的类叫做EasyMuxer,该类对安卓系统的MediaMuxer进行了一些封装,专门对从MediaCodec编码出来的音视频数据进行录像,同时实现了录像自动分段存储的功能。其构造函数如下:
public EasyMuxer(String path, long durationMillis) {
mFilePath = path;
this.durationMillis = durationMillis;
Object mux = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mux = new MediaMuxer(path + "-" + index++ + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
mMuxer = (MediaMuxer) mux;
}
}
构造函数有两个参数,第一个参数为本次录像的文件路径;第二个参数为单个文件最大时长,用来做录像分段(由于MediaMuxer在JELLY_BEAN_MR2以上才加入,因此这里做了判断,低于该版本的系统直接忽略了)。函数内部创建了一个MediaMuxer对象,用来实现真正的录像存储功能。
MediaMuxer在录像之前需要加入Video Track和Audio Track,EasyMuxuer进行了如下封装。
public synchronized void addTrack(MediaFormat format, boolean isVideo) {
// now that we have the Magic Goodies, start the muxer
if (mAudioTrackIndex != -1 && mVideoTrackIndex != -1)
throw new RuntimeException("already add all tracks");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
int track = mMuxer.addTrack(format);
if (VERBOSE)
Log.i(TAG, String.format("addTrack %s result %d", isVideo ? "video" : "audio", track));
if (isVideo) {
mVideoFormat = format;
mVideoTrackIndex = track;
if (mAudioTrackIndex != -1) {
if (VERBOSE)
Log.i(TAG, "both audio and video added,and muxer is started");
mMuxer.start();
mBeginMillis = System.currentTimeMillis();
}
} else {
mAudioFormat = format;
mAudioTrackIndex = track;
if (mVideoTrackIndex != -1) {
mMuxer.start();
mBeginMillis = System.currentTimeMillis();
}
}
}
}
这个函数有两个参数,第一个为要加入的媒体流的MediaFormat,可通过MediaCodec获取到,第二个表示加入的是Video还是Audio。从代码可以看到,在音频和视频都添加成功后会自动启动录像,这样做的好处是应用层的音视频的处理完全可以独立,不用操心录像的事情,减少了代码耦合性。同时,录像启动的时候会记录下来开始时间,后面需要用这个开始时间来计算文件时长。
录像启动后,接下来就应该保存数据了。我们再继续看下面的函数封装。
public synchronized void pumpStream(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo, boolean isVideo) {
if (mAudioTrackIndex == -1 || mVideoTrackIndex == -1) {
Log.i(TAG, String.format("pumpStream [%s] but muxer is not start.ignore..", isVideo ? "video" : "audio"));
return;
}
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// The codec config data was pulled out and fed to the muxer when we got
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
} else if (bufferInfo.size != 0) {
if (isVideo && mVideoTrackIndex == -