不可发布违法信息,一旦发现永久封号,欢迎向我们举报!
百科|常识分享
分享各种百科|日常
18潮服网 > 餐饮行业新闻资讯 > 百科|常识 >  Android AVDemo(8):视频编码,H.264 和 H.265 都支持丨音视频工程示例 科普视频被恶意混剪成了面膜推荐!真鹤叔不干了,要打假鹤叔


  • 【莆田鞋厂家分类】
  • 【奢侈大牌包包厂家分类】
  • 【潮牌奢侈服饰鞋子厂家分类】
  • 【名表厂家分类】

厂家货源分类区域

Android AVDemo(8):视频编码,H.264 和 H.265 都支持丨音视频工程示例 科普视频被恶意混剪成了面膜推荐!真鹤叔不干了,要打假鹤叔

发布时间:2024-09-12 10:24:14  来源:互联网整理  浏览:   【】【】【

Android AVDemo(8):视频编码,H.264 和 H.265 都支持丨音视频工程示例 科普视频被恶意混剪成了面膜推荐!真鹤叔不干了,要打假鹤叔 

Android AVDemo(8):视频编码,H.264 和 H.265 都支持丨音视频工程示例

vx 搜索『gjzkeyframe』 关注『关键帧Keyframe』来及时获得最新的音视频技术文章。

塞尚《樱桃和桃子》

iOS/Android 客户端开发同学如果想要开始学习音视频开发,最丝滑的方式是对音视频基础概念知识有一定了解后,再借助 iOS/Android 平台的音视频能力上手去实践音视频的采集 → 编码 → 封装 → 解封装 → 解码 → 渲染过程,并借助音视频工具来分析和理解对应的音视频数据。

在音视频工程示例这个栏目,我们将通过拆解采集 → 编码 → 封装 → 解封装 → 解码 → 渲染流程并实现 Demo 来向大家介绍如何在 iOS/Android 平台上手音视频开发。

这里是 Android 第八篇:Android 视频编码 Demo。这个 Demo 里包含以下内容:

1)实现一个视频采集模块;

2)实现两个视频编码模块 ByteBuffer、Surface,支持 H.264/H.265;

3)串联视频采集和编码模块,将采集到的视频数据输入给编码模块进行编码,并存储为文件;

4)详尽的代码注释,帮你理解代码逻辑和原理。

在本文中,我们将详解一下 Demo 的具体实现和源码。读完本文内容相信就能帮你掌握相关知识。

不过,如果你的需求是:1)直接获得全部工程源码;2)想进一步咨询音视频技术问题;3)咨询音视频职业发展问题。可以根据自己的需要考虑是否加入『关键帧的音视频开发圈』。

想要了解视频编码,可以看看这几篇:

《视频编码(1):H.264(AVC)》

《视频编码(2):H.265(HEVC)》

《视频编码(3):H.266(VVC)》

1、视频采集模块

在这个 Demo 中,视频采集模块 KFVideoCapture 的实现与《Android 视频采集 Demo》中一样,这里就不再重复介绍了,其接口如下:

KFIVideoCapture.java

public interface KFIVideoCapture {

///< 视频采集初始化。

public void setup(Context context, KFVideoCaptureConfig config, KFVideoCaptureListener listener, EGLContext eglShareContext);

///< 释放采集实例。

public void release();

///< 开始采集。

public void startRunning();

///< 关闭采集。

public void stopRunning();

///< 是否正在采集。

public boolean isRunning();

///< 获取 OpenGL 上下文。

public EGLContext getEGLContext();

///< 切换摄像头。

public void switchCamera();

}

2、视频 ByteBuffer 编码模块

在实现视频编码模块之前,我们先实现一个视频编码配置类 KFVideoEncoderConfig:

KFVideoEncoderConfig.java

public class KFVideoEncoderConfig {

public Size size = new Size(720,1280);

public int bitrate = 4 * 1024 * 1024;

public int fps = 30;

public int gop = 30 * 4;

public boolean isHEVC = false;

public int profile = MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline;

public int profileLevel = MediaCodecInfo.CodecProfileLevel.AVCLevel1;

public KFVideoEncoderConfig() {

}

}

这里可以配置各种编码参数,但不同机型支持能力不同,通过此配置生成编码格式描述 MediaFormat。

接下来,我们来实现一个视频编码模块 KFByteBufferCodec,编码模块 KFByteBufferCodec 的实现与 《Android 音频编码 Demo》 中一样,这里就不再重复介绍了,其接口如下

