安卓MediaRecorder(2)录制源码分析

文章目录

    • 前言
    • JAVA new MediaRecorder() 源码分析
      • android_media_MediaRecorder.cpp native_init()
      • MediaRecorder.java postEventFromNative
      • android_media_MediaRecorder.cpp native_setup()
    • MediaRecorder 参数设置
    • MediaRecorder.prepare 分析
    • MediaRecorder.start 分析
    • MediaRecorder.stop 分析
    • 结语

本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134634628
最新更新地址 https://gitee.com/chenjim/chenjimblog

前言

通过前文 安卓MediaRecorder(1)录制音频的详细使用,我们已经知道如何使用。
本文主要分析一下 Framework 中相关流程。
下图是谷歌提供的MediaRecorder状态关系图
在这里插入图片描述

JAVA new MediaRecorder() 源码分析

public class MediaRecorder implements AudioRouting,AudioRecordingMonitor,AudioRecordingMonitorClient,MicrophoneDirection {static {// 静态代码块,加载链接 liblibmedia_jni.soSystem.loadLibrary("media_jni");native_init();}// 已经废弃 public MediaRecorder() {// 传入 APP Contextthis(ActivityThread.currentApplication());}public MediaRecorder(@NonNull Context context) {// 要求 Context 不为空Objects.requireNonNull(context);// 创建EventHandler,主要用于JNI层回调时切换到当前App端线程中Looper looper;if ((looper = Looper.myLooper()) != null) {// 如果当前线程有Looper,就使用当前线程的LoopermEventHandler = new EventHandler(this, looper);} else if ((looper = Looper.getMainLooper()) != null) {// 使用主线程的 Looper,Jni回调信息会在主线程执行   mEventHandler = new EventHandler(this, looper);} else {mEventHandler = null;}// 录制音频的声道数,此处默认 1 (mono即单声道),2 (stereo即双声道立体声),可以通过 setAudioChannels 修改mChannelCount = 1;// 创建弱引用,并初始化try (ScopedParcelState attributionSourceState = context.getAttributionSource().asScopedParcelState()) {native_setup(new WeakReference<>(this), ActivityThread.currentPackageName(),attributionSourceState.getParcel());}}

android_media_MediaRecorder.cpp native_init()

获取JAVA层 android.media.MediaRecorder 对象
并将 JAVA 对象的属性 mNativeContext、mSurface、postEventFromNative 保存在 fields
详细代码如下

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{jclass clazz;clazz = env->FindClass("android/media/MediaRecorder");if (clazz == NULL) {return;}fields.context = env->GetFieldID(clazz, "mNativeContext", "J");if (fields.context == NULL) {return;}fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");if (fields.surface == NULL) {return;}jclass surface = env->FindClass("android/view/Surface");if (surface == NULL) {return;}fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");if (fields.post_event == NULL) {return;}clazz = env->FindClass("java/util/ArrayList");if (clazz == NULL) {return;}gArrayListFields.add = env->GetMethodID(clazz, "add", "(Ljava/lang/Object;)Z");gArrayListFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
}

MediaRecorder.java postEventFromNative

这个是Jni消息回来的接口,最终会发到 MediaRecorder.EventHandler 的 handleMessage 中
进而可以通过 MediaRecorder.OnInfoListener 、MediaRecorder.OnErrorListener、
AudioRouting.OnRoutingChangedListener 回调到 APP
对应源码如下

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);}
}// EventHandler 如下  
public class MediaRecorder {...private class EventHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch(msg.what) {case MEDIA_RECORDER_EVENT_ERROR:case MEDIA_RECORDER_TRACK_EVENT_ERROR:if (mOnErrorListener != null)mOnErrorListener.onError(mMediaRecorder, msg.arg1, msg.arg2);return;case MEDIA_RECORDER_EVENT_INFO:case MEDIA_RECORDER_TRACK_EVENT_INFO:if (mOnInfoListener != null)mOnInfoListener.onInfo(mMediaRecorder, msg.arg1, msg.arg2);return;case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:// 耳机使能的消息  return;}}}

android_media_MediaRecorder.cpp native_setup()

static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,jstring packageName, jobject jAttributionSource)
{// 构建 JNI 对象 MediaRecorder,attributionSource 可以认为是 JNI 中的上下文 Context sp<MediaRecorder> mr = new MediaRecorder(attributionSource);...// 创建 JNI JNIMediaRecorderListener , 其收到的消息最终通过 fields.post_event 回到JAVAsp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this);mr->setListener(listener);...// 传递客户端包名,以进行权限跟踪  mr->setClientName(clientName);// 将创建的 mr 保存到 fields.context,也就是 Java 层 MediaRecorder 中的 mNativeContext  setMediaRecorder(env, thiz, mr);
}

MediaRecorder JNI 构造

// frameworks/av/media/libmedia/mediarecorder.cpp
MediaRecorder::MediaRecorder(const AttributionSourceState &attributionSource): mSurfaceMediaSource(NULL)
{// 通过 binder 获取 MediaPlayerService const sp<IMediaPlayerService> service(getMediaPlayerService());if (service != NULL) {// 通过 MediaPlayerService 创建 MediaRecorderClient  mMediaRecorder = service->createMediaRecorder(attributionSource);}...
}

MediaRecorder 类关系如下

class MediaRecorder : public BnMediaRecorderClient, public virtual IMediaDeathNotifier {...}
class BnMediaRecorderClient: public BnInterface<IMediaRecorderClient> {...}

MediaPlayerService 类关系如下

class MediaPlayerService : public BnMediaPlayerService {...}
class BnMediaPlayerService: public BnInterface<IMediaPlayerService> {...}

MediaPlayerService 中 createMediaRecorder 如下

// frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(const AttributionSourceState& attributionSource)
{...sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, verifiedAttributionSource);...return recorder;
}

