咸鱼

咸鱼是以盐腌渍后,晒干的鱼

0%

Android MediaCodec硬编解码器

Android 的 MediaCodec 可以编/解码音频视频,支持同步和异步两种使用方式。

异步

参考官网的文档

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
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data

codec.queueInputBuffer(inputBufferId, …);
}

@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.

codec.releaseOutputBuffer(outputBufferId, …);
}

@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}

@Override
void onError(…) {

}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();

音频和视频的用法差不多,差异在于配置上,编码和解码也是。

两个不同的函数 MediaCodec.createDecoderByType(..)MediaCodec.createEncoderByType(..) 创建 MediaCodec

  1. 实现一个异步音频的编码器
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
/**
* @param mime MediaFormat.MIMETYPE_AUDIO_*
*/
fun createMediaCodecEncoder(mime: String, sampleRate:Int){

val format = MediaFormat.createAudioFormat(mime,sampleRate,1)
format.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
format.setInteger(MediaFormat.KEY_BIT_RATE, 128000)

val mMediaCodec = MediaCodec.createEncoderByType(mime)
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

mMediaCodec.setCallback(object :MediaCodec.Callback(){

/**
* 输入缓冲区可用时回调此函数
*/
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
//TODO 这里输入数据
val dataLength = 2
val data = ByteArray(dataLength)//TODO 假设这是一帧PCM数据,作为输入编码器的数据
val inputBuffer = codec.getInputBuffer(index)
if(inputBuffer != null){
inputBuffer.clear()
inputBuffer.put(data)
codec.queueInputBuffer(index,0,dataLength,0,0)
}else{
codec.queueInputBuffer(index,0,0,0,0)
}
}

/**
* 输出编码数据
*/
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo ) {
try {
if(info.size > 0){

if ((info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
println("编码器输出配置信息")
}else{
println("编码器输出编码数据")
}

val outputBuffer = codec.getOutputBuffer(index)//取出数据
val outputData = ByteArray(info.size)
outputBuffer?.get(outputData)
//TODO outputData 是编码后数据
}
}catch (e:Exception){
e.printStackTrace()
}finally {
codec.releaseOutputBuffer(index, false)
}
}

override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
e.printStackTrace()
}

override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
println("onOutputFormatChanged")
}
})
mMediaCodec.start()
}

  1. 实现一个异步音频的解码器
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
/**
* @param mime MediaFormat.MIMETYPE_AUDIO_*
*/
fun createMediaCodecDecoder(mime: String, sampleRate:Int){

val format = MediaFormat.createAudioFormat(mime,sampleRate,1)
format.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
format.setInteger(MediaFormat.KEY_BIT_RATE, 128000)

val mMediaCodec = MediaCodec.createDecoderByType(mime)
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

mMediaCodec.setCallback(object :MediaCodec.Callback(){

/**
* 输入缓冲区可用时回调此函数
*/
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {

//TODO 这里输入数据
val dataLength = 2
val data = ByteArray(dataLength)//TODO 假设这是一帧ACC格式数据,作为输入解码器的数据
val inputBuffer = codec.getInputBuffer(index)
if(inputBuffer != null){
inputBuffer.clear()
inputBuffer.put(data)
codec.queueInputBuffer(index,0,dataLength,0,0)
}else{
codec.queueInputBuffer(index,0,0,0,0)
}
}

/**
* 输出编码数据
*/
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo ) {
try {
if(info.size > 0){

if ((info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
println("解码器输出配置信息")
}else{
println("解码器输出编码数据")
}

val outputBuffer = codec.getOutputBuffer(index)//取出数据
val outputData = ByteArray(info.size)
outputBuffer?.get(outputData)
//TODO outputData 是解码后PCM数据,可以用AudioTrack播放
}
}catch (e:Exception){
e.printStackTrace()
}finally {
codec.releaseOutputBuffer(index, false)
}
}

override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
e.printStackTrace()
}

override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
println("onOutputFormatChanged")
}
})
mMediaCodec.start()
}

同步

参考官网

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
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data

codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.

codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();

实现一个同步音频解码器(伪代码)

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
MediaCodec codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_G711_ALAW);
final MediaFormat format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_G711_ALAW,
8000,
1);
format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
codec.configure(format,null,null,0);
codec.start();
inputBuffers = codec.getInputBuffers();

while (true){
int inputIndex = codec.dequeueInputBuffer(0);
if(inputIndex > -1){
ByteBuffer inputBuffer = inputBuffers[inputIndex];
inputBuffer.clear();

//假如这是输入需要解码的G711A格式数据,
int aacLength = 2;
byte[] g711Buffer = new byte[aacLength];

inputBuffer.put(g711Buffer, 0, aacLength);
codec.queueInputBuffer(inputIndex, 0, aacLength, 0, 0);
MediaCodec.BufferInfo infoOutput = new MediaCodec.BufferInfo();
int outputIndex = codec.dequeueOutputBuffer(infoOutput,10);
if (outputIndex >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputIndex);
byte[] outputData = new byte[infoOutput.size];
outputBuffer.get(outputData);

//TODO 这是输出的PCM数据

codec.releaseOutputBuffer(outputIndex, false);
}
}
}

同步编码器类似,省略。