KFMediaCodecInterface.java

public interface KFMediaCodecInterface {

public static final int KFMediaCodecInterfaceErrorCreate = -2000;

public static final int KFMediaCodecInterfaceErrorConfigure = -2001;

public static final int KFMediaCodecInterfaceErrorStart = -2002;

public static final int KFMediaCodecInterfaceErrorDequeueOutputBuffer = -2003;

public static final int KFMediaCodecInterfaceErrorParams = -2004;

public static int KFMediaCodeProcessParams = -1;

public static int KFMediaCodeProcessAgainLater = -2;

public static int KFMediaCodeProcessSuccess = 0;

///< 初始化 Codec,第一个参数需告知使用编码还是解码。

public void setup(boolean isEncoder,MediaFormat mediaFormat, KFMediaCodecListener listener, EGLContext eglShareContext);

///< 释放 Codec。

public void release();

///< 获取输出格式描述。

public MediaFormat getOutputMediaFormat();

///< 获取输入格式描述。

public MediaFormat getInputMediaFormat();

///< 处理每一帧数据,编码前与编码后都可以,支持编解码 2 种模式。

public int processFrame(KFFrame frame);

///< 清空 Codec 缓冲区。

public void flush();

}

上面是 KFByteBufferCodec 接口的设计,与音频编码对比区别如下:

1)音频编码使用了继承类 KFAudioByteBufferEncoder,视频编码则直接使用类 KFByteBufferCodec。

音频编码使用了继承类 KFByteBufferCodec,目的是切割合适大小的数据 2048 送入编码器,因为 AAC 数据编码每帧大小为 1024 * 2(位深 16 Bit)。

视频编码使用了类 KFByteBufferCodec。

2)外层使用构造方法时配置参数修改:

setup 接口 mInputMediaFormat 需要设置视频编码的格式描述。

更具体细节见上述代码及其注释。

3、视频 Surface 编码模块

接下来,我们来实现一个视频编码模块 KFVideoSurfaceEncoder,在这里输入采集后的数据,输出编码后的数据,同样也需要实现接口 KFMediaCodecInterface,参考模块 KFByteBufferCodec。

KFVideoSurfaceEncoder.java

