Skip to content

Commit

Permalink
ffmpeg添加录制视频功能
Browse files Browse the repository at this point in the history
  • Loading branch information
r17171709 committed Oct 19, 2020
1 parent 3a98c75 commit 38ec130
Show file tree
Hide file tree
Showing 12 changed files with 941 additions and 5 deletions.
8 changes: 5 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {
compileSdkVersion 29
defaultConfig {
applicationId "com.renyu.androidimagelibrary"
minSdkVersion 19
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
Expand All @@ -18,7 +18,7 @@ android {

ndk {
//选择要添加的对应cpu类型的.so库。
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' // 还可以添加 'armeabi', 'x86_64', 'mips', 'mips64'
abiFilters 'armeabi-v7a', 'x86' // 还可以添加 'armeabi', 'x86_64', 'mips', 'mips64', 'arm64-v8a'
}
}
buildTypes {
Expand Down Expand Up @@ -51,11 +51,13 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// 添加支持multidex的兼容包
implementation 'androidx.multidex:multidex:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.github.renyu:ActionSheetLibrary:2.2.5'
implementation 'com.github.renyu:AndroidNetworkLibrary:2.1.4'
// RxFFmpeg 🔥RxFFmpeg 是基于 ( FFmpeg 4.0 + X264 + mp3lame + fdk-aac + opencore-amr + openssl ) 编译的适用于 Android 平台的音视频编辑、视频剪辑的快速处理框架,包含以下功能:视频拼接,转码,压缩,裁剪,片头片尾,分离音视频,变速,添加静态贴纸和gif动态贴纸,添加字幕,添加滤镜,添加背景音乐,加速减速视频,倒放音视频,音频裁剪,变声,混音,图片合成视频,视频解码图片,抖音首页,视频播放器及支持 OpenSSL https 等主流特色功能
implementation 'com.github.microshow:RxFFmpeg:4.8.0-lite'
implementation 'com.github.microshow:RxFFmpeg:4.8.0'
implementation project(':ImageLibrary')
// implementation 'com.github.renyu:AndroidImageLibrary:3.4.7'
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".RecordActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
43 changes: 43 additions & 0 deletions app/src/main/java/com/libyuv/LibyuvUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.libyuv;

import android.graphics.Bitmap;

public class LibyuvUtil {

public static void loadLibrary() {
System.loadLibrary("yuv-sample");
}

/**
* 将 NV21 转 I420
*/
public static native void convertNV21ToI420(byte[] src, byte[] dst, int width, int height);

/**
* 压缩 I420 数据
* <p>
* 执行顺序为:缩放->旋转->镜像
*
* @param src 原始数据
* @param srcWidth 原始宽度
* @param srcHeight 原始高度
* @param dst 输出数据
* @param dstWidth 输出宽度
* @param dstHeight 输出高度
* @param degree 旋转(90, 180, 270)
* @param isMirror 镜像(镜像在旋转之后)
*/
public static native void compressI420(byte[] src, int srcWidth, int srcHeight,
byte[] dst, int dstWidth, int dstHeight,
int degree, boolean isMirror);

/**
* 将 I420 数据注入到 Bitmap 中
*/
public static native void convertI420ToBitmap(byte[] src, Bitmap dst, int width, int height);

/**
* 将 I420 转 NV12
*/
public static native void convertI420ToNV12(byte[] src, byte[] dst, int width, int height);
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ public void onFinish() {
public void onProgress(int progress, long progressTime) {
MainActivity mainActivity = weakReference.get();
if (mainActivity != null) {
File file = new File(InitParams.IMAGE_PATH + "/input.mp4");
Log.d("TAG", "onProgress: " + progress);
}
}
Expand Down
126 changes: 126 additions & 0 deletions app/src/main/java/com/renyu/androidimagelibrary/RecordActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.renyu.androidimagelibrary;

import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.blankj.utilcode.util.TimeUtils;
import com.libyuv.LibyuvUtil;
import com.renyu.commonlibrary.params.InitParams;
import com.zhaoss.weixinrecorded.util.CameraHelp;
import com.zhaoss.weixinrecorded.util.RecordUtil;

import java.io.File;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;

import io.microshow.rxffmpeg.RxFFmpegInvoke;

public class RecordActivity extends AppCompatActivity {
private RecordUtil recordUtil;
private String videoPath;
private String audioPath;
private CameraHelp mCameraHelp;

private SurfaceView surfaceView;
private SurfaceHolder mSurfaceHolder;

private ArrayBlockingQueue<byte[]> mYUVQueue = new ArrayBlockingQueue<>(10);

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);

LibyuvUtil.loadLibrary();
mCameraHelp = new CameraHelp();

surfaceView = findViewById(R.id.surfaceView);
surfaceView.post(() -> {
int width = surfaceView.getWidth();
int height = surfaceView.getHeight();
float viewRatio = width * 1f / height;
float videoRatio = 9f / 16f;
ViewGroup.LayoutParams layoutParams = surfaceView.getLayoutParams();
if (viewRatio > videoRatio) {
layoutParams.height = (int) (width / viewRatio);
} else {
layoutParams.width = (int) (height * viewRatio);
}
surfaceView.setLayoutParams(layoutParams);
});
mCameraHelp.setPreviewCallback((data, camera) -> {
if (mYUVQueue.size() >= 10) {
mYUVQueue.poll();
}
mYUVQueue.add(data);
});
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurfaceHolder = holder;
mCameraHelp.openCamera(RecordActivity.this, Camera.CameraInfo.CAMERA_FACING_BACK, mSurfaceHolder);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCameraHelp.release();
}
});
surfaceView.setOnClickListener(v -> mCameraHelp.callFocusMode());

findViewById(R.id.start_record).setOnClickListener((v) -> {
startRecord();
});
findViewById(R.id.stop_record).setOnClickListener((v) -> {
endRecord();
});
}

private void startRecord() {
videoPath = InitParams.IMAGE_PATH + File.separator + System.currentTimeMillis() + ".h264";
audioPath = InitParams.IMAGE_PATH + File.separator + System.currentTimeMillis() + ".pcm";
final boolean isFrontCamera = mCameraHelp.getCameraId() == Camera.CameraInfo.CAMERA_FACING_FRONT;
final int rotation;
if (isFrontCamera) {
rotation = 270;
} else {
rotation = 90;
}
recordUtil = new RecordUtil(videoPath, audioPath, mCameraHelp.getWidth(), mCameraHelp.getHeight(), rotation, isFrontCamera, mYUVQueue);
recordUtil.start();
}

private void endRecord() {
if (recordUtil != null) {
recordUtil.stop();
recordUtil = null;
}

// 生成Mp4
String commandMp4 = "ffmpeg -i " + videoPath + " -vcodec copy -f mp4 /storage/emulated/0/1/test.mp4";
// 生成aac
String commandAAC = "ffmpeg -f s16le -ar 44100 -ac 1 -i " + audioPath + " -acodec libfdk_aac -b:a 64000 -y /storage/emulated/0/1/test.m4a";
// 合成文件
String commandConvert = "ffmpeg -i /storage/emulated/0/1/test.mp4 -i /storage/emulated/0/1/test.m4a -vcodec copy -acodec copy /storage/emulated/0/1/output.mp4";

new Thread(() -> {
Log.d("TAG", "转换开始时间:" + TimeUtils.date2String(new Date()));
RxFFmpegInvoke.getInstance().runFFmpegCmd(commandMp4.split(" "));
RxFFmpegInvoke.getInstance().runFFmpegCmd(commandAAC.split(" "));
RxFFmpegInvoke.getInstance().runFFmpegCmd(commandConvert.split(" "));
Log.d("TAG", "转换结束时间:" + TimeUtils.date2String(new Date()));
}).start();
}
}
Loading

0 comments on commit 38ec130

Please sign in to comment.