MediaRecorder的使用方式和工作流程

MediaRecorder的使用方式和工作流程

MediaRecorder是安卓用于音视频录制的模块。

使用方式

Android多媒体框架支持捕获和编码各种常见的音频和视频格式。如果设备硬件支持,您可以使用MediaRecorder API。

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 设置audio的采样源
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

// 设置从摄像头采集图像
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

// 设置视频文件的输出格式
// 必须在声音和图像编码格式之前设置
mRecorder.setOutputFormat(OUTPUT_FORMAT_MPEG3TS);

// 设置声音编码格式
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

// 设置图像编码格式
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

// 设置文件保存路径
mRecorder.setOutputFile(videoFile.getAbsolutePath());

// 多相机系统设置
mRecorder.setCamera();
mRecorder.setParameters();

MediaRecorder只是一个壳,真正的功能借由StagefrightRecorder实现。

一个常见的音频录制工作如下:

1
2
3
4
5
6
7
8
9
10
11
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(PATH_NAME);
recorder.prepare();
recorder.start(); // 录制开始
...
recorder.stop();
recorder.reset(); // 通过回到setAudioSource()步骤,可以重复使用这个对象
recorder.release(); // 现在这个对象不能重复使用了

MediaRecorder状态图:

应用程序可能希望注册信息和错误事件,以便在录制期间通知某些内部更新和可能的运行时错误。
通过设置适当的监听器(通过调用setOnInfoListener或者setOnErrorListener)来注册此类事件。
为了接收与这些监听器关联的回调,应用程序需要在运行Looper的线程上创建MediaRecorder对象(默认情况下UI主线程已经运行了Looper)。

注意:目前,MediaRecorder不能在模拟器上工作。

  1. 当为MediaRecorder设定媒体录入源之后即初始化完成,此后才可以为其设定其他的参数。
  2. 在开始录制前必须先调用prepare()准备录制。
  3. 只有在Initial初始化状态,才可以对MediaRecorder调用release()释放资源,其他状态必须先stop()或者reset()。
  4. 错误状态是游离在所有状态之外的,当发生错误的时候,只有进行reset()才会进入Initial初始状态。

请求录制音频的权限

为了能够录制,您的应用程序必须告诉用户它将访问设备的音频输入。您必须在应用的清单文件中包含此权限标记:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

RECORD_AUDIO被视为“危险”权限,因为它可能对用户的隐私构成风险。从Android 6.0(API级别23)开始,使用危险权限的应用必须在运行时请求用户批准。用户授予权限后,应用程序应记住且不再询问。

下面的示例代码显示了如何使用ActivityCompat.requestPermissions().

创建和运行MediaRecorder

MediaRecorder使用以下调用初始化来新实例:

  • 设置音频源setAudioSource()。您可能需要使用麦克风。

  • 设置输出文件格式setOutputFormat()。请注意,从Android 8.0(API级别26)开始MediaRecorder支持MPEG2_TS格式,这对于流式传输很有用:

      mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
  • 设置输出文件名setOutputFile()。您必须指定代表实际文件的文件描述符。

  • 设置音频编码器setAudioEncoder()。

  • 通过调用完成初始化prepare()。

分别调用start()和stop()来启动和停止录制。

当您完成MediaRecorder实例的使用时,请通过调用release()尽快释放其资源。

使用MediaMuxer录制多个频道

从Android 8.0(API级别26)开始,您可以使用MediaMuxer录制多个同时的音频和视频流。在早期版本的Android中,您一次只能录制一个音频轨道或一个视频轨道。

使用addTrack()方法将多个音轨混合在一起。

您还可以为每一帧添加一个或多个带有自定义信息的元数据轨道,但仅限于MP4容器。您的应用定义了元数据的格式和内容。

添加元数据

元数据可用于离线处理。例如,从陀螺仪传感器捕获的数据可用于执行视频稳定。

添加元数据轨道时,轨道的mime格式必须以前缀application/开头。写入元数据与写入视频或音频数据相同,只是数据不是来自MediaCodec。相反,应用程序将带有关联时间戳的ByteBuffer传递给writeSampleData()方法。其必须与视频和音频轨道处于同一时间戳。

生成的MP4文件使用ISO BMFF规范第12.3.3.2节中定义的TextMetaDataSampleEntry来表示元数据的mime格式。当您使用MediaExtractor提取包含元数据轨道的文件时,元数据的mime格式表示为MediaFormat的实例。

示例代码

示例演示了如何使用MediaRecorder和Camera API进行视频录制。

下面的示例活动显示了如何使用MediaRecorder来录制音频文件。它还通过MediaPlayer用于播放音频。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package com.android.audiorecordtest;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import java.io.IOException;