public class KFVideoSurfaceEncoder implements KFMediaCodecInterface {

private static final String TAG = "KFVideoSurfaceEncoder";

private KFMediaCodecListener mListener = null; ///< 回调。

private KFGLContext mEGLContext = null; ///< GL 上下文。

private KFGLFilter mFilter = null; ///< 渲染到 Surface 特效。

private MediaCodec mEncoder = null; ///< 编码器。

private Surface mSurface = null; ///< 渲染 Surface 缓存。

private HandlerThread mEncoderThread = null; ///< 编码线程。

private Handler mEncoderHandler = null;

private Handler mMainHandler = new Handler(Looper.getMainLooper()); ///< 主线程。

private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();

private long mLastInputPts = 0;

private MediaFormat mOutputFormat = null; ///< 输出格式描述。

private MediaFormat mInputFormat = null; ///< 输入格式描述。

public KFVideoSurfaceEncoder() {

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

@Override

public void setup(boolean isEncoder,MediaFormat mediaFormat, KFMediaCodecListener listener, EGLContext eglShareContext) {

mInputFormat = mediaFormat;

mListener = listener;

mEncoderThread = new HandlerThread("KFSurfaceEncoderThread");

mEncoderThread.start();

mEncoderHandler = new Handler((mEncoderThread.getLooper()));

mEncoderHandler.post(()->{

if (mInputFormat == null) {

_callBackError(KFMediaCodecInterfaceErrorParams,"mInputFormat == null");

return;

}

///< 初始化编码器。

boolean setupSuccess = _setupEnocder();

if (setupSuccess) {

mEGLContext = new KFGLContext(eglShareContext,mSurface);

mEGLContext.bind();

///< 初始化特效,用于纹理渲染到编码器 Surface 上。

_setupFilter();

mEGLContext.unbind();

}

});

}

@Override

public MediaFormat getOutputMediaFormat() {

return mOutputFormat;

}

@Override

public MediaFormat getInputMediaFormat() {

return mInputFormat;

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

@Override

public void release() {

mEncoderHandler.post(()->{

///< 释放编码器。

if (mEncoder != null) {

try {

mEncoder.stop();

mEncoder.release();

} catch (Exception e) {

Log.e(TAG, "release: " + e.toString());

}

mEncoder = null;

}

///< 释放 GL 特效上下文。

if (mEGLContext != null) {

mEGLContext.bind();

if (mFilter != null) {

mFilter.release();

mFilter = null;

}

mEGLContext.unbind();

mEGLContext.release();

mEGLContext = null;

}

///< 释放 Surface 缓存。

if (mSurface != null) {

mSurface.release();

mSurface = null;

}

mEncoderThread.quit();

});

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

@Override

public int processFrame(KFFrame inputFrame) {

if (inputFrame == null || mEncoderHandler == null) {

return KFMediaCodeProcessParams;

}

KFTextureFrame frame = (KFTextureFrame)inputFrame;

mEncoderHandler.post(()-> {

if (mEncoder != null && mEGLContext != null) {

if (frame.isEnd) {

///< 最后一帧标记。

mEncoder.signalEndOfInputStream();

} else {

///< 最近一帧时间戳。

mLastInputPts = frame.usTime();

mEGLContext.bind();

///< 渲染纹理到编码器 Surface 设置视口。

GLES20.glViewport(0, 0, frame.textureSize.getWidth(), frame.textureSize.getHeight());

mFilter.render(frame);

///< 设置时间戳。

mEGLContext.setPresentationTime(frame.usTime() * 1000);

mEGLContext.swapBuffers();

mEGLContext.unbind();

///< 获取编码后的数据,尽量拿出最多的数据出来,回调给外层。

long outputDts = -1;

while (outputDts < mLastInputPts){

int bufferIndex = 0;

try {

bufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 10 * 1000);

} catch (Exception e) {

Log.e(TAG, "Unexpected MediaCodec exception in dequeueOutputBufferIndex, " + e);

_callBackError(KFMediaCodecInterfaceErrorDequeueOutputBuffer,e.getMessage());

return;

}

if (bufferIndex >= 0) {

ByteBuffer byteBuffer = mEncoder.getOutputBuffer(bufferIndex);

if (byteBuffer != null) {

outputDts = mBufferInfo.presentationTimeUs;

if (mListener != null) {

KFBufferFrame encodeFrame = new KFBufferFrame();

encodeFrame.buffer = byteBuffer;

encodeFrame.bufferInfo = mBufferInfo;

mListener.dataOnAvailable(encodeFrame);

}

} else {

break;

}

try {

mEncoder.releaseOutputBuffer(bufferIndex, false);

} catch (Exception e) {

Log.e(TAG, e.toString());

return;

}

} else {

if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

mOutputFormat = mEncoder.getOutputFormat();

}

break;

}

}

}

}

});

return KFMediaCodeProcessSuccess;

}

@Override

public void flush() {

mEncoderHandler.post(()-> {

///< 刷新缓冲区。

if (mEncoder != null) {

try {

mEncoder.flush();

} catch (Exception e) {

Log.e(TAG, "flush error!" + e);

}

}

});

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

private boolean _setupEnocder() {

///< 初始化编码器。

try {

String mimeType = mInputFormat.getString(MediaFormat.KEY_MIME);

mEncoder = MediaCodec.createEncoderByType(mimeType);

mEncoder.configure(mInputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

} catch (IOException e) {

Log.e(TAG, "createEncoderByType" + e);

_callBackError(KFMediaCodecInterfaceErrorCreate,e.getMessage());

return false;

}

///< 创建 Surface。

mSurface = mEncoder.createInputSurface();

///< 开启编码器。

try {

mEncoder.start();

} catch (Exception e) {

Log.e(TAG, "start" + e );

_callBackError(KFMediaCodecInterfaceErrorStart,e.getMessage());

return false;

}

return true;

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

private void _setupFilter() {

///< 创建渲染模块,渲染到编码器 Surface。

if (mFilter == null) {

mFilter = new KFGLFilter(true, KFGLBase.defaultVertexShader,KFGLBase.defaultFragmentShader);

}

}

private void _callBackError(int error, String errorMsg){

///< 出错回调。

if (mListener != null) {

mMainHandler.post(()->{

mListener.onError(error,TAG + errorMsg);

});

}

}

}

上面是 KFVideoSurfaceEncoder 的实现,与视频编码 KFByteBufferCodec 对比区别如下:

1)数据源输入不同。

KFByteBufferCodec 输入为 YUV 数据 KFBufferFrame。

KFVideoSurfaceEncoder 输入为纹理数据 KFTextureFrame。

2)编码流水线不同。

KFByteBufferCodec 输入 YUV 数据进行编码。

KFVideoSurfaceEncoder 输入为纹理数据,执行 OpenGL 渲染,将纹理渲染到编码器缓存 mSurface。使用 mFilter.render 进行渲染,同时设置时间戳 setPresentationTime,交换前后台缓冲区 swapBuffers ,将纹理数据刷新到了 mSurface。最后取出编码后数据,需要注意 releaseOutputBuffer 方法第 2 个参数 render 设置为 true。

3)使用场景不同。

KFVideoSurfaceEncoder适用于输入数据为纹理的情况,例如采集后添加特效。

KFByteBufferCodec 适用于非纹理数据,例如游戏直播、录屏直播、图片转视频等输入数据为 ByteBuffer,此时没必要再做数据转换。

更具体细节见上述代码及其注释。

4、采集视频数据进行 H.264/H.265 编码和存储

我们在一个 MainActivity 中来实现视频采集及编码逻辑,因为 Android 编码的默认输出 AnnexB 码流格式,所以这里不需要转换。

MainActivity.java

public class MainActivity extends AppCompatActivity {

private KFIVideoCapture mCapture; ///< 采集器。

private KFVideoCaptureConfig mCaptureConfig; ///< 采集配置。

private KFRenderView mRenderView; ///< 渲染视图。

private KFGLContext mGLContext; ///< OpenGL 上下文。

private KFVideoEncoderConfig mEncoderConfig; ///< 编码配置。

private KFMediaCodecInterface mEncoder; ///< 编码。

private FileOutputStream mStream = null;

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||

ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||

ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions((Activity) this,

new String[] {Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},

1);

}

///< 创建 GL 上下文。

mGLContext = new KFGLContext(null);

///< 创建渲染视图。

mRenderView = new KFRenderView(this,mGLContext.getContext());

WindowManager windowManager = (WindowManager)this.getSystemService(this.WINDOW_SERVICE);

Rect outRect = new Rect();

windowManager.getDefaultDisplay().getRectSize(outRect);

FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(outRect.width(), outRect.height());

addContentView(mRenderView,params);

FrameLayout.LayoutParams startParams = new FrameLayout.LayoutParams(200, 120);

startParams.gravity = Gravity.CENTER_HORIZONTAL;

Button startButton = new Button(this);

startButton.setTextColor(Color.BLUE);

startButton.setText("开始");

startButton.setVisibility(View.VISIBLE);

startButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

///< 创建编码器。

if (mEncoder == null) {

mEncoder = new KFVideoSurfaceEncoder();

MediaFormat mediaFormat = KFAVTools.createVideoFormat(mEncoderConfig.isHEVC,mEncoderConfig.size, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,mEncoderConfig.bitrate,mEncoderConfig.fps,mEncoderConfig.gop / mEncoderConfig.fps,mEncoderConfig.profile,mEncoderConfig.profileLevel);

mEncoder.setup(true,mediaFormat,mVideoEncoderListener,mGLContext.getContext());

((Button)view).setText("停止");

} else {

mEncoder.release();

mEncoder = null;

((Button)view).setText("开始");

}

}

});

addContentView(startButton, startParams);

///< 创建采集器。

mCaptureConfig = new KFVideoCaptureConfig();

mCaptureConfig.cameraFacing = LENS_FACING_FRONT;

mCaptureConfig.resolution = new Size(720,1280);

mCaptureConfig.fps = 30;

boolean useCamera2 = false;

if (useCamera2) {

mCapture = new KFVideoCaptureV2();

} else {

mCapture = new KFVideoCaptureV1();

}

mCapture.setup(this,mCaptureConfig,mVideoCaptureListener,mGLContext.getContext());

mCapture.startRunning();

mEncoderConfig = new KFVideoEncoderConfig();

}

private KFVideoCaptureListener mVideoCaptureListener = new KFVideoCaptureListener() {

@Override

public void cameraOnOpened(){}

@Override

public void cameraOnClosed() {

}

@Override

public void cameraOnError(int error,String errorMsg) {

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

@Override

public void onFrameAvailable(KFFrame frame) {

///< 采集数据回调,进入编码器。

mRenderView.render((KFTextureFrame) frame);

责任编辑:
热门阅读排行
© 18潮服网