MediaRecorderClient 构造如下

// frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp
MediaRecorderClient::MediaRecorderClient(const sp<MediaPlayerService>& service,const AttributionSourceState& attributionSource)
{// 构造StagefrightRecorder,mRecorder = new StagefrightRecorder(attributionSource);mMediaPlayerService = service;
}StagefrightRecorder 类继承关系如下 
struct StagefrightRecorder : public MediaRecorderBase {...}

到此 JAVA 层 new MediaRecord() 相关源码已经分析完成

MediaRecorder 参数设置

Java层 setAudioSource 、 setOutputFormat 等均调用了 Native 接口,下面以 setAudioSource 为例

// frameworks/base/media/java/android/media/MediaRecorder.java
public native void setAudioSource(@Source int audioSource) throws IllegalStateException;
// frameworks/base/media/jni/android_media_MediaRecorder.cpp  
static void android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as)
{...// 读取初始化时保存的mrsp<MediaRecorder> mr = getMediaRecorder(env, thiz);if (mr == NULL) {jniThrowException(env, "java/lang/IllegalStateException", NULL);return;}process_media_recorder_call(env, mr->setAudioSource(as), "java/lang/RuntimeException", "setAudioSource failed.");
}// frameworks/av/media/libmedia/mediarecorder.cpp  
status_t MediaRecorder::setVideoSource(int vs)
{...// 上面知道,这里的 mMediaRecorder 是 MediaRecorderClient status_t ret = mMediaRecorder->setVideoSource(vs);return ret;
}// frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp
status_t MediaRecorderClient::setVideoSource(int vs)
{...// 上面知道,这里的 mRecorder 是 StagefrightRecorder return mRecorder->setVideoSource((video_source)vs);
}// frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::setVideoSource(video_source vs) {...// 最终数据保存在 mVideoSource 中 if (vs == VIDEO_SOURCE_DEFAULT) {mVideoSource = VIDEO_SOURCE_CAMERA;} else {mVideoSource = vs;}return OK;
}

同理,其它参数设置多数最终都是保存在 StagefrightRecorder 中,录制相关的流程很多也是在其中控制

本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134634628

MediaRecorder.prepare 分析

依据前面的分析,最终 prepare 真正实现如下

// frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::prepareInternal() {...status_t status = OK;// 依据不同的输出格式,执行不同的 setup switch (mOutputFormat) {case OUTPUT_FORMAT_DEFAULT:case OUTPUT_FORMAT_THREE_GPP:case OUTPUT_FORMAT_MPEG_4:case OUTPUT_FORMAT_WEBM:status = setupMPEG4orWEBMRecording();break;case OUTPUT_FORMAT_AMR_NB:case OUTPUT_FORMAT_AMR_WB:status = setupAMRRecording();break;case OUTPUT_FORMAT_AAC_ADIF:case OUTPUT_FORMAT_AAC_ADTS:status = setupAACRecording();break;case OUTPUT_FORMAT_RTP_AVP:status = setupRTPRecording();break;case OUTPUT_FORMAT_MPEG2TS:status = setupMPEG2TSRecording();break;case OUTPUT_FORMAT_OGG:status = setupOggRecording();break;default:ALOGE("Unsupported output file format: %d", mOutputFormat);status = UNKNOWN_ERROR;break;}return status;
}

这里我们分析一下录制 MP4 的 prepare 流程 setupMPEG4orWEBMRecording

status_t StagefrightRecorder::setupMPEG4orWEBMRecording() {// 先清理 MediaWriter mWriter.clear();mTotalBitRate = 0;status_t err = OK;sp<MediaWriter> writer;sp<MPEG4Writer> mp4writer;if (mOutputFormat == OUTPUT_FORMAT_WEBM) {writer = new WebmWriter(mOutputFd);} else {// 我们这里分析 MP4 录制,MPEG4Writer 主要是用来写入编码后的音视频内容   writer = mp4writer = new MPEG4Writer(mOutputFd);}if (mVideoSource < VIDEO_SOURCE_LIST_END) {// 如果编码器未配置,设置默认的编码器  setDefaultVideoEncoderIfNecessary();sp<MediaSource> mediaSource;// 设置 视频源err = setupMediaSource(&mediaSource);if (err != OK) {return err;}sp<MediaCodecSource> encoder;// 编码参数配置,然后通过 MediaCodecSource::Create 创建编码器err = setupVideoEncoder(mediaSource, &encoder);if (err != OK) {return err;}// MPEG4Writer 添加编码通道,一般会有audio、video 两个,这里是 videowriter->addSource(encoder);mVideoEncoderSource = encoder;// 输出文件的码率,是视频和音频总码率之和mTotalBitRate += mVideoBitRate;}// 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.// disable audio for time lapse recordingconst bool disableAudio = mCaptureFpsEnable && mCaptureFps < mFrameRate;if (!disableAudio && mAudioSource != AUDIO_SOURCE_CNT) {// 通过  createAudioSource() 配置音频编码参数,进而通过 MediaCodecSource::Create 创建 编码器err = setupAudioEncoder(writer);if (err != OK) return err;mTotalBitRate += mAudioBitRate;}...// 监听 MPEG4Writer 一些参数的回调 writer->setListener(mListener);mWriter = writer;return OK;
}

通过如上,可以看到 MediaRecorder.prepare 主要是进行参数的配置、编码器的初始化

MediaRecorder.start 分析

依据前面的分析,最终 start 真正实现如下

//  frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::start() {Mutex::Autolock autolock(mLock);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:{sp<MetaData> meta = new MetaData;// 设置 meta 信息setupMPEG4orWEBMMetaData(&meta);// MPEG4Writer 传入 meta 信息,// startWriterThread 开启写入线程 // setupAndStartLooper 启动 ALooper, mReflector 用于信息传递 // 通过 writeFtypBox(MetaData *param) 写入// startTracks(MetaData *params) 启动音、视频Track,参见 MPEG4Writer::Track::startstatus = mWriter->start(meta.get());break;}...}if (status != OK) {// start 异常 mWriter.clear();mWriter = NULL;}if ((status == OK) && (!mStarted)) {mAnalyticsDirty = true;mStarted = true;...// 用于编码耗电统计 addBatteryData(params);}return status;
}

MediaRecorder.stop 分析

依据前面的分析,最终 stop 真正实现如下

