Android 10 源码MediaRecorder 录像流程:MediaRecorder 配置

Posted TYYJ-洪伟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 10 源码MediaRecorder 录像流程:MediaRecorder 配置相关的知识,希望对你有一定的参考价值。

MediaRecorder 录像配置主要涉及输出文件路径、音频来源、视频来源、输出格式、音频编码格式、视频编码格式、比特率、帧率和视频尺寸等。

我们假设视频输入源来自 Camera,Camera2 API 将相机图像渲染到 MediaRecorder 提供的 Surface 上,而 MediaRecorder 将这个渲染数据编码为 H264。

    /**
     * 配置录制视频相关数据
     */
    private void configMediaRecorder()
        File file = new File(getExternalCacheDir(),"demo.mp4");
        if (file.exists())
            file.delete();
        
        mMediaRecorder.setAudiosource(MediaRecorder.AudioSource.MIC);//设置音频来源
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置视频来源
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//设置输出格式
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//设置音频编码格式
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//设置视频编码格式
        mMediaRecorder.setVideoEncodingBitRate(1920 * 1080 * 30 * 0.2);//设置比特率
        mMediaRecorder.setVideoFrameRate(30);//设置帧数
        mMediaRecorder.setVideoSize(1920, 1080);
        mMediaRecorder.setOutputFile(file.getAbsolutePath());
        try 
            mMediaRecorder.prepare();
         catch (IOException e) 
            e.printStackTrace();
        
    
    ...
    mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    List<Surface> surfaces = new ArrayList<>();

    // Set up Surface for the camera preview
    Surface previewSurface = new Surface(texture);
    surfaces.add(previewSurface);
    mPreviewBuilder.addTarget(previewSurface);

    // Set up Surface for the MediaRecorder
    Surface recorderSurface = mMediaRecorder.getSurface();
    surfaces.add(recorderSurface);
    mPreviewBuilder.addTarget(recorderSurface);

    // Start a capture session
    // Once the session starts, we can update the UI and start recording
    mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() 

        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) 
            ...   
        

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) 
            ...
        
    , mBackgroundHandler);

获取使用 surface 视频源时要录制的 SURFACE。必须要在 prepare(…) 之后调用。getSurface() 是个 native 方法。

frameworks/base/media/java/android/media/MediaRecorder.java

public class MediaRecorder implements AudioRouting,
                                      AudioRecordingMonitor,
                                      AudioRecordingMonitorClient,
                                      MicrophoneDirection

    ......
   /**
     * Gets the surface to record from when using SURFACE video source.
     *
     * <p> May only be called after @link #prepare. Frames rendered to the Surface before
     * @link #start will be discarded.</p>
     *
     * @throws IllegalStateException if it is called before @link #prepare, after
     * @link #stop, or is called when VideoSource is not set to SURFACE.
     * @see android.media.MediaRecorder.VideoSource
     */
    public native Surface getSurface();    
    ......

  1. 调用 getMediaRecorder(…) 从 Java MediaRecorder 类 mNativeContext field 中取出 jlong 类型的值,并强转为 MediaRecorder* 指针,这是在初始化 MediaRecorder 的时候写入的。
  2. 调用 MediaRecorder 类 querySurfaceMediaSourceFromMediaServer(…) 获取指向 IGraphicBufferProducer 的代理强指针。
  3. 调用 android_view_Surface_createFromIGraphicBufferProducer(…) 将 IGraphicBufferProducer 包装成 Java Surface 返回。

frameworks/base/media/jni/android_media_MediaRecorder.cpp

static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz)

    Mutex::Autolock l(sLock);
    MediaRecorder* const p = (MediaRecorder*)env->GetLongField(thiz, fields.context);
    return sp<MediaRecorder>(p);

