Android硬编码——音频编码、视频编码及音视频混合

android硬编解码MediaCodec 一 mediacodec简介 MediaCodec 类可以用来访问底层媒体编解码器,即编码器/解码器的组件。 它是 Android 底层多媒体支持架构的一部分(通常与 MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface 和 AudioTrack 一起使用)。 编解码器可以处理三类数据:压缩数据、原始音频数据、原始视频数据。 a Compressed Buffers 压缩缓冲区 输... 阅读详情

视频编解码对许多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.1k
private int channelCount=2;     //音频采样通道,默认2通道
private int channelConfig=AudioFormat.CHANNEL_IN_STEREO;        //通道设置,默认立体声
private int audioFormat=AudioFormat.ENCODING_PCM_16BIT;     //设置采样数据格式,默认16比特PCM
private FileOutputStream fos;       //用于保存录音文件

//音频录制实例化和录制过程中需要用到的数据
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2;
buffer=new byte[bufferSize];

//实例化AudioRecord
mRecorder=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

按照上面的步骤,我们就能成功的录制PCM音频文件了,但是处于传输和存储方面的考虑,一般来说,我们是不会直接录制PCM音频文件的。而是在录制过程中就对音频数据进行编码为aac、mp3、wav等其他格式的音频文件。

MediaCodec(硬件编解码API)

理解MediaCodec

MediaCodec的使用在Android Developer官网上有详细的说明。官网上的图能够很好的说明MediaCodec的使用方式。我们只需理解这个图,然后熟悉下MediaCodec的API就可以很快的上手使用MediaCodec来进行音视频的编解码工作了。 
MediaCodec 
针对于上图,我们可以把InputBuffers和OutputBuffers简单的理解为它们共同组成了一个环形的传送带,传送带上铺满了空盒子。编解码开始后,我们需要得到一个空盒子(dequeueInputBuffer),然后往空盒子中填充原料(需要被编/解码的音/视频数据),并且放回到传送带你取出时候的那个位置上面(queueInputBuffer)。传送带经过处理器(Codec)后,盒子里面的原料被加工成了你所期望的东西(编解码后的数据),你就可以按照你放入原料时候的顺序,连带着盒子一起取出加工好的东西(dequeueOutputBuffer),并将取出来的东西贴标签(加数据头之类的非必须)和装箱(组合编码后的帧数据)操作,同样之后也要把盒子放回到原来的位置(releaseOutputBuffer)。

音频编码实例

在官网上有更规范的使用示例,结合上面的音频录制,编码为AAC音频文件示例代码如下:

private String mime = "audio/mp4a-latm";    //录音编码的mime
private 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

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

这样,得到的文件就是AAC音频文件了,一般Android系统自带的播放器都可以直接播放。

视频编码实例

视频的编码和上面音频的编码也大同小异。摄像头的数据回调时间并不是确定的,就算你设置了摄像头FPS范围为30-30帧,它也不会每秒就一定给你30帧数据。Android摄像头的数据回调,受光线的影响非常严重,这是由HAL层的3A算法决定的,你可以将自动曝光补偿、自动白平光等等给关掉,这样你才有可能得到稳定的帧率。 
而我们录制并编码视频的时候,肯定是希望得到一个固定帧率的视频。所以在视频录制并进行编码的过程中,需要自己想些法子,让帧率固定下来。最简单也是最有效的做法就是,按照固定时间编码,如果没有新的摄像头数据回调来就用上一帧的数据。 
参考代码如下:

private String mime="video/avc";    //编码的MIME
private int rate=256000;            //波特率,256kb
private 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

对于其他格式的音频视频编解码也大同小异了,只要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

参照官方的说明和代码示例,我们可以知道,音视频混合(也可以音频和音频混合),只需要将编码器的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

当然是用MediaMuxer前,肯定是需要创建一个MediaMuxer的实例的:

