太久沒寫博客了,由於工作,過年還有孩子出生搞得自己焦頭爛額,現在有些時間了就搞點東西。發現瀏覽量突破10萬了,也是挺高興的,雖然很多東西寫的不好,可也看到了自己的進步,也是前年到現在的累積。剛開始我只是學習視頻解碼,渲染和視頻編碼,慢慢的也開始搞音頻了,本來沒想過搞視頻編輯這一塊的,慢慢的做着做着就接觸到了,也沒想到會搞成一個系列,等完成了再好好整理一下,廢話不多說開始說正題。
轉gif由於文件大小問題降低了幀率所以看起來卡其實不卡
這篇文章只講畫面部分,音頻部分在我以前的文章有android 使用MediaCodec和lamemp3對多段音頻進行截取和拼接,以後再寫一篇相結合的文章,真正實現一個有畫面有聲音的視頻裁剪和合並。做音頻拼接知道有些參數要一樣纔可以進行拼接,視頻也一樣,主要是幀速率和分辨率要一樣,因爲用到opengl,所以分辨率這一塊可以通過計算解決,而幀速率可以進行丟幀處理,選最小的幀速率爲參數,對大幀速率的視頻進行丟幀。
首先裁剪視頻,裁剪視頻用的是我的這篇文章android 簡單的視頻編輯功能
主要用到這些參數,這是純畫面的,先不處理聲音
已經加入了音頻
//視頻路徑
private String videoFile;
//裁剪的座標長寬
private int cropLeft;
private int cropTop;
private int cropWidth;
private int cropHeight;
//開始結束時間
private long startTime;
private long endTime;
//幀時間
private long frameTime;
得到複數的視頻開始統一參數
//這裏遍歷數據,選擇最小的參數用來初始化編碼器
cropWidth = decoderList.get(0).getCropWidth();
cropHeight = decoderList.get(0).getCropHeight();
int frameRate = decoderList.get(0).getFrameRate();
frameTime = decoderList.get(0).getFrameTime();
//寬高選擇方案
//以最小寬高來顯示還是根據寬高比計算最小顯示分辨率
//我這選用根據寬高比計算最小顯示分辨率
float sh = cropWidth*1.0f/cropHeight;
if(decoderList.size() != 1) {
for (VideoDecoder holder : decoderList) {
float vh = holder.getCropWidth()*1.0f/holder.getCropHeight();
if( sh < vh){
cropWidth = Math.min(cropWidth,holder.getCropWidth());
cropHeight = (int) (cropWidth*vh);
//寬高不能是奇數
if(cropHeight%2 != 0){
cropHeight = cropHeight - 1;
}
}
//cropWidth = Math.min(cropWidth,holder.getCropWidth());
//cropHeight = Math.min(cropHeight,holder.getCropHeight());
//選最小幀速率,對幀素率大的進行丟幀處理
frameRate = Math.min(frameRate, holder.getFrameRate());
//幀時間,幀速率越小,幀時間越大
frameTime = Math.max(frameTime, holder.getFrameTime());
}
}
//防止MediaFormat取不到幀速率
if(frameRate < 1){
frameRate = (int) (1000/(frameTime/1000));
}
//編碼參數
int BIT_RATE = cropWidth*cropHeight*2*8;
MediaFormat mediaFormat = MediaFormat.createVideoFormat(VIDEO, cropWidth, cropHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
初始化編碼器
//創建編碼器
try {
videoEncode = MediaCodec.createEncoderByType(VIDEO);
} catch (IOException e) {
e.printStackTrace();
}
videoEncode.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
將編碼器生成的Surface傳給opengl來獲取opengl的顯示畫面
//將編碼器生成的Surface傳給opengl來獲取opengl的顯示畫面
Surface surface = videoEncode.createInputSurface();
videoEncode.start();
//初始化opengl
eglUtils = new EGLUtils();
eglUtils.initEGL(surface);
framebuffer = new GLFramebuffer();
framebuffer.initFramebuffer(cropWidth,cropHeight);
surfaceTexture = framebuffer.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(cropWidth,cropHeight);
解碼開始前要計算顯示的畫面
//計算裁剪的顯示畫面
float f = decoder.getCropLeft()*1.0f/decoder.getVideoWidth();
float t = 1.0f - decoder.getCropTop()*1.0f/decoder.getVideoHeight();
float r = (decoder.getCropLeft()+decoder.getCropWidth())*1.0f/decoder.getVideoWidth();
float b = 1.0f - (decoder.getCropTop()+decoder.getCropHeight())*1.0f/decoder.getVideoHeight();
float[] textureVertexData = {
r, b,
f, b,
r, t,
f, t
};
//將要顯示的畫面傳給opengl
framebuffer.setVertexDat(textureVertexData);
framebuffer.setRect(decoder.getCropWidth(),decoder.getCropHeight());
創建解碼器
//創建解碼器
try {
videoDecoder = MediaCodec.createDecoderByType(getMime());
videoDecoder.configure(format, surface, null, 0);
videoDecoder.start();
} catch (IOException e) {
e.printStackTrace();
}
根據時間進行解碼
int outIndex = videoDecoder.dequeueOutputBuffer(info, 50000);
if(outIndex >= 0){
//根據時間進行解碼
if(info.presentationTimeUs >= videoHolder.getStartTime() &&
info.presentationTimeUs <= videoHolder.getEndTime()){
//解碼一幀完成後回調
if(decoderListener != null){
decoderListener.onDecoder(info.presentationTimeUs,videoHolder);
}
}
videoDecoder.releaseOutputBuffer(outIndex, true);
//判斷是否裁剪內部分解碼完成
if(info.presentationTimeUs >= videoHolder.getEndTime()){
break;
}
}
解碼出的圖像進行處理
surfaceTexture.updateTexImage();
surfaceTexture.getTransformMatrix(mSTMatrix);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0f,0f,0f,0f);
GLES20.glViewport(rect.left, rect.top, rect.right, rect.bottom);
GLES20.glUseProgram(programId);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffers[0]);
GLES20.glEnableVertexAttribArray(aPositionHandle);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
12, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffers[1]);
GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle, 2, GLES20.GL_FLOAT, false, 8, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
GLES20.glUniform1i(uTextureSamplerHandle,0);
GLES20.glUniformMatrix4fv(uSTMMatrixHandle, 1, false, mSTMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
進行編碼
ByteBuffer encodedData = videoEncode.getOutputBuffer(inputIndex);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
info.size = 0;
}
if (info.size != 0) {
encodedData.position(info.offset);
encodedData.limit(info.offset + info.size);
info.presentationTimeUs = timeUs;
//填充數據
mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info);
Log.d("==============","timeUs = "+timeUs);
timeUs = timeUs+frameTime;
//結合presentationTimeUs - videoHolder.getStartTime() < startFrameTime
//對幀速率大的進行丟幀
startFrameTime = startFrameTime + frameTime;
}
videoEncode.releaseOutputBuffer(inputIndex, false);
詳細的可以看我的demo
現在沒加進度條,處理的進度就看log打印,等整理好以後加上音頻和進度條,成爲一個可以看的demo
2019/3/27更新
因爲音頻要轉碼成MP3,再轉成視頻所以效率不太好,可是也找不到什麼好方法將採樣率,比特率和聲道變一樣的方法
在整合音頻時代碼做了大改動,畫面核心代碼沒變只是放到其他類和函數內了,所以就不改博客了,可以下載demo,裏面我也做了註釋