......
static jobject
android_media_MediaRecorder_getSurface(JNIEnv *env, jobject thiz)

    ALOGV("getSurface");
    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
    if (mr == NULL) 
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return NULL;
    

    sp<IGraphicBufferProducer> bufferProducer = mr->querySurfaceMediaSourceFromMediaServer();
    if (bufferProducer == NULL) 
        jniThrowException(
                env,
                "java/lang/IllegalStateException",
                "failed to get surface");
        return NULL;
    

    // Wrap the IGBP in a Java-language Surface.
    return android_view_Surface_createFromIGraphicBufferProducer(env,
            bufferProducer);

使用 binder 接口,通过 Mediaserver 查询 SurfaceMediaSurface。mSurfaceMediaSource 是个 sp 类型的字段,mMediaRecorder 是个 sp 类型的字段。由初始化一节《【Android 10 源码】MediaRecorder 录像流程:MediaRecorder 初始化》不难知道此处 querySurfaceMediaSource() 调用最终会调到 MediaRecorderClient 同名方法处理。

frameworks/av/media/libmedia/mediarecorder.cpp

// Query a SurfaceMediaSurface through the Mediaserver, over the
// binder interface. This is used by the Filter Framework (MediaEncoder)
// to get an <IGraphicBufferProducer> object to hook up to ANativeWindow.
sp<IGraphicBufferProducer> MediaRecorder::
        querySurfaceMediaSourceFromMediaServer()

    Mutex::Autolock _l(mLock);
    mSurfaceMediaSource =
            mMediaRecorder->querySurfaceMediaSource();
    if (mSurfaceMediaSource == NULL) 
        ALOGE("SurfaceMediaSource could not be initialized!");
    
    return mSurfaceMediaSource;

MediaRecorderClient::querySurfaceMediaSource() 仅仅做了简单的检查后,最终委托给 StagefrightRecorder::querySurfaceMediaSource() 处理。

frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp

sp<IGraphicBufferProducer> MediaRecorderClient::querySurfaceMediaSource()

    ALOGV("Query SurfaceMediaSource");
    Mutex::Autolock lock(mLock);
    if (mRecorder == NULL) 
        ALOGE("recorder is not initialized");
        return NULL;
    
    return mRecorder->querySurfaceMediaSource();

注释里写的很明白了,mediaserver 的客户端要求它创建一个 SurfaceMediaSource 并返回一个接口引用。客户端将在编码 GL 帧时使用它。

这里只是简单的返回了 StagefrightRecorder 类的 mGraphicBufferProducer field 指向的值。给 mGraphicBufferProducer 赋值是在什么位置呢?分析后面的配置流程相信一定会水落石出的!

frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp

// The client side of mediaserver asks it to create a SurfaceMediaSource
// and return a interface reference. The client side will use that
// while encoding GL Frames
sp<IGraphicBufferProducer> StagefrightRecorder::querySurfaceMediaSource() const 
    ALOGV("Get SurfaceMediaSource");
    return mGraphicBufferProducer;

再来分析 android_view_Surface_createFromIGraphicBufferProducer(…)。

  1. 调用 Surface cpp 构造函数创建 Surface 对象,入参 controlledByApp 设置为 true,代表这个缓冲区 producer 由应用程序控制。
  2. 接着调用 android_view_Surface_createFromSurface(…) 将 cpp Surface 转化为 java Surface。

frameworks/base/core/jni/android_view_Surface.cpp

jobject android_view_Surface_createFromSurface(JNIEnv* env, const sp<Surface>& surface) 
    jobject surfaceObj = env->NewObject(gSurfaceClassInfo.clazz,
            gSurfaceClassInfo.ctor, (jlong)surface.get());
    if (surfaceObj == NULL) 
        if (env->ExceptionCheck()) 
            ALOGE("Could not create instance of Surface from IGraphicBufferProducer.");
            LOGE_EX(env);
            env->ExceptionClear();
        
        return NULL;
    
    surface->incStrong(&sRefBaseOwner);
    return surfaceObj;


jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env,
        const sp<IGraphicBufferProducer>& bufferProducer) 
    if (bufferProducer == NULL) 
        return NULL;
    

    sp<Surface> surface(new Surface(bufferProducer, true));
    return android_view_Surface_createFromSurface(env, surface);