mMuxer=new MediaMuxer(path+"."+postfix, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
 
  • 1

音频的操作和视频一样更改,将音频编码也加入MeidaMuxer的轨道中,得到一个轨道索引,将编码后的数据加入为MediaMuxer当前音轨的sample data。音轨和上面的视轨各自做各自的,结束录制时,都发送结束标志,然后在编码结束后,停止混合器就可以得到一个固定码率的MP4文件了。

总结

至此,本篇博客就结束了。但是在实际使用MediaCodec和MediaMuxer的过程中,总会遇到这样或者那样的问题,硬编硬解,和硬件相关比较紧密,Android虽然提供了一个很好的API,但是各个厂商在实现的过程中,总是会做些让自己变得独特的事情。当然他们的目的并不是为了独特,有的是为了让产品变得更优秀(虽然最后可能会做砸了),有的是为了省钱,用软件去弥补硬件的缺陷,最后的结果就是苦了做上层开发的码农们。 
从博主在使用MediaCodec和MediaMuxer的过程中遇到的问题,总结下需要注意主要有以下几点:

  1. MediaCodec是Android4.1新增API,MediaMuxer是Android4.3新增API。
  2. 颜色空间。按照Android本身的意思,COLOR_FormatYUV420Planar应该是所有硬件平台都支持的。但是实际上并不是这样。所以在设置颜色空间时,应该获取硬件平台所支持的颜色空间,确保它是支持你打算使用的颜色空间,不支持的话应该启用备用方案(使用其他当前硬件支持的颜色空间)。
  3. 视频尺寸,在一些手机上,视频录制的尺寸可以是任意的。但是有些手机,不支持的尺寸设置会导致录制的视频现错乱。博主在使用Oppo R7测试,360*640的视频,单独录制视频没问题,音视频混合后,出现了颜色错乱的情况,而在360F4手机上,却都是没问题的。将视频宽高都设置为16的倍数,可以解决这个问题。
  4. 编码器格式设置,诸如音频编码的采样率、比特率等,取值也需要结合硬件平台来设置,否则也会导致崩溃或其他问题。这个其实和颜色空间的选择一样。
  5. 网上看到许多queueInputBuffer中设置presentationTimeUs为System.nanoTime()/1000,这样做会导致编码出来的音视频,在播放时,总时长显示的是错误的。应该记录开始时候的nanoTime,然后设置presentationTimeUs为(System.nanoTime()-nanoTime)/1000。
  6. 录制结束时,应该发送结束标志MediaCodec.BUFFER_FLAG_END_OF_STREAM,在编码后区获得这个标志时再终止循环,而不是直接终止循环。

应该还有其他需要注意的问题。我暂时还没遇到。

源码

源码在github中codec module下,有需要的小伙伴fork或者download。后续Android音视频开发相关的Demo也会上传到这个项目下。


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.youkuaiyun.com/junzia/article/details/54018671]


使用OpenGL实现视频录制 之前,我写过一篇文章,用Camera2和GLSurface实现预览:https://blog.youkuaiyun.com/qq_36391075/article/details/81631461。 今天,来实现录制视频: 思路: 通过MediaCodec创建一个用于输入的Surface .通过通过camera预览时的上下文EGL创建OpenGL的环境,根据上面得到的Surface创建EGLSuf... 阅读详情

相关推荐

媒体编解码器MediaCodec

MediaCodec类可以访问底层媒体编解码框架(Stage或OpenMAX),即编解码组件。通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用。它本身并不是Codec,它通过调用底层编解码组件获得了Codec的能力。

weixin_63357306的博客 4038

音视频开发系列(6)——全面了解Android MediaFormat

MediaFormat 是 Android 平台中用于描述音视频格式的类,它提供了许多 API 用于设置和获取音视频的格式信息。用于创建音频和视频格式的 MediaFormat 对象。需要指定媒体类型(例如 audio/mp4a-latm 或 video/avc)、媒体的采样率、通道数、码率、帧率等信息。

一个码农的博客 5458

Android 音视频开篇:基础知识(硬解码)

目录 一、Android音视频硬解码篇: 1,音视频基础知识 2,音视频硬解码流程:封装基础解码框架 3,音视频播放:音视频同步 4,音视频解封和封装:生成一个MP4 二、使用OpenGL渲染视频画面篇 1,初步了解OpenGL ES 2,使用OpenGL渲染视频画面 3,OpenGL渲染多视频,实现画中画 4,深入了解OpenGL之EGL 5,OpenGL FBO数据缓冲区 6,Android音视频硬编码:生成一个MP4 三、Android FFmpeg音视频解码篇 1,...

1 1354

Android音视频开发学习笔记

