Android硬编码——音频编码、视频编码及音视频混合
来源:互联网 发布:淘宝卖什么吃的赚钱 编辑:程序博客网 时间:2024/04/29 21:31
转载自湖广午王的博客:http://blog.csdn.net/junzia/article/details/54018671
android的原生的视频播放方式有三种:利用Intent调用系统自带的播放器或者安装的第三方播放器;利用VideoView来播放视频;利用MediaPlayer类和SufferView来播放视频。
ijkplayer:https://github.com/Bilibili/ijkplayer
bilibili-android-client:https://github.com/Bilibili/ijkplayer
ijkplayer详解使用教程:http://blog.csdn.net/u010987039/article/details/52301077
视频编解码对许多Android程序员来说都是Android中比较难的一个知识点。在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前基本上都是采用FFMpeg来做视频软件编解码的,现在FFMpeg在Android的编解码上依旧广泛应用。本篇博客主要讲到的是利用Android4.1增加的API MediaCodec和Android 4.3增加的API MediaMuxer进行Mp4视频的录制。
概述
通常来说,对于同一平台同一硬件环境,硬编硬解的速度是快于软件编解码的。而且相比软件编解码的高CPU占用率来说,硬件编解码也有很大的优势,所以在硬件支持的情况下,一般硬件编解码是我们的首选。
在Android中,我们可以直接使用MediaRecord来进行录像,但是在很多适合MediaRecord并不能满足我们的需求,比如我们需要对录制的视频加水印或者其他处理后,所有的平台都按照同一的大小传输到服务器等。
在本篇博客中,将会讲到的是利用AudioRecord录音,利用OpenGL渲染相机数据并做处理。然后利用MediaCodec对音频和视频分别进行编码,使用MediaMuxer将编码后的音视频进行混合保存为Mp4的编码过程与代码示例。
值得注意的是,音视频编解码用到的MediaCodec是Android 4.1新增的API,音视频混合用到的MediaMuxer是Android 4.3新增的API,所以本篇博客的示例只实用于Android 4.3以上的设备。
AudioRecord(录音API)
AudioRecord是相对MediaRecord更为底层的API,使用AudioRecord也可以很方便的完成录音功能。AudioRecord录音录制的是原始的PCM音频数据,我们可以使用AudioTrack来播放PCM音频文件。
AudioRecord最简单的使用代码如下:
private int sampleRate=44100; //采样率,默认44.1kprivate int channelCount=2; //音频采样通道,默认2通道private int channelConfig=AudioFormat.CHANNEL_IN_STEREO; //通道设置,默认立体声private int audioFormat=AudioFormat.ENCODING_PCM_16BIT; //设置采样数据格式,默认16比特PCMprivate FileOutputStream fos; //用于保存录音文件//音频录制实例化和录制过程中需要用到的数据bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2;buffer=new byte[bufferSize];//实例化AudioRecordmRecorder=new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig, audioFormat,bufferSize);//开始录制mRecorder.startRecording();//循环读取数据到buffer中,并保存buffer中的数据到文件中int length=mRecorder.read(buffer,0,bufferSize);fos.write(buffer,0,length);//中止循环并结束录制isRecording=false;mRecorder.stop();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
按照上面的步骤,我们就能成功的录制PCM音频文件了,但是处于传输和存储方面的考虑,一般来说,我们是不会直接录制PCM音频文件的。而是在录制过程中就对音频数据进行编码为aac、mp3、wav等其他格式的音频文件。
MediaCodec(硬件编解码API)
理解MediaCodec
MediaCodec的使用在Android Developer官网上有详细的说明。官网上的图能够很好的说明MediaCodec的使用方式。我们只需理解这个图,然后熟悉下MediaCodec的API就可以很快的上手使用MediaCodec来进行音视频的编解码工作了。
针对于上图,我们可以把InputBuffers和OutputBuffers简单的理解为它们共同组成了一个环形的传送带,传送带上铺满了空盒子。编解码开始后,我们需要得到一个空盒子(dequeueInputBuffer),然后往空盒子中填充原料(需要被编/解码的音/视频数据),并且放回到传送带你取出时候的那个位置上面(queueInputBuffer)。传送带经过处理器(Codec)后,盒子里面的原料被加工成了你所期望的东西(编解码后的数据),你就可以按照你放入原料时候的顺序,连带着盒子一起取出加工好的东西(dequeueOutputBuffer),并将取出来的东西贴标签(加数据头之类的非必须)和装箱(组合编码后的帧数据)操作,同样之后也要把盒子放回到原来的位置(releaseOutputBuffer)。
音频编码实例
在官网上有更规范的使用示例,结合上面的音频录制,编码为AAC音频文件示例代码如下:
private String mime = "audio/mp4a-latm"; //录音编码的mimeprivate int rate=256000; //编码的key bit rate//相对于上面的音频录制,我们需要一个编码器的实例MediaFormat format=MediaFormat.createAudioFormat(mime,sampleRate,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); //设置为编码器//同样,在设置录音开始的时候,也要设置编码开始mEnc.start();//之前的音频录制是直接循环读取,然后写入文件,这里需要做编码处理再写入文件//这里的处理就是和之前传送带取盒子放原料的流程一样了,注意一般在子线程中循环处理int index=mEnc.dequeueInputBuffer(-1);if(index>=0){ final ByteBuffer buffer=mEnc.getInputBuffer(index); buffer.clear(); int length=mRecorder.read(buffer,bufferSize); if(length>0){ mEnc.queueInputBuffer(index,0,length,System.nanoTime()/1000,0); }}MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();int outIndex;//每次取出的时候,把所有加工好的都循环取出来do{ outIndex=mEnc.dequeueOutputBuffer(mInfo,0); 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();
- 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
- 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
AAC编码加文件头的实现参照AAC编码规则,将数据填入就好了,网上很容易找到,具体实现如下:
/*** 给编码出的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;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
这样,得到的文件就是AAC音频文件了,一般Android系统自带的播放器都可以直接播放。
视频编码实例
视频的编码和上面音频的编码也大同小异。摄像头的数据回调时间并不是确定的,就算你设置了摄像头FPS范围为30-30帧,它也不会每秒就一定给你30帧数据。Android摄像头的数据回调,受光线的影响非常严重,这是由HAL层的3A算法决定的,你可以将自动曝光补偿、自动白平光等等给关掉,这样你才有可能得到稳定的帧率。
而我们录制并编码视频的时候,肯定是希望得到一个固定帧率的视频。所以在视频录制并进行编码的过程中,需要自己想些法子,让帧率固定下来。最简单也是最有效的做法就是,按照固定时间编码,如果没有新的摄像头数据回调来就用上一帧的数据。
参考代码如下:
private String mime="video/avc"; //编码的MIMEprivate int rate=256000; //波特率,256kbprivate int frameRate=24; //帧率,24帧private int frameInterval=1; //关键帧一秒一关键帧//和音频编码一样,设置编码格式,获取编码器实例MediaFormat format=MediaFormat.createVideoFormat(mime,width,height);format.setInteger(MediaFormat.KEY_BIT_RATE,rate);format.setInteger(MediaFormat.KEY_FRAME_RATE,frameRate);format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,frameInterval);//这里需要注意,为了简单这里是写了个固定的ColorFormat//实际上,并不是所有的手机都支持COLOR_FormatYUV420Planar颜色空间//所以正确的做法应该是,获取当前设备支持的颜色空间,并从中选取format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);mEnc=MediaCodec.createEncoderByType(mime);mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);//同样,准备好了后,开始编码器mEnc.start();//编码器正确开始后,在子线程中循环编码,固定码率的话,就是一个循环加上线程休眠的时间固定//流程和音频编码一样,取出空盒子,往空盒子里面加原料,放回盒子到原处,//盒子中原料被加工,取出盒子,从盒子里面取出成品,放回盒子到原处int index=mEnc.dequeueInputBuffer(-1);if(index>=0){ if(hasNewData){ if(yuv==null){ yuv=new byte[width*height*3/2]; } //把传入的rgba数据转成yuv的数据,转换在网上也是一大堆,不够下面还是一起贴上吧 rgbaToYuv(data,width,height,yuv); } ByteBuffer buffer=getInputBuffer(index); buffer.clear(); buffer.put(yuv); //把盒子和原料一起放回到传送带上原来的位置 mEnc.queueInputBuffer(index,0,yuv.length,timeStep,0);}MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();//尝试取出加工好的数据,和音频编码一样,do while和while都行,觉得怎么好怎么写int outIndex=mEnc.dequeueOutputBuffer(mInfo,0);while (outIndex>=0){ ByteBuffer outBuf=getOutputBuffer(outIndex); byte[] temp=new byte[mInfo.size]; outBuf.get(temp); if(mInfo.flags==MediaCodec.BUFFER_FLAG_CODEC_CONFIG){ //把编码信息保存下来,关键帧上要用 mHeadInfo=new byte[temp.length]; mHeadInfo=temp; }else if(mInfo.flags%8==MediaCodec.BUFFER_FLAG_KEY_FRAME){ //关键帧比普通帧是多了个帧头的,保存了编码的信息 byte[] keyframe = new byte[temp.length + mHeadInfo.length]; System.arraycopy(mHeadInfo, 0, keyframe, 0, mHeadInfo.length); System.arraycopy(temp, 0, keyframe, mHeadInfo.length, temp.length); Log.e(TAG,"other->"+mInfo.flags); //写入文件 fos.write(keyframe,0,keyframe.length); }else if(mInfo.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM){ //结束的时候应该发送结束信号,在这里处理 }else{ //写入文件 fos.write(temp,0,temp.length); } mEnc.releaseOutputBuffer(outIndex,false); outIndex=mEnc.dequeueOutputBuffer(mInfo,0);}//数据的来源,GL处理好后,readpix出来的RGBA数据喂进来,public void feedData(final byte[] data, final long timeStep){ hasNewData=true; nowFeedData=data; nowTimeStep=timeStep;}//RGBA转YUV的方法,这是最简单粗暴的方式,在使用的时候,一般不会选择在Java层,用这种方式做转换private void rgbaToYuv(byte[] rgba,int width,int height,byte[] yuv){ final int frameSize = width * height; int yIndex = 0; int uIndex = frameSize; int vIndex = frameSize + frameSize/4; int R, G, B, Y, U, V; int index = 0; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { index = j * width + i; if(rgba[index*4]>127||rgba[index*4]<-128){ Log.e("color","-->"+rgba[index*4]); } R = rgba[index*4]&0xFF; G = rgba[index*4+1]&0xFF; B = rgba[index*4+2]&0xFF; Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; yuv[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); if (j % 2 == 0 && index % 2 == 0) { yuv[uIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U)); yuv[vIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V)); } } }}
- 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
- 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
对于其他格式的音频视频编解码也大同小异了,只要MediaCodec支持就好。
MediaMuxer(音视频混合API)
MediaMuxer的使用很简单,在Android Developer官网上MediaMuxer的API说明中,也有其简单的使用示例代码:
MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);// More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()// or MediaExtractor.getTrackFormat().MediaFormat audioFormat = new MediaFormat(...);MediaFormat videoFormat = new MediaFormat(...);int audioTrackIndex = muxer.addTrack(audioFormat);int videoTrackIndex = muxer.addTrack(videoFormat);ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);boolean finished = false;BufferInfo bufferInfo = new BufferInfo();muxer.start();while(!finished) { // getInputBuffer() will fill the inputBuffer with one frame of encoded // sample from either MediaCodec or MediaExtractor, set isAudioSample to // true when the sample is audio data, set up all the fields of bufferInfo, // and return true if there are no more samples. finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo); if (!finished) { int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); }};muxer.stop();muxer.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
- 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
参照官方的说明和代码示例,我们可以知道,音视频混合(也可以音频和音频混合),只需要将编码器的MediaFormat加入到MediaMuxer中,得到一个音轨视频轨的索引,然后每次从编码器中取出来的ByteBuffer,写入(writeSampleData)到编码器所在的轨道中就ok了。
这里需要注意的是,一定要等编码器设置编码格式完成后,再将它加入到混合器中,编码器编码格式设置完成的标志是dequeueOutputBuffer
得到返回值为MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
。
音视频录制MP4文件
上面已经给出了音频录制的代码和视频录制的代码,利用MediaMuxer将其结合起来,就可以和简单的完成录制有声音有图像的MP4文件的功能了。音频录制和视频录制的基本流程保持不变,在录制编码后,不再将编码的结果写入到文件流中,而是写入为混合器的sample data。以视频为例,更改循环编码的代码为:
//流程一直,无需更改int index=mVideoEnc.dequeueInputBuffer(-1);if(index>=0){ if(hasNewData){ if(yuv==null){ yuv=new byte[width*height*3/2]; } rgbaToYuv(data,width,height,yuv); } ByteBuffer buffer=getInputBuffer(mVideoEnc,index); buffer.clear(); buffer.put(yuv); //结束时,发送结束标志,在编码完成后结束 mVideoEnc.queueInputBuffer(index,0,yuv.length, mStartFlag?0:MediaCodec.BUFFER_FLAG_END_OF_STREAM);}MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();int outIndex=mVideoEnc.dequeueOutputBuffer(mInfo,0);do { if(outIndex>=0){ ByteBuffer outBuf=getOutputBuffer(mVideoEnc,outIndex); //里面不在是写入到文件,而是写入为混合器的sample data if(mTrackCount==3&&mInfo.size>0){ mMuxer.writeSampleData(mVideoTrack,outBuf,mInfo); } mVideoEnc.releaseOutputBuffer(outIndex,false); outIndex=mVideoEnc.dequeueOutputBuffer(mInfo,0); Log.e("wuwang","outIndex-->"+outIndex); //编码结束的标志 if((mInfo.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ return true; } }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ //按照MediaMuxer中所说,加入轨道的时机在这里 mVideoTrack=mMuxer.addTrack(mVideoEnc.getOutputFormat()); Log.e("wuwang","video track-->"+mVideoTrack); mTrackCount++; //一定要音轨视频轨都加入后,再开始混合 if(mTrackCount==2){ mMuxer.start(); mTrackCount=3; } }}while (outIndex>=0);
- 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
- 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
当然是用MediaMuxer前,肯定是需要创建一个MediaMuxer的实例的:
mMuxer=new MediaMuxer(path+"."+postfix, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
- 1
- 1
音频的操作和视频一样更改,将音频编码也加入MeidaMuxer的轨道中,得到一个轨道索引,将编码后的数据加入为MediaMuxer当前音轨的sample data。音轨和上面的视轨各自做各自的,结束录制时,都发送结束标志,然后在编码结束后,停止混合器就可以得到一个固定码率的MP4文件了。
总结
至此,本篇博客就结束了。但是在实际使用MediaCodec和MediaMuxer的过程中,总会遇到这样或者那样的问题,硬编硬解,和硬件相关比较紧密,Android虽然提供了一个很好的API,但是各个厂商在实现的过程中,总是会做些让自己变得独特的事情。当然他们的目的并不是为了独特,有的是为了让产品变得更优秀(虽然最后可能会做砸了),有的是为了省钱,用软件去弥补硬件的缺陷,最后的结果就是苦了做上层开发的码农们。
从博主在使用MediaCodec和MediaMuxer的过程中遇到的问题,总结下需要注意主要有以下几点:
- MediaCodec是Android4.1新增API,MediaMuxer是Android4.3新增API。
- 颜色空间。按照Android本身的意思,COLOR_FormatYUV420Planar应该是所有硬件平台都支持的。但是实际上并不是这样。所以在设置颜色空间时,应该获取硬件平台所支持的颜色空间,确保它是支持你打算使用的颜色空间,不支持的话应该启用备用方案(使用其他当前硬件支持的颜色空间)。
- 视频尺寸,在一些手机上,视频录制的尺寸可以是任意的。但是有些手机,不支持的尺寸设置会导致录制的视频现错乱。博主在使用Oppo R7测试,360*640的视频,单独录制视频没问题,音视频混合后,出现了颜色错乱的情况,而在360F4手机上,却都是没问题的。将视频宽高都设置为16的倍数,可以解决这个问题。
- 编码器格式设置,诸如音频编码的采样率、比特率等,取值也需要结合硬件平台来设置,否则也会导致崩溃或其他问题。这个其实和颜色空间的选择一样。
- 网上看到许多
queueInputBuffer
中设置presentationTimeUs
为System.nanoTime()/1000
,这样做会导致编码出来的音视频,在播放时,总时长显示的是错误的。应该记录开始时候的nanoTime,然后设置presentationTimeUs
为(System.nanoTime()-nanoTime)/1000
。 - 录制结束时,应该发送结束标志
MediaCodec.BUFFER_FLAG_END_OF_STREAM
,在编码后区获得这个标志时再终止循环,而不是直接终止循环。
应该还有其他需要注意的问题。我暂时还没遇到。
源码
源码在github中codec module下,有需要的小伙伴fork或者download。后续Android音视频开发相关的Demo也会上传到这个项目下。
欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/54018671]
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- Android硬编码——音频编码、视频编码及音视频混合
- 【流媒體】 Android 实时视频编码—H.264硬编码
- Android 实时视频编码—H.264硬编码
- [流媒体]Android 实时视频编码—H.264硬编码
- 【流媒體】 Android 实时视频编码—H.264硬编码
- Android 实时视频编码—H.264硬编码
- 【流媒體】 Android 实时视频编码—H.264硬编码
- Android 实时视频编码—H.264硬编码
- Android实现视频硬编码
- iOS 视频h264硬编码
- Android硬件编码-音视频编码以及音视频混合
- 详解斜率优化
- 关于如何做好自己工作的清单
- Kotlin学习资源
- 百度地图so库文件
- Promise对象的含义和基本用法
- Android硬编码——音频编码、视频编码及音视频混合
- 第七次项目3实验
- 新网域名添加AAAA记录
- probelm D 大数+组合dp
- Android MarkDownView中显示gif图片
- JVM源码分析之一个Java进程究竟能创建多少线程
- Keras以及Tensorflow强制使用CPU
- ffmpeg实战教程(十二)为视频添加/更换背景音乐
- MYSQL索引优化: IN 和 OR 替换为 union all