现在继续分析主线:setAudioSource(MediaRecorder.AudioSource.MIC)。setAudioSource(…) 是个 JNI 方法。

frameworks/base/media/java/android/media/MediaRecorder.java

public class MediaRecorder implements AudioRouting,
                                      AudioRecordingMonitor,
                                      AudioRecordingMonitorClient,
                                      MicrophoneDirection

    ......
    /**
     * Sets the audio source to be used for recording. If this method is not
     * called, the output file will not contain an audio track. The source needs
     * to be specified before setting recording-parameters or encoders. Call
     * this only before setOutputFormat().
     *
     * @param audioSource the audio source to use
     * @throws IllegalStateException if it is called after setOutputFormat()
     * @see android.media.MediaRecorder.AudioSource
     */
    public native void setAudioSource(@Source int audioSource)
            throws IllegalStateException;
    ......

  1. 检查入参 as,对应 Java 方法入参 audioSource,不满足条件则直接调动 jniThrowException(…) 抛出 IllegalArgumentException 异常;
  2. 调用 getMediaRecorder(…) 获取 MediaRecorder native 对象;
  3. 调用 process_media_recorder_call(…) 检查 MediaRecorder native 对象的 setAudioSource(…) 调用是否存在非法操作,只要不等于 OK,就调用 jniThrowException(…) 抛出异常。

frameworks/base/media/jni/android_media_MediaRecorder.cpp

// Returns true if it throws an exception.
static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const char* exception, const char* message)

    ALOGV("process_media_recorder_call");
    if (opStatus == (status_t)INVALID_OPERATION) 
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return true;
     else if (opStatus != (status_t)OK) 
        jniThrowException(env, exception, message);
        return true;
    
    return false;

......
static void
android_media_MediaRecorder_setAudioSource(JNIEnv *env, jobject thiz, jint as)

    ALOGV("setAudioSource(%d)", as);
    if (as < AUDIO_SOURCE_DEFAULT ||
        (as >= AUDIO_SOURCE_CNT && as != AUDIO_SOURCE_FM_TUNER)) 
        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid audio source");
        return;
    

    sp<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.");

  1. mMediaRecorder 是个 sp 类型的强指针,实际指向 BpMediaRecorder,如果为空直接返回 INVALID_OPERATION(无效操作);
  2. 在 MediaRecorder 构造函数中,mCurrentState 被赋值为 MEDIA_RECORDER_IDLE,因此此处会先调用 init() 进行初始化;
  3. 判断 mIsAudioSourceSet 标志是否置位,如果已经被置位那么直接返回 INVALID_OPERATION;
  4. 现在判断 mCurrentState 状态是否为 MEDIA_RECORDER_INITIALIZED(已经初始化),如果不是说明初始化发生了异常,这里也返回 INVALID_OPERATION;
  5. 现在开始调用 BpMediaRecorder setAudioSource(…) 进一步处理,最终实际上由 MediaRecorderClient::setAudioSource(…) 服务,此时 mIsAudioSourceSet 可以设置为 true 了,表征 Audio 源已设置。

frameworks/av/media/libmedia/mediarecorder.cpp

status_t MediaRecorder::setAudioSource(int as)

    ALOGV("setAudioSource(%d)", as);
    if (mMediaRecorder == NULL) 
        ALOGE("media recorder is not initialized yet");
        return INVALID_OPERATION;
    
    if (mCurrentState & MEDIA_RECORDER_IDLE) 
        ALOGV("Call init() since the media recorder is not initialized yet");
        status_t ret = init();
        if (OK != ret) 
            return ret;
        
    
    if (mIsAudioSourceSet) 
        ALOGE("audio source has already been set");
        return INVALID_OPERATION;
    
    if (!(mCurrentState & MEDIA_RECORDER_INITIALIZED)) 
        ALOGE("setAudioSource called in an invalid state(%d)", mCurrentState);
        return INVALID_OPERATION;
    

    status_t ret = mMediaRecorder->setAudioSource(as);
    if (OK != ret) 
        ALOGV("setAudioSource failed: %d", ret);
        mCurrentState = MEDIA_RECORDER_ERROR;
        return ret;
    
    mIsAudioSourceSet = true;
    return ret;