《Learn OpenGL 中文》:https://learnopengl-cn.github.io/ 《Android音频开发》:http://ticktick.blog.51cto.com/823160/d-15 整理一些优秀的文章: 0.从开发小白到音视频专家 1.Android直播实现(一)Android端推流、播放 2.android音视频点/直播模块开发 3.Andro...

大漠孤烟的博客 2542

Zemax中场曲与畸变图的深度解析与应用场景对比

本文深度解析了Zemax中场曲/畸变图与网格畸变图的核心原理、计算逻辑与适用场景。通过对比常规成像镜头、扫描镜头、鱼眼镜头及变形宽银幕镜头等实际案例,详细阐述了如何根据系统对称性、视场角及核心需求选择正确的分析工具与畸变评价标准,为光学设计中的畸变诊断与优化提供清晰指导。

sand8的博客 482

Cadence Virtuoso VIVA波形分析:从背景色修改到线宽调整的完整指南

本文详细介绍了Cadence Virtuoso VIVA波形分析工具的核心使用技巧,包括背景色修改、线宽调整等高级功能。通过具体操作步骤和配置方法,帮助工程师优化波形显示效果,提升工作效率。特别强调了与Calculator组件的配合使用,以及专业波形图的输出技巧。

weixin_27330481的博客 623

Android音视频【四】H264硬编码

人间观察 你什么时候觉得自己好孤独 这篇我们介绍如何在Android中实现硬编码为H264,用camera或者其它数据来源也行,我们先不用camera,camera还是蛮复杂的(比如前后置摄像头预览,yuv数据转换,camera2等等)。本篇采用录屏(录制手机屏幕)然后编码h264到文件。 如果你看了上篇文章了解了硬编码和h264,这篇就很简单了。 代码用kotlin实现,学kotlin中,练练手。。。 录屏 Android中的录屏功能是在Android 5.0 API 21 上新增的,不需要roo.

顾修忠的博客 5656

ICT调试技巧:从基础到实战的全面指南

本文提供了一份全面的ICT调试技巧指南,涵盖从基础认知到实战应用的全过程。详细解析了ICT设备硬件结构、程序文件构成,并重点讲解了调试界面的核心参数与高效快捷键。通过跳线、电阻、电容及二极管等常见元器件的实战调试案例,分享了参数设置要点与典型问题解决方案,帮助工程师快速掌握ICT调试核心技能,提升测试效率与准确性。

weixin_29202213的博客 342

AndroidInstantVideo入门教程:5分钟搭建Android视频硬编码环境

想要快速掌握Android视频硬编码技术吗?🤔 AndroidInstantVideo项目为你提供了一个完美的起点!这个开源项目展示了Android硬编码下的视频数据流动,支持视频处理(如加滤镜、加水印)和RTMP直播推流功能。无论你是Android开发新手还是想要深入了解视频编码的开发者,这篇教程都将带你快速上手。 ## 📱 项目简介与核心功能 AndroidInstantVideo是一

gitblog_00846的博客 993

从“跳变”到“连续”:深入理解霍尔传感器插值与PLL角度估算的优劣与选择

本文深入解析霍尔传感器角度估算技术,对比线性插值法和锁相环(PLL)跟踪法的优劣,为电机控制领域的工程师提供实用指南。通过STM32F407和CubeMX的实例,详细探讨两种方法的实现细节、性能对比及优化策略,帮助在不同应用场景下做出最优选择。

weixin_30855761的博客 560

Mamba SSM 实战:IMDb 文本分类 + Mamba-ssm 全流程

Mamba SSM 文本分类链路:IMDb 二分类 → MambaLMHeadModel (d_model=256, n_layer=8, d_state=16, exp2=2) → 末 token 分类头。5 epoch 达 89.2% 准确率 (与 BERT-base 持平), 但显存降低 44% (3.5GB vs 6.2GB)。建议序列长度 ≥ 1024 再选 Mamba, 短序列 (< 512) 用 BERT 更高效。扩展时注意d_state。

m0_71589036的博客 57

给Android工程师的音视频开发学习指南

毕业至今,之前一直从事Android开发的工作,今年5月份开始接触音视频开发相关工作,于是打算写一个音视频相关专栏,让移动端的同学,能通过这个专栏快速掌握音视频相关知识,首先带来第一篇,移动端的音视频技术涉及哪些? 1. 整体流程 以手机直播为例,其整体流程如下: 2. 数据采集 2.1. 音频采集 音频采集涉及到以下几点: 检测麦克风是否可以使用; 需要检测手机对某个音频采样率的支持; 在一些情况下需要对音频进行回声消除处理; 音频采集时设置正确的缓冲区大小。 在 Android 系统中,一般使用 Au

