版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010302327/article/details/81907369
我以前写过一篇修改ExoPlayer源码,获取帧时间,只是简单的获取帧时间,这次升个级,添加滤镜,获取帧时间功能保留,并且用来刷新画面,这样就可以实现某些基于帧时间来实现的滤镜功能了,比如动画效果,当然我这边还是简单的用黑白滤镜来演示
效果就不贴了,和我以前写的黑白滤镜播放器一样的。
还是基于修改ExoPlayer源码,获取帧时间来修改
在VideoTimeListener内添加onSurface和onRelease函数
public interface VideoTimeListener {
void onVideoTimeChanged(long time);
Surface onSurface(Surface surface, int width, int height);
void onRelease();
}
在SimpleExoPlayer内的ComponentListener内也添加onSurface和onRelease函数
@Override
public void onVideoTimeChanged(long time) {
for (VideoTimeListener videoTimeListener : videoTimeListeners) {
videoTimeListener.onVideoTimeChanged(time);
}
}
@Override
public Surface onSurface(Surface surface,int width,int height) {
for (VideoTimeListener videoTimeListener : videoTimeListeners) {
return videoTimeListener.onSurface(surface,width,height);
}
return null;
}
@Override
public void onRelease() {
for (VideoTimeListener videoTimeListener : videoTimeListeners) {
videoTimeListener.onRelease();
}
}
在MediaCodecVideoRenderer内添加变量
private long timeUsIndex;
private long timeIndex;
private long timeUs;
在函数configureCodec内修改代码变为
@Override
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) throws DecoderQueryException {
codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats);
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
tunnelingAudioSessionId);
if (surface == null) {
Assertions.checkState(shouldUseDummySurface(codecInfo));
if (dummySurface == null) {
dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure);
}
surface = dummySurface;
}
timeUsIndex = timeIndex = 0;
Surface s = surface;
if(timeListener != null){
s = timeListener.onSurface(surface,mediaFormat.getInteger(MediaFormat.KEY_WIDTH),mediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
}
codec.configure(mediaFormat, s, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
}
}
在函数releaseCodec内添加代码变为
@CallSuper
@Override
protected void releaseCodec() {
try {
super.releaseCodec();
} finally {
buffersInCodecCount = 0;
if (dummySurface != null) {
if (surface == dummySurface) {
surface = null;
}
dummySurface.release();
dummySurface = null;
}
if(timeListener != null){
timeListener.onRelease();
}
}
}
再添加函数,timeUs是微秒级的,可根据自己需求除以1000变毫秒级或者再除以1000变为秒
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
while (timeIndex < timeUsIndex){
timeIndex++;
if(timeListener != null && timeIndex == timeUsIndex){
timeListener.onVideoTimeChanged(timeUs);
}
}
super.render(positionUs, elapsedRealtimeUs);
}
最后修改renderOutputBufferV21和renderOutputBuffer
protected void renderOutputBuffer(MediaCodec codec, int index, long presentationTimeUs) {
maybeNotifyVideoSizeChanged();
TraceUtil.beginSection("releaseOutputBuffer");
codec.releaseOutputBuffer(index, true);
TraceUtil.endSection();
lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
decoderCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0;
maybeNotifyRenderedFirstFrame();
timeUsIndex++;
timeUs = presentationTimeUs;
}
这样整个流程就走通了
接下来就贴Activity代码了
public class MainActivity extends AppCompatActivity {
private View videoPlayerView;
private TextureView textureView;
private SimpleExoPlayer player;
private Handler mainHandler;
private EGLUtils mEglUtils;
private GLFramebuffer mFramebuffer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoPlayerView = findViewById(R.id.video_player);
mainHandler = new Handler();
textureView = findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
init(new Surface(surface));
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
}
private void init(Surface surface){
Uri url = Uri.parse(Environment.getExternalStorageDirectory().getAbsolutePath() +"/HMSDK/video/1531383835814.mp4");
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "ExoPlayerTime"), bandwidthMeter);
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(url, mainHandler,null);
player.addVideoTiemListener(new VideoTimeListener() {
@Override
public Surface onSurface(Surface surface,int width,int height) {
mEglUtils = new EGLUtils();
mEglUtils.initEGL(surface);
mFramebuffer = new GLFramebuffer();
mFramebuffer.initFramebuffer(textureView.getWidth(),
textureView.getHeight(),
width,
height);
return new Surface(mFramebuffer.getSurfaceTexture());
}
@Override
public void onVideoTimeChanged(long time) {
mFramebuffer.drawFrame();
mEglUtils.swap();
}
@Override
public void onRelease() {
if(mEglUtils != null){
mEglUtils.release();
}
}
});
player.setVideoSurface(surface);
player.prepare(videoSource);
}
public void playVideo(View view){
if(player.getContentPosition() >= player.getDuration()){
player.seekTo(0);
}
player.setPlayWhenReady(true);
videoPlayerView.setVisibility(View.INVISIBLE);
isPlayEnd();
}
private Handler seekBarHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(player.getPlayWhenReady() && player.getContentPosition() < player.getDuration()){
isPlayEnd();
}else{
if(!isPlayer){
player.setPlayWhenReady(false);
videoPlayerView.setVisibility(View.VISIBLE);
}
}
}
};
private void isPlayEnd(){
seekBarHandler.removeMessages(100);
Message message = seekBarHandler.obtainMessage();
message.what = 100;
seekBarHandler.sendMessageDelayed(message,100);
}
@Override
protected void onResume() {
super.onResume();
if(player != null){
if(isPlayer){
player.setPlayWhenReady(true);
isPlayer = false;
isPlayEnd();
}
}
}
private boolean isPlayer = false;
@Override
protected void onPause() {
super.onPause();
if(player != null){
if(player.getPlayWhenReady()){
player.setPlayWhenReady(false);
isPlayer = true;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(player != null){
player.stop();
player.release();
player = null;
}
}
}
贴demo
下载:百度云
密码:dbl0
为啥研究这个,因为公司想要做类似简影app的功能,所以研究视频编辑,音频编辑,还有研究播放器把每一帧都取出来进行处理,简影的原理很简单,主要是滤镜特效开发太麻烦,简影团队应该有专门搞滤镜特效的吧,我要自己搞,好惨