public class AudioRecordTest extends AppCompatActivity {

private static final String LOG_TAG = "AudioRecordTest";
private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
private static String fileName = null;

private RecordButton recordButton = null;
private MediaRecorder recorder = null;

private PlayButton playButton = null;
private MediaPlayer player = null;

// 请求RECORD_AUDIO的权限
private boolean permissionToRecordAccepted = false;
private String [] permissions = {Manifest.permission.RECORD_AUDIO};

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case REQUEST_RECORD_AUDIO_PERMISSION:
permissionToRecordAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
break;
}
if (!permissionToRecordAccepted ) finish();

}

private void onRecord(boolean start) {
if (start) {
startRecording();
} else {
stopRecording();
}
}

private void onPlay(boolean start) {
if (start) {
startPlaying();
} else {
stopPlaying();
}
}

private void startPlaying() {
player = new MediaPlayer();
try {
player.setDataSource(fileName);
player.prepare();
player.start();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}
}

private void stopPlaying() {
player.release();
player = null;
}

private void startRecording() {
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setOutputFile(fileName);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

try {
recorder.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed");
}

recorder.start();
}

private void stopRecording() {
recorder.stop();
recorder.release();
recorder = null;
}

class RecordButton extends Button {
boolean mStartRecording = true;

OnClickListener clicker = new OnClickListener() {
public void onClick(View v) {
onRecord(mStartRecording);
if (mStartRecording) {
setText("Stop recording");
} else {
setText("Start recording");
}
mStartRecording = !mStartRecording;
}
};

public RecordButton(Context ctx) {
super(ctx);
setText("Start recording");
setOnClickListener(clicker);
}
}

class PlayButton extends Button {
boolean mStartPlaying = true;

OnClickListener clicker = new OnClickListener() {
public void onClick(View v) {
onPlay(mStartPlaying);
if (mStartPlaying) {
setText("Stop playing");
} else {
setText("Start playing");
}
mStartPlaying = !mStartPlaying;
}
};

public PlayButton(Context ctx) {
super(ctx);
setText("Start playing");
setOnClickListener(clicker);
}
}

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

// 记录到外部缓存目录以供查看
fileName = getExternalCacheDir().getAbsolutePath();
fileName += "/audiorecordtest.3gp";

ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION);

LinearLayout ll = new LinearLayout(this);
recordButton = new RecordButton(this);
ll.addView(recordButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
playButton = new PlayButton(this);
ll.addView(playButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
setContentView(ll);
}

@Override
public void onStop() {
super.onStop();
if (recorder != null) {
recorder.release();
recorder = null;
}

if (player != null) {
player.release();
player = null;
}
}
}

工作流程

参考自:Android视频录制-MediaRecorder

当使用CameraSource的时候MediaCodecSource会从CameraSource中取数据。

当使用Surface的时候不用CameraSource。

实际实现都是生产者消费者模型,由camera生产数据,由codec消费数据。只是具体实现过程不一样,camerasource录制使用的是puller,surface录制使用的是BufferQueue.

CameraSource:

在MediaCodecSource::Create中创建MediaCodecSource对象,此处会判断当前对否是FLAG_USE_SURFACE_INPUT,若不是,则mPuller = new Puller(source);

MediaCodecSource::Puller中有存放数据的Queue,并且响应各种对Queue的操作。

大概流程如下:

Puller中readbuffer的调用过程:(此处是从queue里面取走数据)

  • MediaCodecSource::onMessageReceived收到消息kWhatPullerNotify或者kWhatEncoderActivity,kWhatPullerNotify是由MediaCodecSource::onStart发出,kWhatPullerNotify是由MediaCodec::onInputBufferAvailable()发出的。
  • MediaCodecSource::feedEncoderInputBuffers()在此函数中把数据取出来,并调用mediacodec的queueInputBuffer,最终调用acodec的ETB.
  • mPuller->readBuffer(&mbuf) Puller中pushBuffer的调用过程:(从camerasource取到数据,把数据push到queue里面)
  • MediaCodecSource::Puller::onMessageReceived收到kWhatPull,kWhatPull是由schedulePull发出的消息,在收到kWhatStart之后就会调用schedulePull。
  • queue->pushBuffer(mbuf); 编码完的数据会送给mpeg4writer去写入文件。解码后的数据会送给disaplay去render。

Surface:

Surface录制没有puller。

大概流程如下:

此处listener负责监听,防止consumer需要一直查看bufferqueue是否有数据,listener发现有数据会通知consumer。


MediaRecorder的使用方式和工作流程
https://blog.siantao.top/技术/计算机/软件/Android/多媒体/媒体录制/MediaRecorder的使用方式和工作流程/
作者
玉水仙楊
发布于
2022年6月1日
许可协议