Android 使用MediaCodec实现视频的无缝切换

Android 使用MediaCodec实现视频的无缝切换

一、功能说明:

不同控件之间实现视频的无缝切换。不会黑屏,也不需要重新创建解码器。

百度上面很多视频播放都是利用MediaPlayer+显示视图(SurfaceView、TextureView)进行本地或者网络视频的播放。那么利用MediaCodec对视频流进行硬解码的小伙伴该如何在不同的控件之间无缝切换呢?是不是TextureView的生命周期很难控制?

二、实现方案

流程图.png

三、实现效果

效果图.gif

四、TextureView

4.1 与SurfaceTexture的关系

TextureView与SurfaceTexture构成了组合关系,可见SurfaceTexture的确是由TextureView给『包办』了。在程序世界中,一个对象被『包办』无非是指:

(1)这个对象什么时候被创建?

(2)这个对象如何被创建?

(3)这个对象的产生带来了哪些变化,或者说程序自从有了它有哪些不同?​

(4)这个对象什么时候被销毁?​

之所以对​SurfaceTexture这个对象要大动笔墨,因为它是整个显示框架的『连接者』。

4.2 生命周期控制
  1. 切换至后台的时候会调用onSurfaceTextureDestroyed,从后台切换回来会调用onSurfaceTextureAvailable。
  2. TextureView的ViewGroup remove TextureView的时候会调用onSurfaceTextureDestroyed方法。相同,TextureView的ViewGroup add TextureView的时候会调用onSurfaceTextureAvailable。这些都是建立在视图可见的基础上,如果视图不可见,add也不会调用onSurfaceTextureAvailable方法,remove也不会调用onSurfaceTextureDestroyed方法。
  3. 当TextureView设置为Gone的时候,并不会调用onSurfaceTextureDestroyed方法法。
4.3设置SurfaceView

是不是遇到过在播放视频返回后台再回来,发现TextureView显示视图是一片黑色?监听TextureView的生命周期你会发现,返回后台是调用了销毁方法的。那你就会问销毁之后岂不是有需要重新创建?重新创建会引来更多的问题,解码去也需要重新初始化。所以我们只能另寻他法,下面方法就是无缝切换的核心部分。

	@Override
	public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
	  Log.d(TAG, "onSurfaceTextureDestroyed: ");
	  mSurfaceTexture = surface;
	  return false;
	}

在销毁方法中我们注意,有一个返回参数。官方的解释如下

Invoked when the specified SurfaceTexture is about to be destroyed. If returns true, no rendering should happen inside the surface texture after this method is invoked. If returns false, the client needs to call release(). Most applications should return true.

大致的意思是如果返回ture,SurfaceTexture会自动销毁,如果返回false,SurfaceTexture不会销毁,需要用户手动调用release()进行销毁。

现在恍然大悟了吧,我们在销毁的时候返回false,并保存SurfaceTexture对象,然后从后台返回界面的时候在onSurfaceTextureAvailable()方法中,调用setSurfaceTexture(mSurfaceTexture)方法,这样就会恢复之前的画面了。

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, 
int width, int height) {
    Log.d(TAG, "onSurfaceTextureAvailable: ");
    if(mSurfaceTexture!=null){
       mTextureView.setSurfaceTexture(mSurfaceTexture);
    }
}

五、可拖拽效果

使用ItemTouchHelper轻松实现RecyclerView拖拽排序和滑动删除
DEMO传送门

好的,以下是一个使用 MediaCodec 进行视频编码的示例代码,你可以参考一下: ```java import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; public class VideoEncoder { private static final String TAG = "VideoEncoder"; // 编码器 private MediaCodec mEncoder; // 复用器 private MediaMuxer mMuxer; // 视频轨道 private int mTrackIndex; // 是否已经开始编码 private boolean mIsStarted; // 视频宽度 private int mWidth; // 视频高度 private int mHeight; // 视频帧率 private int mFrameRate; // 视频码率 private int mBitRate; // 编码器输出的 buffer 信息 private MediaCodec.BufferInfo mBufferInfo; // 用于存储编码后的数据 private ByteBuffer mOutputBuffer; public VideoEncoder(int width, int height, int frameRate, int bitRate) { mWidth = width; mHeight = height; mFrameRate = frameRate; mBitRate = bitRate; } /** * 开始编码 */ public void start() { try { // 初始化编码器 mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 创建一个文件用于存储编码后的数据 String outputPath = new File(Environment.getExternalStorageDirectory(), "output.mp4").getAbsolutePath(); mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // 开始编码 mEncoder.start(); mIsStarted = true; } catch (IOException e) { e.printStackTrace(); } } /** * 编码一帧数据 * * @param input 输入的数据 * @param pts 每一帧的时间戳 */ public void encodeFrame(byte[] input, long pts) { if (!mIsStarted) { Log.e(TAG, "Encoder is not started."); return; } // 取出可用的输入 buffer int inputBufferIndex = mEncoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex); inputBuffer.clear(); inputBuffer.put(input); // 将输入的数据喂给编码器 mEncoder.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0); } // 取出可用的输出 buffer int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); while (outputBufferIndex >= 0) { mOutputBuffer = mEncoder.getOutputBuffer(outputBufferIndex); mOutputBuffer.position(mBufferInfo.offset); mOutputBuffer.limit(mBufferInfo.offset + mBufferInfo.size); // 写入到复用器 mMuxer.writeSampleData(mTrackIndex, mOutputBuffer, mBufferInfo); // 释放输出 buffer mEncoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0); } } /** * 停止编码 */ public void stop() { if (!mIsStarted) { Log.e(TAG, "Encoder is not started."); return; } mEncoder.stop(); mEncoder.release(); mMuxer.stop(); mMuxer.release(); mIsStarted = false; } /** * 初始化视频轨道 */ public void initTrack() { if (!mIsStarted) { Log.e(TAG, "Encoder is not started."); return; } mTrackIndex = mMuxer.addTrack(mEncoder.getOutputFormat()); mMuxer.start(); mBufferInfo = new MediaCodec.BufferInfo(); } } ``` 这段代码中,我们使用 MediaCodec 进行视频编码,并将编码后的数据通过 MediaMuxer 写入到文件中。 需要注意的是,编码器的输入数据必须是 YUV 格式的数据,而不是一般的 RGB 格式,因此你需要将原始数据转化为 YUV 格式,然后再进行编码。除此之外,还需要注意编码器的配置参数,例如视频宽度、高度、帧率、码率等等。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值