一、需要阅读的文章
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/53885837
https://blog.csdn.net/u014691453/article/details/53256605
https://blog.csdn.net/foryou96/article/details/64132636 (详细 )
此开源库的缺点就是由开源库裁剪或者合并出来的视频文件,不能再由此开源库进行二次操作,否则会抛出异常。
2)FFmepg
https://www.jianshu.com/p/2cf527f2129f
六、视频压缩
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
七、视频录制
调用系统相机,很多API兼容性都不好,如前后摄像头、输出路径,可能都不好使。
八、音频
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。