一、需要阅读的文章 https://blog.csdn.net/feifeiwendao/article/details/52527824 ####MediaCodec类相关的文章:######## https://www.cnblogs.com/renhui/p/7478527.html https://blog.csdn.net/junzia/article/details/54018671 (思路很清晰的一篇文章) https://www.cnblogs.com/roger-yu/p/5635494.html https://www.cnblogs.com/Sharley/p/5964490.html https://blog.csdn.net/leif_/article/details/50971616 MediaCodec实际上就是一个编解码的容器,将byte[]放进去,取出来就得到自己想要的编码格式的byte[],然后 写入文件即可。 #########AudioRecorder录音:################### https://blog.csdn.net/qq_36982160/article/details/79383046 https://www.cnblogs.com/whoislcj/p/5477216.html 录音的流程: 1)初始化AudioRecorder 2)调用AudioRecorder的startRecording方法 3)调用AudioRecorder的read方法,读出byte[]数据。你可以选择将数据存储到文件,最终得到的是.pcm文件。 这种文件一般播放器不支持,需要用andorid的AudioTrack去播放。 AudioTrack,播放PCM音频文件。 https://www.cnblogs.com/stnlcd/p/7151438.html 注意:AudioTrack的构造参数,要与录制时的参数保持一致 。如channelConfig如果不对应 则人说话的声音就超级奇怪了。 以下是PCM录音和播放的源码:

package com.xinyi.czsuperrecorder;import android.media.AudioFormat;import android.media.AudioManager;import android.media.AudioRecord;import android.media.AudioTrack;import android.media.MediaCodec;import android.media.MediaCodecInfo;import android.media.MediaFormat;import android.media.MediaRecorder;import android.util.Log;import com.xinyi.baselib.io.tf.TFileHelper;import java.io.BufferedInputStream;import java.io.DataInputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;/** * Created by XinYi on 2018/7/5. * (录音API) */public class AudioRecorder { private static final String TAG = "AudioRecorder"; private int sampleRate=44100; //采样率,默认44.1k private int channelConfig= AudioFormat.CHANNEL_IN_STEREO; //通道设置,默认立体声 private int audioFormat=AudioFormat.ENCODING_PCM_16BIT; //设置采样数据格式,默认16比特PCM private AudioRecord mRecorder; private int bufferSize; private File recordingFile = new File(TFileHelper.getInstance().getRoot() + "/test/a.pcm"); public void prepare(){ bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2; // buffer=new byte[bufferSize]; mRecorder=new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig, audioFormat,bufferSize); } public void start() throws IOException { byte[] tempBuffer = new byte[bufferSize]; if (mRecorder.getState() != AudioRecord.STATE_INITIALIZED) { stop(); return; } //开始录制 mRecorder.startRecording(); //循环读取数据到buffer中,并保存buffer中的数据到文件中 Log.e(TAG, "start: 录音中。。。"); TFileHelper.getInstance().deleteFile(recordingFile.getAbsolutePath()); TFileHelper.getInstance().createFile(recordingFile.getAbsolutePath()); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(recordingFile); int length; while ((length = mRecorder.read(tempBuffer, 0, bufferSize)) != -1) { fileOutputStream.write(tempBuffer, 0, length); fileOutputStream.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } public void stop(){ //中止循环并结束录制 mRecorder.stop(); Log.e(TAG, "start: 录音结束。。。"); } //播放音频(PCM) public void play() { DataInputStream dis=null; try { //从音频文件中读取声音 dis=new DataInputStream(new BufferedInputStream(new FileInputStream(recordingFile))); } catch (FileNotFoundException e) { e.printStackTrace(); } //最小缓存区 int bufferSizeInBytes= AudioTrack.getMinBufferSize(sampleRate,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT); //创建AudioTrack对象 依次传入 :流类型、采样率(与采集的要一致)、音频通道(采集是IN 播放时OUT)、量化位数、最小缓冲区、模式 /** * !!注意,音频通道与录制时的音频通道要保持一致。 */ AudioTrack player=new AudioTrack(AudioManager.STREAM_MUSIC,sampleRate,AudioFormat.CHANNEL_OUT_STEREO,AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes, AudioTrack.MODE_STREAM); byte[] data =new byte [bufferSizeInBytes]; player.play();//开始播放 while(true) { int i=0; try { while(dis.available()>0&&i<data.length) { data[i]=dis.readByte(); i++; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } player.write(data,0,data.length); if(i!=bufferSizeInBytes) //表示读取完了 { player.stop();//停止播放 player.release();//释放资源 break; } } } public AudioRecord getmRecorder() { return mRecorder; } public int getBufferSize() { return bufferSize; }}

二、缓冲区
https://blog.csdn.net/bzlj2912009596/article/details/75581675

三、音频AAC编码
关于AAC编码的文章:
https://blog.csdn.net/jay100500/article/details/52955232/ (AAC的头文件,介绍了一款工具可以查看aac文件的头文件)

音频AAC编码实现过程 1)开启音频录制 2)通过MediaCodec,将音频编码,得到AAC裸流,加上AAC头,然后再写入文件。 代码如下:

package com.xinyi.czsuperrecorder.code.audio;import android.media.AudioRecord;import android.media.MediaCodec;import android.media.MediaCodecInfo;import android.media.MediaFormat;import android.util.Log;import com.xinyi.baselib.io.tf.TFileHelper;import com.xinyi.czsuperrecorder.Config;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;/** * Created by XinYi on 2018/7/5. * AAC编码 */public class AACEncoder { private static final String TAG = "AACEncoder"; private String mime = "audio/mp4a-latm"; //录音编码的mime private int rate=256000; //编码的key bit rate private MediaCodec mEnc; private AudioRecorder audioRecorder; private File recordingFile = new File(TFileHelper.getInstance().getRoot() + "/test/a.m4a"); private boolean isStopped = true; private int bufferSize; public AACEncoder(AudioRecorder audioRecorder) { this.audioRecorder = audioRecorder; isStopped = true; } public void prepare(){ audioRecorder.prepare(); try { bufferSize = 0; //相对于上面的音频录制,我们需要一个编码器的实例 MediaFormat format=MediaFormat.createAudioFormat(mime, Config.sampleRate,Config.channelCount); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_BIT_RATE, rate); mEnc= MediaCodec.createEncoderByType(mime); mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); //设置为编码器 } catch (IOException e) { e.printStackTrace(); } } public void start() throws IOException {//同样,在设置录音开始的时候,也要设置编码开始 mEnc.start();//之前的音频录制是直接循环读取,然后写入文件,这里需要做编码处理再写入文件//这里的处理就是和之前传送带取盒子放原料的流程一样了,注意一般在子线程中循环处理 isStopped = false; MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo(); FileOutputStream fos = null; TFileHelper.getInstance().deleteFile(recordingFile.getAbsolutePath()); TFileHelper.getInstance().createFile(recordingFile.getAbsolutePath()); try { fos = new FileOutputStream(recordingFile); audioRecorder.start(); while (!isStopped){ //循环读取AudioRecorder的数据流 int index=mEnc.dequeueInputBuffer(-1); Log.e(TAG, "start: index1 = " + index); if(index>=0){ final ByteBuffer buffer=mEnc.getInputBuffer(index); buffer.clear(); int length=audioRecorder.getmRecorder().read(buffer,audioRecorder.getBufferSize()); Log.e(TAG, "start length = : " + length); if(length>0){ mEnc.queueInputBuffer(index,0,length,System.nanoTime()/1000,0); } } int outIndex; //每次取出的时候,把所有加工好的都循环取出来 do{ outIndex=mEnc.dequeueOutputBuffer(mInfo,0); Log.e(TAG, "start: index2 = " + outIndex); if(outIndex>=0){ ByteBuffer buffer=mEnc.getOutputBuffer(outIndex); buffer.position(mInfo.offset); //AAC编码,需要加数据头,AAC编码数据头固定为7个字节 byte[] temp=new byte[mInfo.size+7]; buffer.get(temp,7,mInfo.size); addADTStoPacket(temp,temp.length); fos.write(temp); mEnc.releaseOutputBuffer(outIndex,false); }else if(outIndex ==MediaCodec.INFO_TRY_AGAIN_LATER){ //TODO something }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ //TODO something } }while (outIndex>=0); } //编码停止,发送编码结束的标志,循环结束后,停止并释放编码器 mEnc.stop(); mEnc.release(); } catch (IOException e) { e.printStackTrace(); } catch (MediaCodec.CryptoException e) { e.printStackTrace(); } finally { if(fos != null){ fos.close(); } } } /** * 给编码出的aac裸流添加adts头字段 * @param packet 要空出前7个字节,否则会搞乱数据 * @param packetLen */ private void addADTStoPacket(byte[] packet, int packetLen) { int profile = 2; //AAC LC int freqIdx = 4; //44.1KHz int chanCfg = 2; //CPE 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; } public void stop(){ audioRecorder.stop(); isStopped = true; }}

四、视频编码
https://www.jianshu.com/p/f3a55d3d1f5d/ (摄像头采集的数据格式YUV)

五、MP4文件裁剪

1)mp4parser
https://github.com/sannies/mp4parser

https://blog.csdn.net/u012027644/article/details/53885837https://blog.csdn.net/u014691453/article/details/53256605https://blog.csdn.net/foryou96/article/details/64132636 (详细 )此开源库的缺点就是由开源库裁剪或者合并出来的视频文件,不能再由此开源库进行二次操作,否则会抛出异常。2)FFmepghttps://www.jianshu.com/p/2cf527f2129f

六、视频压缩

https://github.com/zerochl/FFMPEG-AAC-264-Android-32-64https://blog.csdn.net/w690333243/article/details/88591807

https://blog.csdn.net/qq_21937107/article/details/80083380 (根据SilliCompresser改的,部分视频可能压缩不成功,有bug。)
bug:https://stackoverflow.com/questions/36915383/what-does-error-code-1010-in-android-mediacodec-mean

https://www.cnblogs.com/wainiwann/p/4633208.html (MediaMetadataRetriever视频信息获取)https://blog.csdn.net/chen930724/article/details/50267669 (MediaMetadataRetriever视频信息获取)https://blog.csdn.net/u014653815/article/details/81084161 (MediaCodec解码)https://github.com/mabeijianxi/small-video-record
https://www.jianshu.com/p/cdae476087d4
8.https://www.cnblogs.com/wzqnxd/p/10038881.html (SiliCompressor,亲测可用,缺点是没有进度监听)
9.https://blog.csdn.net/dzzzheng95/article/details/60142379 (系统相机录制视频无效)

七、视频录制
调用系统相机,很多API兼容性都不好,如前后摄像头、输出路径,可能都不好使。

https://www.jianshu.com/p/3f4ad878f6c8 https://blog.csdn.net/nature_day/article/details/36889815 (音视频Uri转真实地址)

八、音频

音频格式:https://blog.csdn.net/love_xsq/article/details/51254777播放音频的几种方式:https://www.cnblogs.com/HDK2016/p/8043247.html暂停其他应用的音频:https://blog.csdn.net/franksunny/article/details/12224551
https://blog.csdn.net/oLevin/article/details/51476122
https://bbs.csdn.net/topics/391930743?page=1https://www.cnblogs.com/senior-engineer/p/7867626.html (mediaplayer详解)https://blog.csdn.net/weixin_34015860/article/details/87948255 (音视频播放器框架)