资料:本文基于android 7.1.2 版本源码分析。
功能:MediaRecorder即可以录制音频,也可以录制视频。
附上相关内容:
【二】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【三】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【四】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【五】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
一、 MediaRecorder整体框架图
1.1、 MediaRecorder整体层级关系图 – 各个具体类之间的依赖关系图:
概要总结:
如上在运行时,整个MediaRecorder大致上可以分为Client和Server两个部分,分别在两个进程中运行,它们之间使用Binder机制实现IPC通信。
MediaPlayerService是多媒体框架中非常重要的一个服务,从框架图中可以看出,MediaRecorder是客户端,MediaPlayerService和MediaRecorderClient是服务器端,MediaPlayerService实现了IMediaPlayerService(接口类)定义的业务逻辑。MediaRecorderClient实现了IMediaRecorder(接口类)定义的业务逻辑,其主要功能包括prepare、start、pause、resume、stop、reset、release等。
C++/JNI层回调事件(JNI层:mr->setListener(listener):将回调接口类MediaRecorderListener设置给MediaRecorder)通知JAVA层是使用JNIMediaRecorderListener::notify(int msg, int ext1, int ext2),该方法通过调用MediaRecoder.java类中private static void postEventFromNative(Object mediarecorder_ref, int what, int arg1, int arg2, Object obj)方法在JNI中的方法句柄,把Native事件回调到Java层,然后使用EventHandler.java类 post 事件回到主线层。
C++层Binder通信总结:【另外章节分析】
Android C++底层Binder通信机制原理总结
1.2、 MediaRecorder State diagram 状态流转图:(一个简单的状态机)
此处图片直接引用于官方文档MediaRecorder.java简介:
https://developer.android.google.cn/reference/android/media/MediaRecorder.html#summary
这个状态迁移这里就不详细描述了,主要根据各个状态和触发的条件进行状态转换,图上是一个状态机的实现的说明已经足够了,不过值得注意的是还有两个状态函数是pause()【暂停】和resume()【恢复】录制功能,图上没有的。
二、 流程实现浅析
该部分主要是根据MediaRecorder在使用中的主要的各模块和代码层的调用流程来进行分析整体架构中涉及到的主要类关系及其实现关系等。
主要根据一个基本的录制音视频的调用流程开始分析:
1、创建:new MediaRecorder();
2、设置Camera:mRecorder.setCamera();
3、设置音频源(采集方式):mRecorder.setAudioSource();
4、设置视频源(采集方式):mRecorder.setVideoSource();
5、设置文件的输出格式:mRecorder.setOutputFormat();
6、设置Audio的编码格式(生成对应的编码器):mRecorder.setAudioEncoder();
7、设置Video的编码格式(生成对应的编码器):mRecorder.setVideoEncoder();
8、设置录制的视频编码比特率(每秒编码多少位bit):mRecorder.setVideoEncodingBitRate();
9、设置录制的视频帧率:mRecorder.setVideoFrameRate();
10、设置要捕获的视频的宽度和高度:mRecorder.setVideoSize();
11、设置记录会话的最大持续时间(毫秒):mRecorder.setMaxDuration();
12、设置一个Surface进行预览显示:mRecorder.setPreviewDisplay();
13、设置输出文件路径:mRecorder.setOutputFile();
14、准备录制:mRecorder.prepare();
15、开始录制:mRecorder.start();
16、暂停或恢复录制:mRecorder.pause()/resume();
17、停止录制:mRecorder.stop();
18、重置Recorder:mRecorder.reset();
19、释放Recorder资源:mRecorder.release();
如上:
1、 创建:new MediaRecorder() 源码
/**
* Default constructor.
*/
public MediaRecorder() {
// 定义一个Looper,当前线程或主线程中的Looper实现者,主要用于JNI层回调时切换到当前App端线程中
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
String packageName = ActivityThread.currentPackageName();
// 创建MediaPlayer,在Native实现,使用弱引用
native_setup(new WeakReference<MediaRecorder>(this), packageName,
ActivityThread.currentOpPackageName());
}
// 静态代码中,加载和链接media_jni.so文件
static {
System.loadLibrary("media_jni");
native_init();
}
进入JNI层android_media_MediaRecorder.cpp的native_init()方法:
static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
// JNIEnv该类可以理解为一个万能指针表,通过操作符(->)访问JNI中的函数
// 类的class句柄
jclass clazz;
// 通过JNI层调用并获取JAVA层MediaRecorder对象
clazz = env->FindClass("android/media/MediaRecorder");
if (clazz == NULL) {
return;
}
// 获取java层成员变量mNativeContext,long类型,实际对应一个内存地址即Native层的MediaRecorder对象实例指针变量值转换的内存地址,用于缓存
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
// 获取java层成员变量mSurface
fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
if (fields.surface == NULL) {
return;
}
// 获取并存储一个回调JAVA层的静态回调函数,将native事件回调到java层
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
}
// JNI层对应调用的Java 层静态方法回调函数,将native事件回调到java层,用弱引用指向原生的MediaRecorder对象,来保证native代码安全的,并使用Handler机制切换线程到主线程中。
private static void postEventFromNative(Object mediarecorder_ref,
int what, int arg1, int arg2, Object obj)
{
MediaRecorder mr = (MediaRecorder)((WeakReference)mediarecorder_ref).get();
if (mr == null) {
return;
}
if (mr.mEventHandler != null) {
Message m = mr.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mr.mEventHandler.sendMessage(m);
}
}
前面构造函数中:native_setup实现函数分析
static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jstring packageName, jstring opPackageName)
{
// 创建JNI层MediaRecorder实例
sp<MediaRecorder> mr = new MediaRecorder(String16(opPackageNameStr.c_str()));
// create new listener and give it to MediaRecorder
// 回调事件监听并设置,如此java层的回调监听就能起作用
sp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this);
mr->setListener(listener);
// 该方法就是缓存当前创建的native层的MediaRecorder对象的指针变量值,用于缓存取用
setMediaRecorder(env, thiz, mr);
}
如此便设置了一些监听并创建了C++层对应实现的MediaRecorder对象。
再看C++层MediaRecorder构造函数
MediaRecorder::MediaRecorder(const String16& opPackageName) : mSurfaceMediaSource(NULL)
{
// 前面所讲通过Binder机制获取到BpMediaPlayerService代理对象
const sp<IMediaPlayerService> service(getMediaPlayerService());
if (service != NULL) {
// 以此来创建一个BpMediaRecorder代理对象,用此mMediaRecorder变量通过Binder机制来获取操作 MediaRecorderClient服务器端的实现业务功能。
mMediaRecorder = service->createMediaRecorder(opPackageName);
}
if (mMediaRecorder != NULL) {
// 初始化状态录制为闲置状态
mCurrentState = MEDIA_RECORDER_IDLE;
}
}
查看服务器端的实现:创建了MediaRecorderClient【Bn对象】
sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(const String16 &opPackageName)
{
pid_t pid = IPCThreadState::self()->getCallingPid();
sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, pid, opPackageName);
wp<MediaRecorderClient> w = recorder;
Mutex::Autolock lock(mLock);
mMediaRecorderClients.add(w);
return recorder;
}
查看MediaRecorderClient构造函数:
MediaRecorderClient::MediaRecorderClient(const sp<MediaPlayerService>& service, pid_t pid,
const String16& opPackageName)
{
// 最终通过Factory工厂配置创建了一个StageFrightRecorder对象,基本所有数据的操作都发生在该类中实现的,非常重要的功能实现类
mRecorder = AVMediaServiceFactory::get()->createStagefrightRecorder(opPackageName);
}
StagefrightRecorder *AVMediaServiceFactory::createStagefrightRecorder(
const String16 &opPackageName) {
// 工厂方式创建了一个StageFrightRecorder对象
return new StagefrightRecorder(opPackageName);
}
再看StagefrightRecorder构造函数:就是一些初始化操作,因此到此基本可以确定录制大部分功能都最终由它实现和管理的
StagefrightRecorder::StagefrightRecorder(const String16 &opPackageName)
: MediaRecorderBase(opPackageName),
mWriter(NULL),
mOutputFd(-1),
mAudioSource(AUDIO_SOURCE_CNT),
mVideoSource(VIDEO_SOURCE_LIST_END),
mStarted(false) {
reset();
}
2、 设置Camera:mRecorder.setCamera();
static void android_media_MediaRecorder_setCamera(JNIEnv* env, jobject thiz, jobject camera)
{
// 通过java层Camera对象获取Native层中的Camera对象实例
sp<Camera> c = get_native_camera(env, camera, NULL);
// 获取缓存的JNI层MediaRecorder实例对象
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
// mr->setCamera() 该方法设置Native层的Camera及其录制代理对象实例传入JNI层的MediaRecorder中,
process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
"java/lang/RuntimeException", "setCamera failed.");
}
然后看下:
status_t MediaRecorder::setCamera(const sp<hardware::ICamera>& camera,
const sp<ICameraRecordingProxy>& proxy)
{
// 最终调用了前面MediaRecorder构造函数中获取到的Bp代理对象
status_t ret = mMediaRecorder->setCamera(camera, proxy);
return ret;
}
然后查看其MediaRecorderClient服务器端的对应业务实现:
status_t MediaRecorderClient::setCamera(const sp<hardware::ICamera>& camera,
const sp<ICameraRecordingProxy>& proxy)
{
// mRecorder前面分析出是StageFrightRecorder实例。
return mRecorder->setCamera(camera, proxy);
}
最终还是调用到了StageFrightRecorder中对应的该方法:
status_t StagefrightRecorder::setCamera(const sp<hardware::ICamera> &camera,
const sp<ICameraRecordingProxy> &proxy) {
// 最终在StageFrightRecorder中缓存下来了Camera相关的实例对象
mCamera = camera;
mCameraProxy = proxy;
return OK;
}
因此后面一些简单的调用,我们也能直接猜到肯定最终都会调用到了StageFright框架层的StageFrightRecorder实现中,如下直接将最终对应的调用关系写出:
3、 设置音频源(采集方式):mRecorder.setAudioSource()
最终调用了底层该方法,并将音频来源采集方法枚举值存放下来,后面创建真正的音频源实现
status_t StagefrightRecorder::setAudioSource(audio_source_t as) {
if (as == AUDIO_SOURCE_DEFAULT) {
mAudioSource = AUDIO_SOURCE_MIC;
} else {
mAudioSource = as;
}
return OK;
}
4、 设置视频源(采集方式):mRecorder.setVideoSource();
与音频源类似的设置,缓存视频来源选择的枚举类型
status_t StagefrightRecorder::setVideoSource(video_source vs) {
ALOGV("setVideoSource: %d", vs);
if (vs < VIDEO_SOURCE_DEFAULT ||
vs >= VIDEO_SOURCE_LIST_END) {
ALOGE("Invalid video source: %d", vs);
return BAD_VALUE;
}
if (vs == VIDEO_SOURCE_DEFAULT) {
mVideoSource = VIDEO_SOURCE_CAMERA;
} else {
mVideoSource = vs;
}
return OK;
}
5、 设置文件的输出格式:mRecorder.setOutputFormat();
最终调用了该方法,缓存上层设置文件的输出格式枚举类型,用于后面编码最终生成的文件
status_t StagefrightRecorder::setOutputFormat(output_format of) {
if (of == OUTPUT_FORMAT_DEFAULT) {
mOutputFormat = OUTPUT_FORMAT_THREE_GPP;
} else {
mOutputFormat = of;
}
return OK;
}
6、 设置Audio的编码格式(生成对应的编码器):mRecorder.setAudioEncoder();
最终调用了并将用户设置的音频编码器对应的枚举值((audio_encoder)保存下来,后面开始编码时创建真正的编码器并编码
status_t StagefrightRecorder::setAudioEncoder(audio_encoder ae) {
if (ae == AUDIO_ENCODER_DEFAULT) {
mAudioEncoder = AUDIO_ENCODER_AMR_NB;
} else {
mAudioEncoder = ae;
}
return OK;
}
7、 设置Video的编码格式(生成对应的编码器):mRecorder.setVideoEncoder();
与音频类似,直接调用了StageFrightRecorder的对应方法,并将视频编码器对应的枚举值(video_encoder)缓存下来,以便编码器创建时使用
tatus_t StagefrightRecorder::setVideoEncoder(video_encoder ve) {
mVideoEncoder = ve;
return OK;
}
8、 设置录制的视频编码比特率(每秒编码多少位bit):mRecorder.setVideoEncodingBitRate();
Java层:
public void setVideoEncodingBitRate(int bitRate) {
if (bitRate <= 0) {
throw new IllegalArgumentException("Video encoding bit rate is not positive");
}
setParameter("video-param-encoding-bitrate=" + bitRate);
}
// 调用一个native方法
private native void setParameter(String nameValuePair);
// 然后调用
status_t StagefrightRecorder::setParameter(
const String8 &key, const String8 &value) {
if (key == "video-param-encoding-bitrate") {
int32_t video_bitrate;
if (safe_strtoi32(value.string(), &video_bitrate)) {
return setParamVideoEncodingBitRate(video_bitrate);
}
}
}
// 最终调用该方法进行了设置缓存
status_t StagefrightRecorder::setParamVideoEncodingBitRate(int32_t bitRate) {
mVideoBitRate = bitRate;
return OK;
}
9、 设置期望录制的视频帧率(系统可能会调整):mRecorder.setVideoFrameRate();
最终调用:缓存下来
status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) {
// Additional check on the frame rate will be performed later
mFrameRate = frames_per_second;
return OK;
}
10、 设置要捕获的视频的宽度和高度:mRecorder.setVideoSize();
最终调用:缓存下来做准备
status_t StagefrightRecorder::setVideoSize(int width, int height) {
// Additional check on the dimension will be performed later
mVideoWidth = width;
mVideoHeight = height;
return OK;
}
11、 设置记录会话的最大持续时间(毫秒):mRecorder.setMaxDuration();
static void
android_media_MediaRecorder_setMaxDuration(JNIEnv *env, jobject thiz, jint max_duration_ms)
{
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
char params[64];
// 设置对应的键值对类型拼接
sprintf(params, "max-duration=%d", max_duration_ms);
// 然后调用mr->setParameters
process_media_recorder_call(env, mr->setParameters(String8(params)), "java/lang/RuntimeException", "setMaxDuration failed.");
}
最终调用:缓存下来
status_t StagefrightRecorder::setParamMaxFileDurationUs(int64_t timeUs) {
mMaxFileDurationUs = timeUs;
return OK;
}
12、 设置一个Surface进行预览显示:mRecorder.setPreviewDisplay();
该方法前面将MediaRecorder对象创建的时候讲过该变量在native中直接进行缓存下来了
public void setPreviewDisplay(Surface sv) {
mSurface = sv;
}
// 缓存如下:
struct fields_t {
jfieldID context;
// 缓存对应java层的【mSurface】,后面prepare时使用
jfieldID surface;
jmethodID post_event;
};
13、 设置输出文件路径:mRecorder.setOutputFile();
最终调用该方法,将传入文件的描述符,该描述符是Linux系统操作文件的id
status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t length) {
// start with a clean, empty file
ftruncate(fd, 0);
if (mOutputFd >= 0) {
::close(mOutputFd);
}
mOutputFd = dup(fd);
return OK;
}
14、 准备录制:mRecorder.prepare();
JNI层:
static void
android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz)
{
// 获取此前缓存的C++层实现的MediaRecorder对象实例
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
// 获取此前缓存的java层Surface对象实例
jobject surface = env->GetObjectField(thiz, fields.surface);
if (surface != NULL) {
// 获取native层的Surface对象
const sp<Surface> native_surface = get_surface(env, surface);
// 此处重要下面分析
if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface->getIGraphicBufferProducer()), "java/lang/RuntimeException", "setPreviewSurface failed.")) {
return;
}
}
// 这句可不看,录像时一般都要求设置一个Surface对象用于预览影像,录音时才会用到这里
process_media_recorder_call(env, mr->prepare(), "java/io/IOException", "prepare failed.");
}
Surface相关:【录制的时候需要预览界面窗口】
mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
mr->setPreviewSurface(native_surface->getIGraphicBufferProducer());
这里的IGraphicBufferProducer就是APP与BufferQueue【数据源缓冲队列】的重要桥梁,GraphicBufferProducer承担着单个应用进程中的UI显示需求。GraphicBufferProducer负责与SurfaceFlinger交互,其向BufferQueue获取Buffer并且填充UI信息完成后通知SurfaceFlinger进行显示。
最终调用:
status_t StagefrightRecorder::setPreviewSurface(const sp<IGraphicBufferProducer> &surface) {
mPreviewSurface = surface;
return OK;
}
并将该实例缓存下来,后续显示图像使用。
不过后续还会调用真正的prepare方法,因此这里分析下为以下最终方法:
status_t StagefrightRecorder::prepareInternal() {
ALOGV("prepare");
if (mOutputFd < 0) {
ALOGE("Output file descriptor is invalid");
return INVALID_OPERATION;
}
// Get UID and PID here for permission checking
mClientUid = IPCThreadState::self()->getCallingUid();
mClientPid = IPCThreadState::self()->getCallingPid();
status_t status = OK;
// 此处根据上层设置的想要的输出文件格式,来初始化对应的录制控制器,此处分析MPEG4,其他同理分析即可。
switch (mOutputFormat) {
case OUTPUT_FORMAT_DEFAULT:
case OUTPUT_FORMAT_THREE_GPP:
case OUTPUT_FORMAT_MPEG_4:
case OUTPUT_FORMAT_WEBM:
// 初始化设置MPEG4或者WEB格式的开始录制
status = setupMPEG4orWEBMRecording();
break;
case OUTPUT_FORMAT_AMR_NB:
case OUTPUT_FORMAT_AMR_WB:
// 初始化设置AMR格式的开始录制
status = setupAMRRecording();
break;
case OUTPUT_FORMAT_AAC_ADIF:
case OUTPUT_FORMAT_AAC_ADTS:
// 初始化设置AAC格式的开始录制
status = setupAACRecording();
break;
case OUTPUT_FORMAT_RTP_AVP:
status = setupRTPRecording();
break;
case OUTPUT_FORMAT_MPEG2TS:
status = setupMPEG2TSRecording();
break;
default:
if (handleCustomRecording() != OK) {
ALOGE("Unsupported output file format: %d", mOutputFormat);
status = UNKNOWN_ERROR;
}
break;
}
ALOGV("Recording frameRate: %d captureFps: %f",
mFrameRate, mCaptureFps);
return status;
}
// 初始化设置MPEG4或者WEB格式的开始录制
status = setupMPEG4orWEBMRecording();
status_t StagefrightRecorder::setupMPEG4orWEBMRecording() {
// 这是 sp<MediaWriter> mWriter; 对象,此处对应是MPEG4Writer对象实例
mWriter.clear();
mTotalBitRate = 0;
status_t err = OK;
sp<MediaWriter> writer;
sp<MPEG4Writer> mp4writer;
if (mOutputFormat == OUTPUT_FORMAT_WEBM) {
writer = new WebmWriter(mOutputFd);
} else {
/** 此处只分析MPEG4 格式文件,因此运行这里,工厂创建了一个MPEG4对象如下代码:MPEG4Writer* AVFactory::CreateMPEG4Writer(int fd) {
传入了文件描述符,以便操作该文件如写入编码后的数据
return new MPEG4Writer(fd);
}
**/
writer = mp4writer = AVFactory::get()->CreateMPEG4Writer(mOutputFd);
}
if (mVideoSource < VIDEO_SOURCE_LIST_END) {
// 此处是默认设置编码器 如有有必要的话,里面会有一些配置参数的判断是否为默认值进行设置的,此处直接是采用H.264
setDefaultVideoEncoderIfNecessary();
sp<MediaSource> mediaSource;
/** 该方法主要是根据上层对应的视频源设置枚举值进行创建该对象来读取Camera中捕获的数据源,当然只有音频录制时不会创建该对象的,也不需要
并且此对象实际上是返回的CameraSource实例:
class CameraSource : public MediaSource, public MediaBufferObserver
因此可以对Camera中捕获的视频源数据进行处理。
最终会根据一个【mCaptureFpsEnable】参数来判断是否创建时使用光流逝录影CameraSourceTimeLapse【继承CameraSource】,默认不使用。
**/
err = setupMediaSource(&mediaSource);
if (err != OK) {
return err;
}
/**
此处初始化了一个视频编码器,传入了一个CameraSource类型的媒体源数据对象,
该方法中有:sp<MetaData> meta = cameraSource->getFormat(); 该调用即是从Camera捕获的视频源数据中获取视频源数据的长宽等一些该视频格式信息。然后又根据一些变量值处理来设置视频最终的格式数据等。
然后: sp<MediaCodecSource> encoder = MediaCodecSource::Create(
mLooper, format, cameraSource, mPersistentSurface, flags);
此句代码则根据上面的视频格式和配置参数数据等真正创建了最终的对应编码器:
struct MediaCodecSource : public MediaSource, public MediaBufferObserver;
此处能够看得出来刚好与上面的CameraSource视频数据来源对象的接口一致的,这样在MediaCodecSource里面就能与CameraSource做消息传递交互和数据的获取等操作了,这样就把视频源数据和视频编码器绑定了。然后MediaCodec就可以不断的从CameraSource中拉取视频源数据处理了。
这里面还有一套ALooper、AHandler和AMessage机制,即可以看做是java层的那套Handler的消息机制呗,通过回调来处理事件和数据等,如此就可以异步处理了。
具体实现逻辑是:
MediaCodecSource里面的Handler机制,有一个Puller继承AHandler的实例,该实例拥有了CameraSource视频来源对象实例,如此就能通过控制和调用其对应方法进行控制CameraSource的行为并且也可以读取其视频源数据,如:
status_t err = mSource->start(static_cast<MetaData *>(obj.get()));通知开始录制。
mSource->stop();通知结束停止录制。
status_t err = mSource->read(&mbuf);获取CameraSource里面缓存的Buffer源数据来进行编码处理。
这块源码看完了比较复杂,后续有时间将会单独进行分析。
**/
sp<MediaCodecSource> encoder;
err = setupVideoEncoder(mediaSource, &encoder);
if (err != OK) {
return err;
}
/**
然后将编码器encoder添加到MPEG4Writer的writer对象中,
Track *track = new Track(this, source /** encoder实例*/, 1 + mTracks.size()); 并且将writer和encoder都放入了一个Track中,如此就能使用Track来对writer、encoder、CameraSource进行操作处理的能力,最终写入编码后的数据也是由track来完成的。该track会被放入List<Track *> mTracks;中缓存起来。每个音频源和视频源都对应有一个Track对象实例来处理。
**/
writer->addSource(encoder);
// 赋值缓存给全局视频源编码器
mVideoEncoderSource = encoder;
// 总的比特率
mTotalBitRate += mVideoBitRate;
}
if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
// Audio source is added at the end if it exists.
// This help make sure that the "recoding" sound is suppressed for
// camcorder applications in the recorded files.
// TODO Audio source is currently unsupported for webm output; vorbis encoder needed.
// disable audio for time lapse recording
bool disableAudio = mCaptureFpsEnable && mCaptureFps < mFrameRate;
if (!disableAudio && mAudioSource != AUDIO_SOURCE_CNT) {
// 创建了一个音频源编码器,与上面那个视频源差不多,也是放入了writer对象中,并且新放入了一个新的Track对象中来单独对音频源数据处理。
err = setupAudioEncoder(writer);
if (err != OK) return err;
mTotalBitRate += mAudioBitRate;
}
// …… 省略部分代码
if (mVideoSource == VIDEO_SOURCE_DEFAULT
|| mVideoSource == VIDEO_SOURCE_CAMERA) {
mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId);
} else if (mVideoSource == VIDEO_SOURCE_SURFACE) {
// surface source doesn't need large initial delay
// 设置开始时间偏移200ms
mStartTimeOffsetMs = 200;
}
if (mStartTimeOffsetMs > 0) {
// 传入进write
writer->setStartTimeOffsetMs(mStartTimeOffsetMs);
}
// 设置监听回调对象,通过该对象C++层就可以把native事件一路回调到JAVA层
writer->setListener(mListener);
mWriter = writer;
return OK;
}
15、 开始录制:mRecorder.start();
开始录制是比较复杂的,如下处理分析:
status_t StagefrightRecorder::start() {
ALOGV("start");
if (mOutputFd < 0) {
ALOGE("Output file descriptor is invalid");
return INVALID_OPERATION;
}
status_t status = OK;
if (mVideoSource != VIDEO_SOURCE_SURFACE) {
// 这句是视频源即录制时需要调用准备过程的状态,因为前面在视频准备阶段并未真正执行,此处就需要执行了,该方法上面已经分析了
status = prepareInternal();
if (status != OK) {
return status;
}
}
switch (mOutputFormat) {
case OUTPUT_FORMAT_DEFAULT:
case OUTPUT_FORMAT_THREE_GPP:
case OUTPUT_FORMAT_MPEG_4:
case OUTPUT_FORMAT_WEBM:
{
bool isMPEG4 = true;
if (mOutputFormat == OUTPUT_FORMAT_WEBM) {
isMPEG4 = false;
}
// 创建一个数据源格式的元数据对象,用以保存MPEG4格式的元数据即格式数据如视频开始时间、视频文件类型,视频总比特率等,以此来获取视频数据的特定格式信息。
sp<MetaData> meta = new MetaData;
setupMPEG4orWEBMMetaData(&meta);
/** 然后调用MPEG4的start函数并传入该meta对象,以此开始录制音视频。
内部start方法处理基本如下:封装box结构的视频格式数据,然后
调用非常重要的 【startWriterThread()】开启另一个线程来进行不断从CameraSource的read函数中获取从Driver层返回的音视频源数据,分别在各自track中先处理。
然后还会调用【startTracks(param);】内部会遍历所有的track即音视频轨道的音视频源数据追踪对象让其各自都立即开始,【for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
status_t err = (*it)->start(params);】
并且各自的Track实例中都会有
status_t err = mSource->start(meta.get());如此操作,而这个source就是此前分析过的encoder编解码器,如此就会最终调用CameraSource.start真正的开始录制。
然后通过C++层的Handler消息机制进行异步事件和数据的传递等操作
**/
status = mWriter->start(meta.get());
break;
}
// ….. 省略部分代码
}
return status;
}
总结分析:MPEG4Writer读取的是最终encoder(OMXCodec)编码器进过编码后的数据,而encoder将CameraSource从driver层传递过来的源音视频数据进行编码等操作。
最终核心处理类是MPEG4Writer和Track对象,Track内部最终通过dump和MPEG4Writer调用write函数,将最终编码后的数据写入了文件,调用了文件写入函数write函数【::write(fd, result.string(), result.size());】处理的。
因此对于音视频两个轨道的元数据,进行了分Track进行记录和处理的:
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
(*it)->dump(fd, args);
}
直接遍历进行了数据在文件中进行合并了。
调用关系:
StagefrightRecorder::dump()->MPEG4Writer.dump()->遍历Track.dump(),最终写入文件数据。
由于篇幅或理解不够完全透彻,后续有时间再单独将start真正做的事情理清楚,单独拉出来仔细分析。
16、 暂停或恢复录制:mRecorder.pause()/resume();
status_t StagefrightRecorder::pause() {
if (mAudioEncoderSource != NULL) {
// 最终会调用音频编码器的pause,此处先不展开了,后续继续
mAudioEncoderSource->pause();
}
if (mVideoEncoderSource != NULL) {
// 最终会调用视频编码器的pause,此处先不展开了,后续继续
mVideoEncoderSource->pause();
}
mPauseStartTimeUs = systemTime() / 1000;
return OK;
}
17、 停止录制:mRecorder.stop();
status_t StagefrightRecorder::stop() {
if (mWriter != NULL) {
// 最终调用了MPEG4的stop()方法,然后调用了reset()方法,然后stopWriterThread();执行将读取线程停止,即可停止录制了,并release释放资源
err = mWriter->stop();
mWriter.clear();
}
return err;
}
18、 重置Recorder:mRecorder.reset();
与上stop差不多,停止write线程,并release释放资源
19、 释放Recorder资源:mRecorder.release();
关闭文件流,释放缓存buffer资源等
void MPEG4Writer::release() {
close(mFd);
mFd = -1;
mInitCheck = NO_INIT;
mStarted = false;
free(mMoovBoxBuffer);
mMoovBoxBuffer = NULL;
}
以上为大致的一个完整流程的初步分析,最终的如何编码的和如何进行音视频同步保存的,并且重新换个角度去思考音频及视频的处理两个流程的处理。后续也不会在详细从java层一直分析到c++层了,直接就在c++层的StageFright框架层进行分析这四部分内容,这一块会再下次给出来,本次由于时间短加上需要学习和了解大量的概念,也花了很多时间去学习,目前已差不多了,所以也有些能力可以将最终的这两个流程给完整的分析出来啦。
接下来一周需完成的目标任务:1、编码过程;2、音视频同步编码的实现;3、音频处理流程;4、视频处理流程。这四个过程会涉及OMX和IOMX,还有StageFright框架等。
目前看了这么多源码和分析后,收获如下:
1、 SourceInsight常用功能已经能够流畅的查阅庞大的源码并进行分析了。
2、 看这部分MediaRecorder的时候也顺便分析了MediaPlayer的流程,发现两者的框架架构及其相似,只是功能上是刚好相反而已,一个编码,一个解码,哈哈。
3、 这一两周内对音视频的大部分概念也有比较完整和清晰的学习和掌握,也查阅了许多优秀的书籍讲解和文章,受益匪浅,也借此希望自己不仅学到了“鱼”,也要掌握如何“渔”。
4、 对C++层Binder机制进行了深入的分析(不包括driver如何实现的,最终通过ioctl()函数进行驱动层的数据写入和读取的),对Bp和Bn架构设计实现已有了更深入的学习和掌握。
最后,音视频是我的最爱,我会加倍在音视频领域奋力前行的。
MediaRecorder本系列章节内容分析已在一两年前分析完成的,当初只是用于分析的笔记记录下来,如今已走上了我向往的音视频开发领域,也调研和参与过音视频相关技术,目前喜爱C++底层音视频技术的开发,偏向于底层的音视频复用/解复用、解码/编码、推流拉流技术等,而当初分析本章内容时音视频技术积累较少,因此若本文中分析有误还请多多指教,谢谢。
过了这么久时间才分享出来的原因是,以往技术知识也向前辈们分享文章有所收获,现如今我也可以有所技术分享给需要的人,也希望可以帮忙到需要的人,技术分享帮助他人的同时也能作为自身技术掌握的总结记录。
以往忙于技术深入和研究未有所分享的技术,往后坚持有质量的技术分享。
若有需要请查看后面章节分析:
【二】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【三】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【四】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析
【五】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析