//  frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::stop() {Mutex::Autolock autolock(mLock);status_t err = OK;if (mCaptureFpsEnable && mCameraSourceTimeLapse != NULL) {// 延时录制,详细可参见 CameraSourceTimeLapse.cppmCameraSourceTimeLapse->startQuickReadReturns();mCameraSourceTimeLapse = NULL;}int64_t stopTimeUs = systemTime() / 1000;for (const auto &source : { mAudioEncoderSource, mVideoEncoderSource }) {// 设置停止时间戳if (source != nullptr && OK != source->setStopTimeUs(stopTimeUs)) {}}if (mWriter != NULL) {// MPEG4Writer 的停止 ,实际调用其  reset(true, true)// stopWriterThread() 停止写的线程// Track 停止// release 关闭文件,停止释放Looper,资源状态重置err = mWriter->stop();mLastSeqNo = mWriter->getSequenceNum();mWriter.clear();}// 写入参数相关信息flushAndResetMetrics(true);// 重置参数状态mDurationRecordedUs = 0;mDurationPausedUs = 0;mNPauses = 0;mTotalPausedDurationUs = 0;mPauseStartTimeUs = 0;mStartedRecordingUs = 0;mGraphicBufferProducer.clear();mPersistentSurface.clear();mAudioEncoderSource.clear();mVideoEncoderSource.clear();......return err;
}

结语

到这里,已经完成了 MediaRecorder 录制 Framework 源码的分析。
其它部分流程,可以对照参见 StagefrightRecorder.cpp 中源码。希望对你有所帮助。
如果你在使用MediaRecorder的过程中遇到了其他问题,欢迎留言讨论。
如果你觉得本文还不错,可以点赞+收藏。


相关文章
安卓MediaRecorder(1)录制音频的详细使用
安卓MediaRecorder(2)录制源码分析
安卓MediaRecorder(3)音频采集编码写入详细源码分析
安卓MediaRecorder(4)视频采集编码写入详细源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/211666.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

当前 .NET SDK 不支持面向 .NET X.0 (如8.0)问题的解决方案

如果您加载方案或运行时出现如下错误时&#xff1a; 当前 .NET SDK 不支持面向 .NET 8.0。请面向 .NET 7.0 或更低版本&#xff0c;或者使用支持 .NET 8.0 的 .NET SDK 版本。从 https://aka.ms/dotnet/download 下载 .NET SDK (项目名称).Domain C:\Program Files\dotnet\…

Windows在cmd中执行bat脚本

在Linux中执行脚本常用的是sh或者直接输入脚本名称即可。 sh shell脚本.sh # 或者 shell脚本.sh在Windows中类似&#xff0c;使用start或者直接输入脚本名称。 start bat脚本.bat :: 或者 bat脚本.bat

【Angular开发】Angular在2023年之前不是很好

做一个简单介绍&#xff0c;年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【架构师酒馆】…

SSL证书更新

首先&#xff0c;我们需要理解为什么需要更新SSL证书。SSL证书的有效期通常为一年。一旦证书过期&#xff0c;浏览器会显示警告&#xff0c;提示用户该网站的SSL证书已经过期&#xff0c;这可能会导致用户对网站的信任度下降&#xff0c;甚至直接离开网站。此外&#xff0c;一些…

【Python】手把手教你用tkinter设计图书管理登录UI界面(一)

下一篇&#xff1a; 本项目将分段设计“图书管理登录UI界面”的用户登录、用户注册、用户账号找回等。主要围绕GUI标准库tkinter、以及类的继承&#xff08;重点&#xff09;来设计本项目。 首先新建一个文件夹命名为“图书管理系统项目”&#xff0c;并在其目录下新建文件夹…

【分治】最接近点对Python实现

文章目录 [toc]问题描述一维最接近点对算法Python实现 二维最接近点对算法分治算法时间复杂性Python实现 问题描述 给定平面上 n n n个点&#xff0c;找其中的一对点&#xff0c;使得在 n n n个点组成的所有点对中&#xff0c;该点对的距离最小 一维最接近点对算法 Python实…

LED透镜粘接UV胶是一种特殊的UV固化胶,用于固定和粘合LED透镜。

LED透镜粘接UV胶是一种特殊的UV固化胶&#xff0c;用于固定和粘合LED透镜。 它具有以下特点&#xff1a; 1. 高透明度&#xff1a;LED透镜粘接UV胶具有高透明度&#xff0c;可以确保光线的透过性&#xff0c;不影响LED的亮度和效果。 2. 快速固化&#xff1a;经过UV紫外线照射…

