安卓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.rhkb.cn/news/213505.html

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

相关文章

目标检测——OverFeat算法解读

论文&#xff1a;OverFeat: Integrated Recognition, Localization and Detection using Convolutional Networks 作者&#xff1a;Pierre Sermanet, David Eigen, Xiang Zhang, Michael Mathieu, Rob Fergus, Yann LeCun 链接&#xff1a;https://arxiv.org/abs/1312.6229 文章…

【Flink】Flink核心概念简述

目录 一、Flink 简介二、Flink 组件栈1. API & Libraries 层2. runtime层3. 物理部署层 三、Flink 集群架构四、Flink基本编程模型五、Flink 的部署模式六、Flink 任务的执行模式五、Flink 的优点 一、Flink 简介 Apache Flink 的前身是柏林理工大学一个研究性项目&#x…

IP地址定位技术为网络安全建设提供全新方案

随着互联网的普及和数字化进程的加速&#xff0c;网络安全问题日益引人关注。网络攻击、数据泄露、欺诈行为等安全威胁层出不穷&#xff0c;对个人隐私、企业机密和社会稳定构成严重威胁。在这样的背景下&#xff0c;IP地址定位技术应运而生&#xff0c;为网络安全建设提供了一…

合并一个文件夹下的多个txt文件,并对文本内容分列处理。

python 合并一个文件夹下的多个txt文件&#xff0c;并对文本内容分列。 原始文件&#xff1a; 最终结果&#xff1a; import pandas as pd import xlwt import pandas as pd from sqlalchemy import create_engine import pandas as pd import os import glob dirPath g…

LLM之Agent(四)| AgentGPT:一个在浏览器运行的Agent

AgentGPT是一个自主人工智能Agent平台&#xff0c;用户只需要为Agent指定一个名称和目标&#xff0c;就可以在浏览器中链接大型语言模型&#xff08;如GPT-4&#xff09;来创建和部署Agent平台。 PS&#xff1a;目前agentGPT仅支持chatgpt模型&#xff0c;暂时不支持本地llm模…

(华为)网络工程师教程笔记(网工教程)网工入门——3、静态路由路由表的配置

参考文章&#xff1a;【全236集】网络工程师从基础入门到进阶必学教程&#xff01;通俗易懂&#xff0c;2023最新版&#xff0c;学完即可就业&#xff01;网工入门_华为认证_HCIA_HCIP_数据通信_网工学习路线 文章目录 13. 网工入门10-静态路由&#xff08;路由表的配置&#x…

【Fastadmin】一个完整的轮播图功能示例

目录 1.效果展示&#xff1a; 列表 添加及编辑页面同 2.建表&#xff1a; 3.使用crud一键生成并创建控制器 4.html页面 add.html edit.html index.php 5.js页面 6.小知识点 1.效果展示&#xff1a; 列表 添加及编辑页面同 2.建表&#xff1a; 表名&#xff1a;fa_x…

kubesphere安装后启用DevOps

官方文档&#xff1a;KubeSphere DevOps 系统 1、集群管理---定制资源定义 进入目录&#xff1a;集群管理---定制资源定义搜索&#xff1a;clusterconfiguration 点击 ks-installer 右侧的 &#xff0c;选择编辑 YAML 在该 YAML 文件中&#xff0c;搜索 devops&#xff0c;…

SSM项目实战-前端-在Index.vue中展示第一页数据

1、util/request.js import axios from "axios";let request axios.create({baseURL: "http://localhost:8080",timeout: 50000 });export default request 2、api/schedule.js import request from "../util/request.js";export let getSchedu…

Python搭建代理IP池实现接口设置与整体调度

目录 前言 1. 搭建免费代理IP爬虫 2. 将获取到的代理IP存储到数据库中 3. 构建一个代理IP池 4. 实现调度器来调度代理IP池 5. 实现带有代理IP池的爬虫 总结 前言 在网络爬虫中&#xff0c;代理IP池是一个非常重要的组件。由于许多网站对单个IP的请求有限制&#xff0c;…

【数据结构和算法】--- 栈

目录 栈的概念及结构栈的实现初始化栈入栈出栈其他一些栈函数 小结栈相关的题目 栈的概念及结构 栈是一种特殊的线性表。相比于链表和顺序表&#xff0c;栈只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的…

iphone/安卓手机如何使用burp抓包

iphone 1. 电脑 ipconfig /all 获取电脑网卡ip&#xff1a; 192.168.31.10 2. 电脑burp上面打开设置&#xff0c;proxy&#xff0c;增加一条 192.168.31.10:8080 3. 4. 手机进入设置 -> Wi-Fi -> 找到HTTP代理选项&#xff0c;选择手动&#xff0c;192.168.31.10:8080 …

AI:95-基于卷积神经网络的艺术品风格分类

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的核心代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新…

一套rk3588 rtsp服务器推流的 github 方案及记录 -01

我不生产代码&#xff0c;我只是代码的搬运工&#xff0c;相信我&#xff0c;看完这个文章你的图片一定能变成流媒体推出去。 诉求&#xff1a;使用opencv拉流&#xff0c;转成bgr数据&#xff0c;需要把处理后的数据&#xff08;BGR&#xff09;编码成264&#xff0c;然后推流…

测试Centos上用Gunicorn启动的Django-Web服务在Django源文件有改变的情况下能否自动重载最新源码下的web服务

01-先上传最新的源码文件 参考博文 https://blog.csdn.net/wenhao_ir/article/details/134762966 进行 02-先在Django直接开web服务下修改源码测试 这是没有问题的&#xff0c;会自己重置。 03-开启gunicorn服务 cd /djangoproject/mmdj01/ gunicorn -c /djangoproject/mm…

如何跑AI模型—ultralytics

这里以跑 ultralytics 为示例&#xff0c;记录了如何从 0-1 跑个简单的模型&#xff0c;包括环境搭建。我的是 Window 系统&#xff0c;其他系统也类似。 主要流程是环境搭建&#xff0c;找个官网的 demo&#xff0c;收集好所需素材&#xff08;模型&#xff0c;图片等&#x…

炫酷不止一面:探索JavaScript动画的奇妙世界(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

口袋参谋:运营必备的生意参谋指数免费转换工具!

​至少99.99%的商家都知道&#xff0c;淘宝天猫上交易的指数越高&#xff0c;宝贝的热度就高&#xff0c;那就代表着入店的流量就会有很多。 01 在哪里可以看到指数呢&#xff1f; 其实我们在生意参谋后台&#xff0c;在市场板块或者竞争板块中&#xff0c;是可以看到访问人气…

智慧能源:数字孪生压缩空气储能管控平台

压缩空气储能在解决可再生能源不稳定性和提供可靠能源供应方面具有重要的优势。压缩空气储能&#xff0c;是指在电网负荷低谷期将电能用于压缩空气&#xff0c;在电网负荷高峰期释放压缩空气推动汽轮机发电的储能方式。通过提高能量转换效率、增加储能密度、快速启动和调节能力…

第21章网络通信

网络程序设计基础 网络程序设计编写的是与其他计算机进行通信的程序。Java 已经将网络程序所需要的元素封 装成不同的类&#xff0c;用户只要创建这些类的对象&#xff0c;使用相应的方法&#xff0c;即使不具备有关的网络支持&#xff0c;也可 以编写出高质量的网络通信程序…