基本原理和流程
Mediacodec用于硬件编解码,其存在3种形态Stopped[Uninitialized,Confirured,Error]Executing[Flushed,Running,End of Stream],Released。构造一个Mediacodec对象(eg:MediaCodec.createDecoderByType)时处于Uninitialized态,执行configure会进入Configured态,调用start会立即进入Executing的Flushed态(通过flush方法在Executing下可回到Flushed态),当第一个输出缓冲出队(dequeueInputBuffer)则进入Running态(这个状态处理编解码工作,可以执行stop回到Uninitialized态),当放入EOS标记(queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM))时则会转入
End of Stream态(不接受输入,但可能会继续输出,直到EOS),不再使用时调用release进入Released态
可以在任何时候执行reset使回到Uninitialized态
采用2个环形队列作为输入输出缓冲区,input client在空闲区放入数据,codec取出数据进行编解码然后在输出的空闲区放入数据(清空归还使用的区),output client取出编解码后的数据 (归还)。
[Uninitialized]
MediaCodec.createDecoderByType
[Configured]
mediaCodec.configure
[Flushed]
mediaCodec.start
[Running]
mediaCodec.dequeueInputBuffer
mediaCodec.queueInputBuffer
mediaCodec.dequeueOutputBuffer
mediaCodec.releaseOutputBuffer
注意点
1.如果是解码视频不想直接显示,则configure不要指定surface,然后在输出中获取数据。
2.如果是自己定义设置format,一定要记得设置csd-0,csd-1等,不然会报错。对于H264涉及的sps和pps参考地址
对于aac涉及的adts如图http://stackoverflow.com/questions/18862715/how-to-generate-the-aac-adts-elementary-stream-with-android-mediacodec
也可以利用queueInputBuffer方法使用BUFFER_FLAG_CODEC_CONFIG进行标记
示例代码
旧api同步处理方式
public class DecodeVideoThread extends Thread {
//媒体信息提取器
private MediaExtractor mediaExtractor;
private MediaCodec mediaCodec;
private boolean running=true;
@RequiresApi(api = Build.VERSION_CODES.N)
public DecodeVideoThread(Surface surface, AssetFileDescriptor assetFileDescriptor){
mediaExtractor=new MediaExtractor();
try {
mediaExtractor.setDataSource(assetFileDescriptor);
} catch (IOException e) {
e.printStackTrace();
}
init(surface);
}
public DecodeVideoThread(Surface surface,String filePath){
mediaExtractor=new MediaExtractor();
try {
mediaExtractor.setDataSource(filePath);
} catch (IOException e) {
e.printStackTrace();
}
init(surface);
}
//利用的mediaextractor
private void init(Surface surface){
int trackCount = mediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
String mime = trackFormat.getString(MediaFormat.KEY_MIME);
if(mime.startsWith("video/")){
Log.i("tag",mime);
mediaExtractor.selectTrack(i);
try {
mediaCodec = MediaCodec.createDecoderByType(mime);
} catch (IOException e) {
e.printStackTrace();
}
mediaCodec.configure(trackFormat,surface,null,0);
mediaCodec.start();
break;
}
}
}
@Override
public void run() {
super.run();
MediaCodec.BufferInfo bufferInfo=new MediaCodec.BufferInfo();
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
boolean needInput=true;
boolean first=true;
long startWhen=0;
while (running){
if(needInput){
int inputIndex = mediaCodec.dequeueInputBuffer(10 * 1000);
if(inputIndex>=0){
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//新版api,测试
Log.i("tag","aaaaaaaaa");
inputBuffer = mediaCodec.getInputBuffer(inputIndex);
}else{
inputBuffer = inputBuffers[inputIndex];
}
int sampleDataSize = mediaExtractor.readSampleData(inputBuffer, 0);
if(mediaExtractor.advance() && sampleDataSize>0){
mediaCodec.queueInputBuffer(inputIndex,0,sampleDataSize,mediaExtractor.getSampleTime(),0);
}else {
mediaCodec.queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
needInput=false;
}
}
}
int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10 * 1000);
switch (outputIndex){
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.i("tag","output format"+mediaCodec.getOutputFormat());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.i("tag","如果前面指定了超时值,这个则表明超时");
break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers=mediaCodec.getOutputBuffers();
Log.i("tag","输出缓冲区更改");
break;
default:
if(first){
first=false;
startWhen=System.currentTimeMillis();
}
//presentationTimeUs表示的是展示的时间,单位是微妙
long sleepTime=bufferInfo.presentationTimeUs/1000-(System.currentTimeMillis()-startWhen);
//这样处理只能保证播放不变快,没有处理播放太慢的情况
if(sleepTime>0){
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mediaCodec.releaseOutputBuffer(outputIndex,true);
}
if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){
//结束循环
break;
}
}
mediaCodec.stop();
mediaCodec.release();
mediaCodec=null;
mediaExtractor.release();
}
public void close(){
running=false;
}
}
新api异步处理方式
public class DecodeVideoThread2 extends Thread {
//媒体信息提取器
private MediaExtractor mediaExtractor;
private MediaCodec mediaCodec;
private boolean running=true;
@RequiresApi(api = Build.VERSION_CODES.N)
public DecodeVideoThread2(Surface surface, AssetFileDescriptor assetFileDescriptor){
mediaExtractor=new MediaExtractor();
try {
mediaExtractor.setDataSource(assetFileDescriptor);
} catch (IOException e) {
e.printStackTrace();
}
init(surface);
}
public DecodeVideoThread2(Surface surface, String filePath){
mediaExtractor=new MediaExtractor();
try {
mediaExtractor.setDataSource(filePath);
} catch (IOException e) {
e.printStackTrace();
}
init(surface);
}
//利用的mediaextractor
private void init(Surface surface){
int trackCount = mediaExtractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
String mime = trackFormat.getString(MediaFormat.KEY_MIME);
if(mime.startsWith("video/")){
Log.i("tag",mime);
mediaExtractor.selectTrack(i);
try {
mediaCodec = MediaCodec.createDecoderByType(mime);
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mediaCodec.setCallback(new MediaCodec.Callback() {
boolean first=true;
long startWhen;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
int sampleDataSize = mediaExtractor.readSampleData(inputBuffer, 0);
if(sampleDataSize>0){
mediaCodec.queueInputBuffer(index,0,sampleDataSize,mediaExtractor.getSampleTime(),0);
mediaExtractor.advance();
}else{
mediaCodec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
if(first){
first=false;
startWhen=System.currentTimeMillis();
}
long sleepTime=info.presentationTimeUs/1000-(System.currentTimeMillis()-startWhen);
if(sleepTime>0){
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mediaCodec.releaseOutputBuffer(index,true);
// //下面这样设置并没有什么卵用,姿势不对?
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// mediaCodec.releaseOutputBuffer(index,info.presentationTimeUs*1000);
// }
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
}
});
}
mediaCodec.configure(trackFormat,surface,null,0);
mediaCodec.start();
break;
}
}
}
public void close(){
running=false;
mediaCodec.stop();
mediaCodec.release();
mediaCodec=null;
mediaExtractor.release();
}
}
编码音频(播放速度不对不知道为啥)
public class EncodeAudioThread extends Thread {
boolean running=true;
MediaCodec mediaCodec;
MediaMuxer mediaMuxer;
int trackIndex;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public EncodeAudioThread(){
try {
mediaCodec=MediaCodec.createEncoderByType("audio/mp4a-latm");
} catch (IOException e) {
e.printStackTrace();
}
MediaFormat mediaFormat=makeAACCodecSpecificData(AACObjectLC,44100,2);
mediaCodec.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
try {
mediaMuxer=new MediaMuxer(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
e.printStackTrace();
}
trackIndex=mediaMuxer.addTrack(mediaFormat);
mediaMuxer.start();
}
private MediaFormat makeAACCodecSpecificData(int audioProfile, int sampleRate, int channelConfig) {
// MediaFormat format = new MediaFormat();
// format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
// format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
// format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig);
MediaFormat format=MediaFormat.createAudioFormat("audio/mp4a-latm",sampleRate,channelConfig);
//编码不设置比特率会报错 android.media.MediaCodec$CodecException: Error 0x80001001
format.setInteger(MediaFormat.KEY_BIT_RATE,56*1000);
format.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC);
int samplingFreq[] = {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000
};
// Search the Sampling Frequencies9
int sampleIndex = -1;
for (int i = 0; i < samplingFreq.length; ++i) {
if (samplingFreq[i] == sampleRate) {
Log.d("TAG", "kSamplingFreq " + samplingFreq[i] + " i : " + i);
sampleIndex = i;
}
}
if (sampleIndex == -1) {
return null;
}
//没搞懂这段的计算原理
ByteBuffer csd = ByteBuffer.allocate(2);
csd.put((byte) ((audioProfile << 3) | (sampleIndex >> 1)));
csd.position(1);
csd.put((byte) ((byte) ((sampleIndex << 7) & 0x80) | (channelConfig << 3)));
csd.flip();
format.setByteBuffer("csd-0", csd); // add csd-0
System.out.println(Arrays.toString(csd.array())+"===++");
return format;
}
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
// fill in ADTS data
packet[0] = (byte)0xFF;
packet[1] = (byte)0xF9;
packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
packet[4] = (byte)((packetLen&0x7FF) >> 3);
packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
packet[6] = (byte)0xFC;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
public void run() {
super.run();
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
MediaCodec.BufferInfo bufferInfo=new MediaCodec.BufferInfo();
//================
FileOutputStream fileOutputStream = null;
FileOutputStream fileOutputStream2 = null;
try {
fileOutputStream=new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record.pcm");
fileOutputStream2=new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record2");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
AudioRecord audioRecord=new AudioRecord(MediaRecorder.AudioSource.MIC,44100, AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT,minBufferSize);
audioRecord.startRecording();
byte[] buffer=new byte[minBufferSize];
while(running){
int readSize = audioRecord.read(buffer, 0, buffer.length);
try {
fileOutputStream.write(buffer,0,readSize);
} catch (IOException e) {
e.printStackTrace();
}
//===================
int inputIndex = mediaCodec.dequeueInputBuffer(1000);
ByteBuffer inputBuffer = inputBuffers[inputIndex];
// inputBuffer.clear();
inputBuffer.put(buffer,0,readSize);
// inputBuffer.flip();
mediaCodec.queueInputBuffer(inputIndex,0,readSize,0,0);
int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 1000);
switch (outputIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = mediaCodec.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat outputFormat = mediaCodec.getOutputFormat();
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
//超时
break;
default:
mediaMuxer.writeSampleData(trackIndex,outputBuffers[outputIndex],bufferInfo);
//通过文件写
// byte[] array = outputBuffers[outputIndex].array();//会报错 ReadOnlyBufferException
ByteBuffer outputBuffer = outputBuffers[outputIndex];
byte[] newArray=new byte[outputBuffer.limit()+7];
addADTStoPacket(newArray,newArray.length);
outputBuffer.get(newArray,7,outputBuffer.limit());
try {
fileOutputStream2.write(newArray);
} catch (IOException e) {
e.printStackTrace();
}//
mediaCodec.releaseOutputBuffer(outputIndex,false);
break;
}
//================
}
try {
fileOutputStream.close();
fileOutputStream2.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
audioRecord.release();
audioRecord=null;
mediaCodec.stop();
mediaCodec.release();
mediaCodec=null;
mediaMuxer.release();
}
System.out.println("finish");
//读取播放
FileInputStream fileInputStream = null;
try {
fileInputStream=new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/record.pcm");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
AudioTrack audioTrack=new AudioTrack(AudioManager.STREAM_MUSIC,44100,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT,minBufferSize,AudioTrack.MODE_STREAM);
audioTrack.play();
int rs = 0;
try {
while((rs=fileInputStream.read(buffer))!=-1){
audioTrack.write(buffer,0,rs);
}
} catch (IOException e) {
e.printStackTrace();
}
//finally{}
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
audioTrack.release();
}
}
public void stopThread(){
running=false;
}
}