CPU、MCU、MPU、DSP、FPGA各是什么?有什么区别?

1、CPU 中央处理器&#xff0c;简称 CPU&#xff08;Central Processing Unit&#xff09;&#xff0c;中央处理器主要包括两个部分&#xff0c;即控制器、运算器&#xff0c;其中还包括高速缓冲存储器及实现它们之间联系的数据、控制的总线。 电子计算机三大核心部件就是CPU…

力扣257. 二叉树的所有路径(递归回溯与迭代)

题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5] 输出&#xff1a;["1->2->5","…

[陇剑杯 2021]简单日志分析

[陇剑杯 2021]简单日志分析 题目做法及思路解析&#xff08;个人分享&#xff09; 问一&#xff1a;某应用程序被攻击&#xff0c;请分析日志后作答&#xff1a; 黑客攻击的参数是______。&#xff08;如有字母请全部使用小写&#xff09;。 题目思路&#xff1a; 分析…

C++牛客知识点2

提示&#xff1a;接上文 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0…

http与https的区别,以及生产环境配置https的几种方式

http HTTP(超文本传输协议)是一种用于传输和处理超文本文档的协议。HTTP使用客户端-服务器模型。客户端通过HTTP请求协议向服务器发送请求&#xff0c;服务器则使用HTTP响应协议返回响应。HTTP协议通常使用TCP/IP作为底层传输协议&#xff0c;但它也可以使用其他传输协议。 H…

sql注入学习

基础查询语句&#xff1a; 给指定字段添加数据 insert into 表名(字段名1,字段名2,.....) values(值1,值2,......); 给全部字段添加数据 insert into 表名 values (值1,值2,.....);--无限制条件的修改,会修改整张表 update 表名 set 字段 值; --有限制条件的修改,只修改特定记…

软件设计师——计算机网络(二)

&#x1f4d1;前言 本文主要是【计算机网络】——软件设计师——计算机网络的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1…

Promise介绍和使用

Promise Promise是一门新的技术&#xff08;ES6规范&#xff09;&#xff0c;JS中进行异步编程的新解决方案。&#xff08;旧的方案是使用回调函数&#xff0c;比如AJAX请求&#xff09;。 从语法上来说Promise是一个构造函数。 从功能上来说Promise对象用来封装一个异步操作并…

生成式AI赋能千行百业加速创新,2023亚马逊云科技re:Invent行业盘点

2023亚马逊云科技re:Invent全球大会已于上周圆满闭幕&#xff0c;在本次大会中&#xff0c;亚马逊云科技又为大家带来了很多功能/项目迭代更新&#xff0c;也重磅发布了很多全新的功能。今天从行业视角来盘点回顾哪些重磅发布适用于垂直行业客户&#xff0c;以及面向汽车、制造…

ChatGLM3-6B和langchain阿里云部署

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ChatGLM3-6B部署搭建环境部署GLM3 二、Chatglm2-6blangchain部署三、Tips四、总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; …

ffmpeg之ffprobe.c源码分析一---大流程及核心代码分析

文章目录 前言为什么学习ffprobe源码源码调试main()函数重要流程函数分析open_input_file函数分析avformat_match_stream_specifier函数分析read_packets函数分析本篇文章带你打通ffprobe源码的脉络。 关注公众号免费看: 前言 注:本文章全凭个人经验以及平时学习所记录,由…

gdal合成多个波段

def synthesis_bands(dst_list, outfile):"""将多光谱波段合成一个tif:param dst_list: 输入待合成文件的列表:param outfile: 影像的输出文件夹"""dataset_init gdal.Open(dst_list[0])# 创建待输出的图tiff_driver gdal.GetDriverByName(GTi…

【MySQL进阶】索引使用

一、索引使用 1.验证索引效率 tb_sku 这张表中准备了 1000w 的记录。 我用夸克网盘分享了「1000w的模拟数据」链接&#xff1a;https://pan.quark.cn/s/15cf665202b2 这张表中id为主键&#xff0c;有主键索引&#xff0c;而其他字段是没有建立索引的。 我们先来查询其中的…