init() 方法中主要调用 MediaRecorderClient::init() 和 MediaRecorderClient::setListener(…) 进一步处理,并且把 mCurrentState 置为 MEDIA_RECORDER_INITIALIZED。

frameworks/av/media/libmedia/mediarecorder.cpp

status_t MediaRecorder::init()

    ALOGV("init");
    if (mMediaRecorder == NULL) 
        ALOGE("media recorder is not initialized yet");
        return INVALID_OPERATION;
    
    if (!(mCurrentState & MEDIA_RECORDER_IDLE)) 
        ALOGE("init called in an invalid state(%d)", mCurrentState);
        return INVALID_OPERATION;
    

    status_t ret = mMediaRecorder->init();
    if (OK != ret) 
        ALOGV("init failed: %d", ret);
        mCurrentState = MEDIA_RECORDER_ERROR;
        return ret;
    

    ret = mMediaRecorder->setListener(this);
    if (OK != ret) 
        ALOGV("setListener failed: %d", ret);
        mCurrentState = MEDIA_RECORDER_ERROR;
        return ret;
    

    mCurrentState = MEDIA_RECORDER_INITIALIZED;
    return ret;

MediaRecorderClient::init() 仅仅将 init 动作交给 StagefrightRecorder::init() 进一步处理。

MediaRecorderClient::setListener(…) 看似很复杂,其实做的事非常简单,首先调用 StagefrightRecorder::setListener(…) 设置 Listener;接着将 DeathNotifier 对象逐个插入到 mDeathNotifiers 引用的 std::vector 容器中。从代码不难看出分别在 camera service、OMX service、Codec2 service 死掉的情况下发送相应通知告知 MediaRecorder native 对象,进而会调用其 notify(…) 方法将消息丢给 JNIMediaRecorderListener 继续抛到 Java 层。

frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp

status_t MediaRecorderClient::init()

    ALOGV("init");
    Mutex::Autolock lock(mLock);
    if (mRecorder == NULL) 
        ALOGE("recorder is not initialized");
        return NO_INIT;
    
    return mRecorder->init();

......

status_t MediaRecorderClient::setListener(const sp<IMediaRecorderClient>& listener)

    ALOGV("setListener");
    Mutex::Autolock lock(mLock);
    mDeathNotifiers.clear();
    if (mRecorder == NULL) 
        ALOGE("recorder is not initialized");
        return NO_INIT;
    
    mRecorder->setListener(listener);

    sp<IServiceManager> sm = defaultServiceManager();

    // WORKAROUND: We don't know if camera exists here and getService might block for 5 seconds.
    // Use checkService for camera if we don't know it exists.
    static std::atomic<bool> sCameraChecked(false);  // once true never becomes false.
    static std::atomic<bool> sCameraVerified(false); // once true never becomes false.
    sp<IBinder> binder = (sCameraVerified || !sCameraChecked)
        ? sm->getService(String16("media.camera")) : sm->checkService(String16("media.camera"));
    // If the device does not have a camera, do not create a death listener for it.
    if (binder != NULL) 
        sCameraVerified = true;
        mDeathNotifiers.emplace_back(
                binder, [l = wp<IMediaRecorderClient>(listener)]()
            sp<IMediaRecorderClient> listener = l.promote();
            if (listener) 
                ALOGV("media.camera service died. "
                      "Sending death notification.");
                listener->notify(
                        MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED,
                        MediaPlayerService::CAMERA_PROCESS_DEATH);
             else 
                ALOGW("media.camera service died without a death handler.");
            
        );
    
    sCameraChecked = true;

    
        using ::android::hidl::base::V1_0::IBase;

        // Listen to OMX's IOmxStore/default
        
            sp<IBase> base = ::android::hardware::media::omx::V1_0::
                    IOmx::getService();
            if (base == nullptr) 
                ALOGD(

如何在 Android 设备上将 WAV 编码为 mp3

【中文标题】如何在 Android 设备上将 WAV 编码为 mp3【英文标题】:How to encode a WAV to a mp3 on a Android device 【发布时间】:2010-09-04 10:50:49 【问题描述】:

我已经简化了我的问题并提供了赏金: 有哪些选项可用于在 Android 设备上将原始 PCM 音频数据压缩为 mp3。

我的原帖: 我正在我的 Android 手机上创建一个合成器,并且我一直在生成 PCM 数据以发送到扬声器。现在我想知道是否可以将此 PCM 数据编码为 mp3 以保存到 SD 卡。 MediaRecorder 对象可以将来自麦克风的音频编码为各种格式,但不允许对以编程方式生成的音频数据进行编码。

所以我的问题是,是否有用于编码音频的标准 Android API?如果没有,有哪些纯 Java 或基于 NDK 的解决方案?你能推荐其中任何一个吗?

如果做不到这一点,我只需将生成的音频保存为 WAV 文件,这很容易做到。

【问题讨论】:

“所以我的问题是,有没有用于编码音频的标准 Android API?” - 不。我会用java 标签重新标记您的问题,以帮助您获得意见... 仅供参考:MP3 技术已获得专利,因此将其包含在商业应用程序中会使您面临潜在的诉讼。你最好的防御就是不要让你的产品成功。 :) 是否还有另一种比 mp3 更开放的紧凑且受良好支持的(Android、Mac、Windows)格式?另外,mp3 真的会追随任何人使用这种格式吗? @VictorGrazi Ogg Vorbis 音频 (.ogg) 实际上是一种比 mp3 更好的格式(文件大小更小,音质更好),但使用较少。 【参考方案1】:

纯 Java 查看 Tritonus 的 javasound 洁净室实现,它在此处提供 MP3 编码器 插件:http://www.tritonus.org/plugins.html 其次,我建议查看 jzoom 的库 JLayer 或 JLayerME:http://www.javazoom.net/javalayer/javalayer.html(这可能只是解码,不确定) 如果这些不适合您的需要,您可以查看 2000 年关于将 MP3 功能添加到 J2SE 的这篇文章(带有源代码):http://www.javaworld.com/javaworld/jw-11-2000/jw-1103-mp3.html 原生路线 如果您想要“本机”性能,我会查看适用于 Android 的 FFmpeg 或 Lame 端口。 跛脚:http://lame.sourceforge.net/

【讨论】:

嗨,我正在使用 Android 上的 AudioRecorder 录制音频,结果输出格式为 WAV。输出的大小非常大,例如 1 分钟的记录最多需要 2mb 大小。我可以在使用 tritonus jar 转换为 mp3 时减小大小吗?如何在 android 上实现 tritonus。请帮帮我。【参考方案2】:

据我所知,仅使用 SDK 中的工具是无法做到这一点的。根据官方开发者指南,平台中没有 MP3 编码器(Android Supported Media Formats),因此您必须使用 NDK 自行移植编码器,然后编写一些包装器代码以通过 JNI 接收音频样本。

我目前正在为我自己的音乐播放器从 Rockbox 项目中移植一些音频解码器,它可以将音频录制到 MP3 中,所以也许你应该尝试查看它的源并找到编码器库。大多数解码器都有 ARM 优化,可以加快速度,所以我猜一些编码器也有这个添加。

【讨论】:

【参考方案3】:

Mp3 编码器在 android 中不可用。你必须使用 mp3 lame lib 编译 libav,你可以从中找到代码 http://libavandroid.wordpress.com

【讨论】:

以上是关于Android 10 源码MediaRecorder 录像流程:MediaRecorder 配置的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 设备上将 WAV 编码为 mp3

Android的PackageManagerService10.0源码解读(AndroidManifest.xml解析)

Android的PackageManagerService10.0源码解读(AndroidManifest.xml解析)

Android的PackageManagerService10.0源码解读(AndroidManifest.xml解析)

Android的PackageManagerService10.0源码解读(AndroidManifest.xml解析)

Android 10 startActivity 源码分析