一个码农的博客 892

基于PUMA实现DOA估计-附Matlab代码

具体而言,PUMA算法将接收的信号分别在时域和频域上进行处理,并结合平稳合成(SS)和平稳子空间(SSP)方法,以提高估计精度。该算法将传统的基于协方差矩阵的最大似然(ML)和最小二乘(LS)方法扩展到了非均匀线性阵列(ULA)上,并通过对信号分离和DOA估计的优化,达到了高精度和高分辨率的效果。方位角(DOA)估计是信号处理中的一个重要问题,它的目的是确定从多个传感器接收到的信号的方向。本文将介绍如何使用基于PUMA的方位角估计算法来实现DOA估计,并附有相应的Matlab代码实现。

Byte_O_O的博客 497

Android常见问题-科普一下面试中常问的音视频基础知识

一本静态的小人书,通过翻动以后,就会变成一个有趣的小动画,如果画面够多,翻动速度够快的话,这其实就是一个小视频。而视频的原理正是如此,由于人类眼睛的特殊结构,画面快速切换时,画面会有残留,感觉起来就是连贯的动作。所以,视频就是由一系列图片构成的。音频数据的承载方式最常用的是脉冲编码调制,即PCM。在自然界中,声音是连续不断的,是一种模拟信号,那怎样才能把声音保存下来呢?那就是把声音数字化,即转换为数字信号。我们知道声音是一种波,有自己的振幅和频率,那么要保存声音,就要保存声音在各个时间点上的振幅。

不靠谱先森的博客 941

在 SAP 中,Primary Cost Element(初级成本要素)与Secondary Cost Element(次级成本要素)的核心区别、作用及业务含义可归纳如下

摘要:SAP中初级成本要素与次级成本要素的核心区别在于:初级要素必须关联FI模块的损益科目,直接影响财务报表,用于记录外部成本(如电费、薪酬);次级要素仅存在于CO模块,用于内部成本分配(如分摊、作业分配),不影响外部报表。初级要素是"外部成本入口",次级要素是"内部成本搬运工",二者共同实现成本的多维度追踪与管理。

金牌架构师 762

HyperLynx(十五)多板仿真

在对话框的“Design file#1”下拉列表中选择“B00,Mainboard”,在其下方的“Reference designators”列表中选择元器件“J1”,然后在“Designfile#2”下拉列表中选择“BO1、RAM1”,在其下方的“Refer ence designators”列表中选择元器件“J1”,单击“Insert”按钮,为这两个元器件建立互连映射。在“Assign Models”对话框中,选择“B00 Motherboard”,将“U16.B12”设置为输出。

小幽余生不加糖 5062

Android 音视频涉及到的技术总结

现在市面上的图像,音视频软件越来越多,最近两年也是直播,短视频的红利期。而图像、音视频一直是互联网视觉的入口,掌握并熟练运用音视频、图像技术已经是当前互联网时代不可或缺的技能,而且这个技能是具有沉淀性质的。目前市面上的学习资料参差不齐,我觉得想要开启音视频的学习之路,先得了解整体流程上会涉及的技术点,再一个个击破。我也是音视频方面的小白,最近公司要做一个视频换脸的应用,目前处于技术调研期。趁此空档,我准备开始我的音视频学习之路。

yinshipin007的博客 380

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【Android音视频开发打怪升级:音视频硬解码篇】一、音视频基础知识

开篇炙手可热,望而生畏的音视频开发时至今日,短视频App可谓是如日中天,一片兴兴向荣。随着短视频的兴起,音视频开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,...

音视频开发进阶 696

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

【Android 音视频开发打怪升级:音视频硬解码篇】一、音视频基础知识

作为开篇的文章,我们先来看看音视频由什么构成的,以及一些常见的术语和概念。

开发的猫的博客 2009

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

上一篇: Thread.join()详解
下一篇: 计算机中容量单位bit(位)、B(字节)、KB、MB、GB和TB的关系
闲暇部落
博客等级 码龄12年 2777粉丝 215原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值