安卓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,一经查实,立即删除!

相关文章

【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; 分析…

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

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

生成式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源码的脉络。 关注公众号免费看: 前言 注:本文章全凭个人经验以及平时学习所记录,由…

【MySQL进阶】索引使用

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

JS基础之原型原型链

JS基础之原型&原型链 原型&原型链构造函数创建对象prototypeprotoconstructor实例与原型原型的原型原型链其他constructorproto继承 原型&原型链 构造函数创建对象 我们先使用构造函数创建一个对象&#xff1a; function Person(){ } var person new Person();…

多窗口文件管理工具Q-Dir安装以及使用教程

软件介绍 Q-Dir 是一款功能强大的Windows资源管理器&#xff0c;可以非常方便的管理你的各种文件。Q-Dir有4 个窗口&#xff0c;特别适用于频繁在各个目录间跳跃复制粘贴的情况&#xff0c;每个窗口都可以方便的切换目录&#xff0c;以不同颜色区分不同类型的文件&#xff0c;…

(企业项目)微服务项目解决跨域问题:

前后端分离项目中前端出现了跨域的问题 在网关模块配置文件中添加 配置 application.properties # 允许请求来源&#xff08;老版本叫allowedOrigin&#xff09; spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns* # 允许携带的头信息 spri…

idea__SpringBoot微服务06——静态资源(新依赖),首页和图标定制

静态资源 一、静态资源二、首页和图标定制————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶&#xffe3;)&#xff0c;谢谢~~ 新依赖&#xff1a;jquery的 <dependency><groupId>org.webjars&…

matplotlib 默认属性和绘图风格

matplotlib 默认属性 一、绘图风格1. 绘制叠加折线图2. Solarize_Light23. _classic_test_patch4. _mpl-gallery5. _mpl-gallery-nogrid6. bmh7. classic8. fivethirtyeight9. ggplot10. grayscale11. seaborn12. seaborn-bright13. seaborn-colorblind14. seaborn-dark15. sea…

Chart 7 内存优化

文章目录 前言7.1 Adreno GPU OpenCL内存7.1.1 内存声明周期7.1.2 Loacl Memory7.1.3 Constant memory(常量内存)7.1.4 Private Memory7.1.5 Global Memory7.1.5.1 Buffer Object7.1.5.2 Image Object7.1.5.3 Image object vs. buffer object7.1.5.4 Use of both Image and buf…

C语言数据结构-双向链表

文章目录 1 双向链表的结构2 双向链表的实现2.1 定义双向链表的数据结构2.2 打印链表2.3 初始化链表2.4 销毁链表2.5 尾插,头插2.6 尾删,头删2.7 根据头次出现数据找下标2.8 定点前插入2.9 删除pos位置2.10 定点后插入 3 完整代码3.1 List.h3.2 Lish.c3.3 test.c 1 双向链表的结…